Merge with trunk

This commit is contained in:
Tomas Groth 2014-11-26 09:04:09 +00:00
commit e3bd817c5b
48 changed files with 983 additions and 193 deletions

View File

@ -28,7 +28,9 @@
###############################################################################
import sys
import multiprocessing
from openlp.core.common import is_win, is_macosx
from openlp.core import main
@ -36,9 +38,13 @@ if __name__ == '__main__':
"""
Instantiate and run the application.
"""
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support
if is_win():
multiprocessing.freeze_support()
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
# to avoid any potential conflicts.
if sys.platform.startswith('darwin'):
if is_macosx():
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main()

View File

@ -318,6 +318,7 @@ def create_separated_list(string_list):
return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
from .colorbutton import ColorButton
from .filedialog import FileDialog
from .screen import ScreenList
from .listwidgetwithdnd import ListWidgetWithDnD

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 #
###############################################################################
"""
Provide a custom widget based on QPushButton for the selection of colors
"""
from PyQt4 import QtCore, QtGui
from openlp.core.common import translate
class ColorButton(QtGui.QPushButton):
"""
Subclasses QPushbutton to create a "Color Chooser" button
"""
colorChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
"""
Initialise the ColorButton
"""
super(ColorButton, self).__init__()
self.parent = parent
self.change_color('#ffffff')
self.setToolTip(translate('OpenLP.ColorButton', 'Click to select a color.'))
self.clicked.connect(self.on_clicked)
def change_color(self, color):
"""
Sets the _color variable and the background color.
:param color: String representation of a hexidecimal color
"""
self._color = color
self.setStyleSheet('background-color: %s' % color)
@property
def color(self):
"""
Property method to return the color variable
:return: String representation of a hexidecimal color
"""
return self._color
@color.setter
def color(self, color):
"""
Property setter to change the imstamce color
:param color: String representation of a hexidecimal color
"""
self.change_color(color)
def on_clicked(self):
"""
Handle the PushButton clicked signal, showing the ColorDialog and validating the input
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self._color), self.parent)
if new_color.isValid() and self._color != new_color.name():
self.change_color(new_color.name())
self.colorChanged.emit(new_color.name())

View File

@ -250,7 +250,13 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
# Remove two or more option slide breaks next to each other (causing infinite loop).
while '\n[---]\n[---]\n' in text:
text = text.replace('\n[---]\n[---]\n', '\n[---]\n')
while True:
while ' [---]' in text:
text = text.replace(' [---]', '[---]')
while '[---] ' in text:
text = text.replace('[---] ', '[---]')
count = 0
# only loop 5 times as there will never be more than 5 incorrect logical splits on a single slide.
while True and count < 5:
slides = text.split('\n[---]\n', 2)
# If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last
# for now).
@ -296,6 +302,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
lines = text.strip('\n').split('\n')
pages.extend(self._paginate_slide(lines, line_end))
break
count += 1
else:
# Clean up line endings.
pages = self._paginate_slide(text.split('\n'), line_end)

View File

@ -37,7 +37,7 @@ import sys
from PyQt4 import QtCore, QtGui
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.lib import ColorButton, SettingsTab, build_icon
from openlp.core.utils import format_time, get_images_filter
log = logging.getLogger(__name__)
@ -181,7 +181,7 @@ class AdvancedTab(SettingsTab):
self.default_image_layout.setObjectName('default_image_layout')
self.default_color_label = QtGui.QLabel(self.default_image_group_box)
self.default_color_label.setObjectName('default_color_label')
self.default_color_button = QtGui.QPushButton(self.default_image_group_box)
self.default_color_button = ColorButton(self.default_image_group_box)
self.default_color_button.setObjectName('default_color_button')
self.default_image_layout.addRow(self.default_color_label, self.default_color_button)
self.default_file_label = QtGui.QLabel(self.default_image_group_box)
@ -247,7 +247,7 @@ class AdvancedTab(SettingsTab):
self.service_name_time.timeChanged.connect(self.update_service_name_example)
self.service_name_edit.textChanged.connect(self.update_service_name_example)
self.service_name_revert_button.clicked.connect(self.on_service_name_revert_button_clicked)
self.default_color_button.clicked.connect(self.on_default_color_button_clicked)
self.default_color_button.colorChanged.connect(self.on_background_color_changed)
self.default_browse_button.clicked.connect(self.on_default_browse_button_clicked)
self.default_revert_button.clicked.connect(self.on_default_revert_button_clicked)
self.alternate_rows_check_box.toggled.connect(self.on_alternate_rows_check_box_toggled)
@ -299,7 +299,6 @@ class AdvancedTab(SettingsTab):
self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window'))
self.default_image_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Default Image'))
self.default_color_label.setText(translate('OpenLP.AdvancedTab', 'Background color:'))
self.default_color_button.setToolTip(translate('OpenLP.AdvancedTab', 'Click to select a color.'))
self.default_file_label.setText(translate('OpenLP.AdvancedTab', 'Image file:'))
self.default_browse_button.setToolTip(translate('OpenLP.AdvancedTab', 'Browse for an image file to display.'))
self.default_revert_button.setToolTip(translate('OpenLP.AdvancedTab', 'Revert to the default OpenLP logo.'))
@ -395,7 +394,7 @@ class AdvancedTab(SettingsTab):
self.current_data_path = AppLocation.get_data_path()
log.warning('User requested data path set to default %s' % self.current_data_path)
self.data_directory_label.setText(os.path.abspath(self.current_data_path))
self.default_color_button.setStyleSheet('background-color: %s' % self.default_color)
self.default_color_button.color = self.default_color
# Don't allow data directory move if running portable.
if settings.value('advanced/is portable'):
self.data_directory_group_box.hide()
@ -498,14 +497,11 @@ class AdvancedTab(SettingsTab):
self.service_name_edit.setText(UiStrings().DefaultServiceName)
self.service_name_edit.setFocus()
def on_default_color_button_clicked(self):
def on_background_color_changed(self, color):
"""
Select the background colour of the default display screen.
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self.default_color), self)
if new_color.isValid():
self.default_color = new_color.name()
self.default_color_button.setStyleSheet('background-color: %s' % self.default_color)
self.default_color = color
def on_default_browse_button_clicked(self):
"""

View File

@ -96,6 +96,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
"""
# The songs plugin is enabled
if FirstTimePage.Welcome < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
print('Go for songs! %r' % self.songs_check_box.isChecked())
return FirstTimePage.Songs
# The Bibles plugin is enabled
elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
@ -182,6 +183,17 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.no_internet_finish_button.setVisible(False)
# Check if this is a re-run of the wizard.
self.has_run_wizard = Settings().value('core/has run wizard')
if self.has_run_wizard:
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
self.application.set_normal_cursor()
# Sort out internet access for downloads
if self.web_access:
songs = self.config.get('songs', 'languages')
@ -211,7 +223,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
# Download the theme screenshots.
self.theme_screenshot_thread = ThemeScreenshotThread(self)
self.theme_screenshot_thread.start()
self.application.set_normal_cursor()
def update_screen_list_combo(self):
"""

View File

@ -431,7 +431,7 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Import OpenLP settings from a specified *.config file previously '
'exported on this or another machine'))
self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings'))
self.view_projector_manager_item.setText(translate('OPenLP.MainWindow', '&ProjectorManager'))
self.view_projector_manager_item.setText(translate('OPenLP.MainWindow', '&Projector Manager'))
self.view_projector_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Toggle Projector Manager'))
self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow',
'Toggle the visibility of the Projector Manager'))
@ -641,10 +641,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
if view_mode == 'default':
self.mode_default_item.setChecked(True)
elif view_mode == 'setup':
self.set_view_mode(True, True, False, True, False)
self.set_view_mode(True, True, False, True, False, True)
self.mode_setup_item.setChecked(True)
elif view_mode == 'live':
self.set_view_mode(False, True, False, False, True)
self.set_view_mode(False, True, False, False, True, True)
self.mode_live_item.setChecked(True)
def app_startup(self):
@ -981,7 +981,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
# FIXME: We are conflicting with the standard "General" section.
if 'eneral' in section_key:
section_key = section_key.lower()
key_value = settings.value(section_key)
try:
key_value = settings.value(section_key)
except KeyError:
QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'The key "%s" does not have a default value '
'so it will be skipped in this export.') % section_key,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
key_value = None
if key_value is not None:
export_settings.setValue(section_key, key_value)
export_settings.sync()
@ -1003,21 +1010,21 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
Put OpenLP into "Default" view mode.
"""
self.set_view_mode(True, True, True, True, True, 'default')
self.set_view_mode(True, True, True, True, True, True, 'default')
def on_mode_setup_item_clicked(self):
"""
Put OpenLP into "Setup" view mode.
"""
self.set_view_mode(True, True, False, True, False, 'setup')
self.set_view_mode(True, True, False, True, False, True, 'setup')
def on_mode_live_item_clicked(self):
"""
Put OpenLP into "Live" view mode.
"""
self.set_view_mode(False, True, False, False, True, 'live')
self.set_view_mode(False, True, False, False, True, True, 'live')
def set_view_mode(self, media=True, service=True, theme=True, preview=True, live=True, mode=''):
def set_view_mode(self, media=True, service=True, theme=True, preview=True, live=True, projector=True, mode=''):
"""
Set OpenLP to a different view mode.
"""
@ -1027,6 +1034,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.media_manager_dock.setVisible(media)
self.service_manager_dock.setVisible(service)
self.theme_manager_dock.setVisible(theme)
self.projector_manager_dock.setVisible(projector)
self.set_preview_panel_visibility(preview)
self.set_live_panel_visibility(live)
@ -1189,18 +1197,22 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.theme_manager_dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
self.service_manager_dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
self.media_manager_dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
self.projector_manager_dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
self.view_media_manager_item.setEnabled(False)
self.view_service_manager_item.setEnabled(False)
self.view_theme_manager_item.setEnabled(False)
self.view_projector_manager_item.setEnabled(False)
self.view_preview_panel.setEnabled(False)
self.view_live_panel.setEnabled(False)
else:
self.theme_manager_dock.setFeatures(QtGui.QDockWidget.AllDockWidgetFeatures)
self.service_manager_dock.setFeatures(QtGui.QDockWidget.AllDockWidgetFeatures)
self.media_manager_dock.setFeatures(QtGui.QDockWidget.AllDockWidgetFeatures)
self.projector_manager_dock.setFeatures(QtGui.QDockWidget.AllDockWidgetFeatures)
self.view_media_manager_item.setEnabled(True)
self.view_service_manager_item.setEnabled(True)
self.view_theme_manager_item.setEnabled(True)
self.view_projector_manager_item.setEnabled(True)
self.view_preview_panel.setEnabled(True)
self.view_live_panel.setEnabled(True)
Settings().setValue('user interface/lock panel', lock)

View File

@ -32,7 +32,7 @@ The :mod:`~openlp.core.ui.media.playertab` module holds the configuration tab fo
from PyQt4 import QtCore, QtGui
from openlp.core.common import Registry, Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib import ColorButton, SettingsTab
from openlp.core.lib.ui import create_button
from openlp.core.ui.media import get_media_players, set_media_players
@ -76,7 +76,7 @@ class PlayerTab(SettingsTab):
self.background_color_label = QtGui.QLabel(self.background_color_group_box)
self.background_color_label.setObjectName('background_color_label')
self.color_layout.addWidget(self.background_color_label)
self.background_color_button = QtGui.QPushButton(self.background_color_group_box)
self.background_color_button = ColorButton(self.background_color_group_box)
self.background_color_button.setObjectName('background_color_button')
self.color_layout.addWidget(self.background_color_button)
self.form_layout.addRow(self.color_layout)
@ -124,7 +124,7 @@ class PlayerTab(SettingsTab):
self.left_layout.addStretch()
self.right_layout.addStretch()
# Signals and slots
self.background_color_button.clicked.connect(self.on_background_color_button_clicked)
self.background_color_button.colorChanged.connect(self.on_background_color_changed)
def retranslateUi(self):
"""
@ -138,14 +138,11 @@ class PlayerTab(SettingsTab):
'Visible background for videos with aspect ratio different to screen.'))
self.retranslate_players()
def on_background_color_button_clicked(self):
def on_background_color_changed(self, color):
"""
Set the background color
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self.background_color), self)
if new_color.isValid():
self.background_color = new_color.name()
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
self.background_color = color
def on_player_check_box_changed(self, check_state):
"""
@ -212,7 +209,7 @@ class PlayerTab(SettingsTab):
self.background_color = settings.value('background color')
self.initial_color = self.background_color
settings.endGroup()
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
self.background_color_button.color = self.background_color
def save(self):
"""

View File

@ -28,4 +28,4 @@
###############################################################################
"""
The Projector driver module.
"""
"""

View File

@ -494,7 +494,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
service.append({'openlp_core': core})
return service
def save_file(self):
def save_file(self, field=None):
"""
Save the current service file.
@ -1427,9 +1427,10 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
self.drop_position = -1
self.set_modified()
def make_preview(self):
def make_preview(self, field=None):
"""
Send the current item to the Preview slide controller
:param field:
"""
self.application.set_busy_cursor()
item, child = self.find_service_item()

View File

@ -46,7 +46,7 @@ class Ui_SettingsDialog(object):
"""
settings_dialog.setObjectName('settings_dialog')
settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
settings_dialog.resize(800, 500)
settings_dialog.resize(800, 700)
self.dialog_layout = QtGui.QGridLayout(settings_dialog)
self.dialog_layout.setObjectName('dialog_layout')
self.dialog_layout.setMargin(8)

View File

@ -683,7 +683,8 @@ class SlideController(DisplayController, RegistryProperties):
self.play_slides_loop.setChecked(False)
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
if item.is_text():
if Settings().value(self.main_window.songs_settings_section + '/display songbar') and self.slide_list:
if (Settings().value(self.main_window.songs_settings_section + '/display songbar')
and not self.song_menu.menu().isEmpty()):
self.toolbar.set_widget_visible(['song_menu'], True)
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
self.toolbar.set_widget_visible(LOOP_LIST)
@ -691,7 +692,8 @@ class SlideController(DisplayController, RegistryProperties):
self.mediabar.show()
self.previous_item.setVisible(not item.is_media())
self.next_item.setVisible(not item.is_media())
self.set_blank_menu()
# The layout of the toolbar is size dependent, so make sure it fits
self.on_controller_size_changed(self.controller.width())
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
self.toolbar.show()

View File

@ -65,18 +65,18 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
self.theme_layout_form = ThemeLayoutForm(self)
self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed)
self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed)
self.color_button.clicked.connect(self.on_color_button_clicked)
self.image_color_button.clicked.connect(self.on_image_color_button_clicked)
self.gradient_start_button.clicked.connect(self.on_gradient_start_button_clicked)
self.gradient_end_button.clicked.connect(self.on_gradient_end_button_clicked)
self.color_button.colorChanged.connect(self.on_color_changed)
self.image_color_button.colorChanged.connect(self.on_image_color_changed)
self.gradient_start_button.colorChanged.connect(self.on_gradient_start_button_changed)
self.gradient_end_button.colorChanged.connect(self.on_gradient_end_button_changed)
self.image_browse_button.clicked.connect(self.on_image_browse_button_clicked)
self.image_file_edit.editingFinished.connect(self.on_image_file_edit_editing_finished)
self.main_color_button.clicked.connect(self.on_main_color_button_clicked)
self.outline_color_button.clicked.connect(self.on_outline_color_button_clicked)
self.shadow_color_button.clicked.connect(self.on_shadow_color_button_clicked)
self.main_color_button.colorChanged.connect(self.on_main_color_changed)
self.outline_color_button.colorChanged.connect(self.on_outline_color_changed)
self.shadow_color_button.colorChanged.connect(self.on_shadow_color_changed)
self.outline_check_box.stateChanged.connect(self.on_outline_check_check_box_state_changed)
self.shadow_check_box.stateChanged.connect(self.on_shadow_check_check_box_state_changed)
self.footer_color_button.clicked.connect(self.on_footer_color_button_clicked)
self.footer_color_button.colorChanged.connect(self.on_footer_color_changed)
self.customButtonClicked.connect(self.on_custom_1_button_clicked)
self.main_position_check_box.stateChanged.connect(self.on_main_position_check_box_state_changed)
self.footer_position_check_box.stateChanged.connect(self.on_footer_position_check_box_state_changed)
@ -295,14 +295,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
Handle the display and state of the Background page.
"""
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
self.color_button.setStyleSheet('background-color: %s' % self.theme.background_color)
self.color_button.color = self.theme.background_color
self.setField('background_type', 0)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient):
self.gradient_start_button.setStyleSheet('background-color: %s' % self.theme.background_start_color)
self.gradient_end_button.setStyleSheet('background-color: %s' % self.theme.background_end_color)
self.gradient_start_button.color = self.theme.background_start_color
self.gradient_end_button.color = self.theme.background_end_color
self.setField('background_type', 1)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.image_color_button.setStyleSheet('background-color: %s' % self.theme.background_border_color)
self.image_color_button.color = self.theme.background_border_color
self.image_file_edit.setText(self.theme.background_filename)
self.setField('background_type', 2)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
@ -323,14 +323,14 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
Handle the display and state of the Main Area page.
"""
self.main_font_combo_box.setCurrentFont(QtGui.QFont(self.theme.font_main_name))
self.main_color_button.setStyleSheet('background-color: %s' % self.theme.font_main_color)
self.main_color_button.color = self.theme.font_main_color
self.setField('main_size_spin_box', self.theme.font_main_size)
self.setField('line_spacing_spin_box', self.theme.font_main_line_adjustment)
self.setField('outline_check_box', self.theme.font_main_outline)
self.outline_color_button.setStyleSheet('background-color: %s' % self.theme.font_main_outline_color)
self.outline_color_button.color = self.theme.font_main_outline_color
self.setField('outline_size_spin_box', self.theme.font_main_outline_size)
self.setField('shadow_check_box', self.theme.font_main_shadow)
self.shadow_color_button.setStyleSheet('background-color: %s' % self.theme.font_main_shadow_color)
self.shadow_color_button.color = self.theme.font_main_shadow_color
self.setField('shadow_size_spin_box', self.theme.font_main_shadow_size)
self.setField('main_bold_check_box', self.theme.font_main_bold)
self.setField('main_italics_check_box', self.theme.font_main_italics)
@ -340,7 +340,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
Handle the display and state of the Footer Area page.
"""
self.footer_font_combo_box.setCurrentFont(QtGui.QFont(self.theme.font_footer_name))
self.footer_color_button.setStyleSheet('background-color: %s' % self.theme.font_footer_color)
self.footer_color_button.color = self.theme.font_footer_color
self.setField('footer_size_spin_box', self.theme.font_footer_size)
def set_position_page_values(self):
@ -399,33 +399,29 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
self.theme.background_direction = BackgroundGradientType.to_string(index)
self.set_background_page_values()
def on_color_button_clicked(self):
def on_color_changed(self, color):
"""
Background / Gradient 1 _color button pushed.
"""
self.theme.background_color = self._color_button(self.theme.background_color)
self.set_background_page_values()
self.theme.background_color = color
def on_image_color_button_clicked(self):
def on_image_color_changed(self, color):
"""
Background / Gradient 1 _color button pushed.
"""
self.theme.background_border_color = self._color_button(self.theme.background_border_color)
self.set_background_page_values()
self.theme.background_border_color = color
def on_gradient_start_button_clicked(self):
def on_gradient_start_button_changed(self, color):
"""
Gradient 2 _color button pushed.
"""
self.theme.background_start_color = self._color_button(self.theme.background_start_color)
self.set_background_page_values()
self.theme.background_start_color = color
def on_gradient_end_button_clicked(self):
def on_gradient_end_button_changed(self, color):
"""
Gradient 2 _color button pushed.
"""
self.theme.background_end_color = self._color_button(self.theme.background_end_color)
self.set_background_page_values()
self.theme.background_end_color = color
def on_image_browse_button_clicked(self):
"""
@ -445,33 +441,29 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
"""
self.theme.background_filename = str(self.image_file_edit.text())
def on_main_color_button_clicked(self):
def on_main_color_changed(self, color):
"""
Set the main colour value
"""
self.theme.font_main_color = self._color_button(self.theme.font_main_color)
self.set_main_area_page_values()
self.theme.font_main_color = color
def on_outline_color_button_clicked(self):
def on_outline_color_changed(self, color):
"""
Set the outline colour value
"""
self.theme.font_main_outline_color = self._color_button(self.theme.font_main_outline_color)
self.set_main_area_page_values()
self.theme.font_main_outline_color = color
def on_shadow_color_button_clicked(self):
def on_shadow_color_changed(self, color):
"""
Set the shadow colour value
"""
self.theme.font_main_shadow_color = self._color_button(self.theme.font_main_shadow_color)
self.set_main_area_page_values()
self.theme.font_main_shadow_color = color
def on_footer_color_button_clicked(self):
def on_footer_color_changed(self, color):
"""
Set the footer colour value
"""
self.theme.font_footer_color = self._color_button(self.theme.font_footer_color)
self.set_footer_area_page_values()
self.theme.font_footer_color = color
def update_theme(self):
"""
@ -532,12 +524,3 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
return
self.theme_manager.save_theme(self.theme, save_from, save_to)
return QtGui.QDialog.accept(self)
def _color_button(self, field):
"""
Handle _color buttons
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(field), self)
if new_color.isValid():
field = new_color.name()
return field

View File

@ -37,7 +37,7 @@ from xml.etree.ElementTree import ElementTree, XML
from PyQt4 import QtCore, QtGui
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
check_directory_exists, UiStrings, translate
check_directory_exists, UiStrings, translate, is_win
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType
@ -662,8 +662,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
out_file.close()
if image_from and os.path.abspath(image_from) != os.path.abspath(image_to):
try:
encoding = get_filesystem_encoding()
shutil.copyfile(str(image_from).encode(encoding), str(image_to).encode(encoding))
# Windows is always unicode, so no need to encode filenames
if is_win():
shutil.copyfile(image_from, image_to)
else:
encoding = get_filesystem_encoding()
shutil.copyfile(image_from.encode(encoding), image_to.encode(encoding))
except IOError as xxx_todo_changeme:
shutil.Error = xxx_todo_changeme
self.log_exception('Failed to save theme image')

View File

@ -32,7 +32,7 @@ The Create/Edit theme wizard
from PyQt4 import QtCore, QtGui
from openlp.core.common import UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib import build_icon, ColorButton
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
@ -82,7 +82,7 @@ class Ui_ThemeWizard(object):
self.color_layout.setObjectName('color_layout')
self.color_label = QtGui.QLabel(self.color_widget)
self.color_label.setObjectName('color_label')
self.color_button = QtGui.QPushButton(self.color_widget)
self.color_button = ColorButton(self.color_widget)
self.color_button.setObjectName('color_button')
self.color_layout.addRow(self.color_label, self.color_button)
self.color_layout.setItem(1, QtGui.QFormLayout.LabelRole, self.spacer)
@ -94,12 +94,12 @@ class Ui_ThemeWizard(object):
self.gradient_layout.setObjectName('gradient_layout')
self.gradient_start_label = QtGui.QLabel(self.gradient_widget)
self.gradient_start_label.setObjectName('gradient_start_label')
self.gradient_start_button = QtGui.QPushButton(self.gradient_widget)
self.gradient_start_button = ColorButton(self.gradient_widget)
self.gradient_start_button.setObjectName('gradient_start_button')
self.gradient_layout.addRow(self.gradient_start_label, self.gradient_start_button)
self.gradient_end_label = QtGui.QLabel(self.gradient_widget)
self.gradient_end_label.setObjectName('gradient_end_label')
self.gradient_end_button = QtGui.QPushButton(self.gradient_widget)
self.gradient_end_button = ColorButton(self.gradient_widget)
self.gradient_end_button.setObjectName('gradient_end_button')
self.gradient_layout.addRow(self.gradient_end_label, self.gradient_end_button)
self.gradient_type_label = QtGui.QLabel(self.gradient_widget)
@ -117,7 +117,7 @@ class Ui_ThemeWizard(object):
self.image_layout.setObjectName('image_layout')
self.image_color_label = QtGui.QLabel(self.color_widget)
self.image_color_label.setObjectName('image_color_label')
self.image_color_button = QtGui.QPushButton(self.color_widget)
self.image_color_button = ColorButton(self.color_widget)
self.image_color_button.setObjectName('image_color_button')
self.image_layout.addRow(self.image_color_label, self.image_color_button)
self.image_label = QtGui.QLabel(self.image_widget)
@ -156,7 +156,7 @@ class Ui_ThemeWizard(object):
self.main_color_label.setObjectName('main_color_label')
self.main_properties_layout = QtGui.QHBoxLayout()
self.main_properties_layout.setObjectName('main_properties_layout')
self.main_color_button = QtGui.QPushButton(self.main_area_page)
self.main_color_button = ColorButton(self.main_area_page)
self.main_color_button.setObjectName('main_color_button')
self.main_properties_layout.addWidget(self.main_color_button)
self.main_properties_layout.addSpacing(20)
@ -192,7 +192,7 @@ class Ui_ThemeWizard(object):
self.outline_check_box.setObjectName('outline_check_box')
self.outline_layout = QtGui.QHBoxLayout()
self.outline_layout.setObjectName('outline_layout')
self.outline_color_button = QtGui.QPushButton(self.main_area_page)
self.outline_color_button = ColorButton(self.main_area_page)
self.outline_color_button.setEnabled(False)
self.outline_color_button.setObjectName('Outline_color_button')
self.outline_layout.addWidget(self.outline_color_button)
@ -209,7 +209,7 @@ class Ui_ThemeWizard(object):
self.shadow_check_box.setObjectName('shadow_check_box')
self.shadow_layout = QtGui.QHBoxLayout()
self.shadow_layout.setObjectName('shadow_layout')
self.shadow_color_button = QtGui.QPushButton(self.main_area_page)
self.shadow_color_button = ColorButton(self.main_area_page)
self.shadow_color_button.setEnabled(False)
self.shadow_color_button.setObjectName('shadow_color_button')
self.shadow_layout.addWidget(self.shadow_color_button)
@ -235,7 +235,7 @@ class Ui_ThemeWizard(object):
self.footer_area_layout.addRow(self.footer_font_label, self.footer_font_combo_box)
self.footer_color_label = QtGui.QLabel(self.footer_area_page)
self.footer_color_label.setObjectName('footer_color_label')
self.footer_color_button = QtGui.QPushButton(self.footer_area_page)
self.footer_color_button = ColorButton(self.footer_area_page)
self.footer_color_button.setObjectName('footer_color_button')
self.footer_area_layout.addRow(self.footer_color_label, self.footer_color_button)
self.footer_size_label = QtGui.QLabel(self.footer_area_page)

View File

@ -256,7 +256,8 @@ def check_latest_version(current_version):
retries = 0
while True:
try:
remote_version = str(urllib.request.urlopen(req, None, timeout=CONNECTION_TIMEOUT).read().decode()).strip()
remote_version = str(urllib.request.urlopen(req, None,
timeout=CONNECTION_TIMEOUT).read().decode()).strip()
except ConnectionException:
if retries > CONNECTION_RETRIES:
log.exception('Failed to download the latest OpenLP version file')

View File

@ -30,7 +30,7 @@
from PyQt4 import QtGui
from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib import ColorButton, SettingsTab
from openlp.core.lib.ui import create_valign_selection_widgets
@ -57,14 +57,14 @@ class AlertsTab(SettingsTab):
self.font_color_label.setObjectName('font_color_label')
self.color_layout = QtGui.QHBoxLayout()
self.color_layout.setObjectName('color_layout')
self.font_color_button = QtGui.QPushButton(self.font_group_box)
self.font_color_button = ColorButton(self.font_group_box)
self.font_color_button.setObjectName('font_color_button')
self.color_layout.addWidget(self.font_color_button)
self.color_layout.addSpacing(20)
self.background_color_label = QtGui.QLabel(self.font_group_box)
self.background_color_label.setObjectName('background_color_label')
self.color_layout.addWidget(self.background_color_label)
self.background_color_button = QtGui.QPushButton(self.font_group_box)
self.background_color_button = ColorButton(self.font_group_box)
self.background_color_button.setObjectName('background_color_button')
self.color_layout.addWidget(self.background_color_button)
self.font_layout.addRow(self.font_color_label, self.color_layout)
@ -95,8 +95,8 @@ class AlertsTab(SettingsTab):
self.right_layout.addWidget(self.preview_group_box)
self.right_layout.addStretch()
# Signals and slots
self.background_color_button.clicked.connect(self.on_background_color_button_clicked)
self.font_color_button.clicked.connect(self.on_font_color_button_clicked)
self.background_color_button.colorChanged.connect(self.on_background_color_changed)
self.font_color_button.colorChanged.connect(self.on_font_color_changed)
self.font_combo_box.activated.connect(self.on_font_combo_box_clicked)
self.timeout_spin_box.valueChanged.connect(self.on_timeout_spin_box_changed)
self.font_size_spin_box.valueChanged.connect(self.on_font_size_spin_box_changed)
@ -113,15 +113,12 @@ class AlertsTab(SettingsTab):
self.preview_group_box.setTitle(UiStrings().Preview)
self.font_preview.setText(UiStrings().OLPV2x)
def on_background_color_button_clicked(self):
def on_background_color_changed(self, color):
"""
The background color has been changed.
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self.background_color), self)
if new_color.isValid():
self.background_color = new_color.name()
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
self.update_display()
self.background_color = color
self.update_display()
def on_font_combo_box_clicked(self):
"""
@ -129,15 +126,12 @@ class AlertsTab(SettingsTab):
"""
self.update_display()
def on_font_color_button_clicked(self):
def on_font_color_changed(self, color):
"""
The Font Color button has clicked.
"""
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self.font_color), self)
if new_color.isValid():
self.font_color = new_color.name()
self.font_color_button.setStyleSheet('background-color: %s' % self.font_color)
self.update_display()
self.font_color = color
self.update_display()
def on_timeout_spin_box_changed(self):
"""
@ -169,8 +163,8 @@ class AlertsTab(SettingsTab):
settings.endGroup()
self.font_size_spin_box.setValue(self.font_size)
self.timeout_spin_box.setValue(self.timeout)
self.font_color_button.setStyleSheet('background-color: %s' % self.font_color)
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
self.font_color_button.color = self.font_color
self.background_color_button.color = self.background_color
self.vertical_combo_box.setCurrentIndex(self.location)
font = QtGui.QFont()
font.setFamily(self.font_face)

View File

@ -330,7 +330,10 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
if not book_ref_id:
book_ref_id = bible.get_book_ref_id_by_localised_name(book, language_selection)
elif not bible.get_book_by_book_ref_id(book_ref_id):
book_ref_id = False
return False
# We have not found the book so do not continue
if not book_ref_id:
return False
ranges = match.group('ranges')
range_list = get_reference_match('range_separator').split(ranges)
ref_list = []

View File

@ -30,7 +30,7 @@
from PyQt4 import QtGui
from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib import ColorButton, SettingsTab
class ImageTab(SettingsTab):
@ -51,7 +51,7 @@ class ImageTab(SettingsTab):
self.background_color_label = QtGui.QLabel(self.background_color_group_box)
self.background_color_label.setObjectName('background_color_label')
self.color_layout.addWidget(self.background_color_label)
self.background_color_button = QtGui.QPushButton(self.background_color_group_box)
self.background_color_button = ColorButton(self.background_color_group_box)
self.background_color_button.setObjectName('background_color_button')
self.color_layout.addWidget(self.background_color_button)
self.form_layout.addRow(self.color_layout)
@ -64,7 +64,7 @@ class ImageTab(SettingsTab):
self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.right_layout.addStretch()
# Signals and slots
self.background_color_button.clicked.connect(self.on_background_color_button_clicked)
self.background_color_button.colorChanged.connect(self.on_background_color_changed)
def retranslateUi(self):
self.background_color_group_box.setTitle(UiStrings().BackgroundColor)
@ -72,11 +72,8 @@ class ImageTab(SettingsTab):
self.information_label.setText(
translate('ImagesPlugin.ImageTab', 'Visible background for images with aspect ratio different to screen.'))
def on_background_color_button_clicked(self):
new_color = QtGui.QColorDialog.getColor(QtGui.QColor(self.background_color), self)
if new_color.isValid():
self.background_color = new_color.name()
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
def on_background_color_changed(self, color):
self.background_color = color
def load(self):
settings = Settings()
@ -84,7 +81,7 @@ class ImageTab(SettingsTab):
self.background_color = settings.value('background color')
self.initial_color = self.background_color
settings.endGroup()
self.background_color_button.setStyleSheet('background-color: %s' % self.background_color)
self.background_color_button.color = self.background_color
def save(self):
settings = Settings()

View File

@ -446,6 +446,13 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryPropert
# Set media length info
self.playback_length = self.vlc_media_player.get_length()
log.debug('playback_length: %d ms' % self.playback_length)
# if length is 0, wait a bit, maybe vlc will change its mind...
loop_count = 0
while self.playback_length == 0 and loop_count < 20:
sleep(0.1)
self.playback_length = self.vlc_media_player.get_length()
loop_count += 1
log.debug('in loop, playback_length: %d ms' % self.playback_length)
self.position_slider.setMaximum(self.playback_length)
# setup start and end time
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)

View File

@ -48,14 +48,15 @@ log = logging.getLogger(__name__)
def song_generator(songs):
"""
This is a generator function to return tuples of two songs. When completed then all songs have once been returned
combined with any other songs.
This is a generator function to return tuples of tuple with two songs and their position in the song array.
When completed then all songs have once been returned combined with any other songs.
:param songs: All songs in the database.
"""
for outer_song_counter in range(len(songs) - 1):
for inner_song_counter in range(outer_song_counter + 1, len(songs)):
yield (songs[outer_song_counter], songs[inner_song_counter])
yield ((outer_song_counter, songs[outer_song_counter].search_lyrics),
(inner_song_counter, songs[inner_song_counter].search_lyrics))
class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
@ -187,16 +188,17 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
# Do not accept any further tasks. Also this closes the processes if all tasks are done.
pool.close()
# While the processes are still working, start to look at the results.
for song_tuple in result:
for pos_tuple in result:
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
# The call to process_events() will keep the GUI responsive.
self.application.process_events()
if self.break_search:
pool.terminate()
return
if song_tuple is None:
if pos_tuple is None:
continue
song1, song2 = song_tuple
song1 = songs[pos_tuple[0]]
song2 = songs[pos_tuple[1]]
duplicate_added = self.add_duplicates_to_song_list(song1, song2)
if duplicate_added:
self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title)

View File

@ -89,13 +89,19 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
def __init__(self, parent=None, plugin=None, db_manager=None):
QtGui.QDialog.__init__(self, parent)
self.plugin = plugin
self.db_manager = db_manager
self.setup_ui(self)
def initialise(self):
"""
Initialise the SongSelectForm
"""
self.thread = None
self.worker = None
self.song_count = 0
self.song = None
self.plugin = plugin
self.song_select_importer = SongSelectImport(db_manager)
self.song_select_importer = SongSelectImport(self.db_manager)
self.save_password_checkbox.toggled.connect(self.on_save_password_checkbox_toggled)
self.login_button.clicked.connect(self.on_login_button_clicked)
self.search_button.clicked.connect(self.on_search_button_clicked)
@ -264,6 +270,9 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
self.login_progress_bar.setValue(0)
self.login_spacer.setVisible(True)
self.login_button.setEnabled(True)
self.username_edit.setEnabled(True)
self.password_edit.setEnabled(True)
self.save_password_checkbox.setEnabled(True)
self.search_combobox.setFocus()
self.application.process_events()

View File

@ -99,7 +99,7 @@ class WordsOfWorshipImport(SongImport):
"""
Initialise the Words of Worship importer.
"""
SongImport.__init__(self, manager, **kwargs)
super(WordsOfWorshipImport, self).__init__(manager, **kwargs)
def do_import(self):
"""
@ -112,17 +112,17 @@ class WordsOfWorshipImport(SongImport):
return
self.set_defaults()
song_data = open(source, 'rb')
if song_data.read(19) != 'WoW File\nSong Words':
if song_data.read(19).decode() != 'WoW File\nSong Words':
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "Wow File\\nSong '
'Invalid Words of Worship song file. Missing "WoW File\\nSong '
'Words" header.')))
continue
# Seek to byte which stores number of blocks in the song
song_data.seek(56)
no_of_blocks = ord(song_data.read(1))
song_data.seek(66)
if song_data.read(16) != 'CSongDoc::CBlock':
if song_data.read(16).decode() != 'CSongDoc::CBlock':
self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
@ -131,11 +131,17 @@ class WordsOfWorshipImport(SongImport):
# Seek to the beginning of the first block
song_data.seek(82)
for block in range(no_of_blocks):
skip_char_at_end = True
self.lines_to_read = ord(song_data.read(4)[:1])
block_text = ''
while self.lines_to_read:
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
song_data.seek(1, os.SEEK_CUR)
if skip_char_at_end:
skip_char = ord(song_data.read(1))
# Check if we really should skip a char. In some wsg files we shouldn't
if skip_char != 0:
song_data.seek(-1, os.SEEK_CUR)
skip_char_at_end = False
if block_text:
block_text += '\n'
block_text += self.line_text

View File

@ -93,7 +93,7 @@ class WorshipAssistantImport(SongImport):
details = chardet.detect(detect_content)
detect_file.close()
songs_file = open(self.import_source, 'r', encoding=details['encoding'])
songs_reader = csv.DictReader(songs_file)
songs_reader = csv.DictReader(songs_file, escapechar='\\')
try:
records = list(songs_reader)
except csv.Error as e:
@ -104,6 +104,8 @@ class WorshipAssistantImport(SongImport):
num_records = len(records)
log.info('%s records found in CSV file' % num_records)
self.import_wizard.progress_bar.setMaximum(num_records)
# Create regex to strip html tags
re_html_strip = re.compile(r'<[^>]+>')
for index, record in enumerate(records, 1):
if self.stop_import_flag:
return
@ -119,13 +121,12 @@ class WorshipAssistantImport(SongImport):
self.title = record['TITLE']
if record['AUTHOR'] != EMPTY_STR:
self.parse_author(record['AUTHOR'])
print(record['AUTHOR'])
if record['COPYRIGHT'] != EMPTY_STR:
self.add_copyright(record['COPYRIGHT'])
if record['CCLINR'] != EMPTY_STR:
self.ccli_number = record['CCLINR']
if record['ROADMAP'] != EMPTY_STR:
verse_order_list = record['ROADMAP'].split(',')
verse_order_list = [x.strip() for x in record['ROADMAP'].split(',')]
lyrics = record['LYRICS2']
except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d' % index),
@ -136,8 +137,15 @@ class WorshipAssistantImport(SongImport):
'File not valid WorshipAssistant CSV format.'), 'TypeError: %s' % e)
return
verse = ''
used_verses = []
for line in lyrics.splitlines():
if line.startswith('['): # verse marker
# Add previous verse
if verse:
# remove trailing linebreak, part of the WA syntax
self.add_verse(verse[:-1], verse_id)
used_verses.append(verse_id)
verse = ''
# drop the square brackets
right_bracket = line.find(']')
content = line[1:right_bracket].lower()
@ -152,19 +160,31 @@ class WorshipAssistantImport(SongImport):
verse_index = VerseType.from_loose_input(verse_tag) if verse_tag else 0
verse_tag = VerseType.tags[verse_index]
# Update verse order when the verse name has changed
if content != verse_tag + verse_num:
verse_id = verse_tag + verse_num
# Make sure we've not choosen an id already used
while verse_id in verse_order_list and content in verse_order_list:
verse_num = str(int(verse_num) + 1)
verse_id = verse_tag + verse_num
if content != verse_id:
for i in range(len(verse_order_list)):
if verse_order_list[i].lower() == content.lower():
verse_order_list[i] = verse_tag + verse_num
elif line and not line.isspace():
verse += line + '\n'
elif verse:
self.add_verse(verse, verse_tag+verse_num)
verse = ''
verse_order_list[i] = verse_id
else:
# add line text to verse. Strip out html
verse += re_html_strip.sub('', line) + '\n'
if verse:
self.add_verse(verse, verse_tag+verse_num)
# remove trailing linebreak, part of the WA syntax
if verse.endswith('\n\n'):
verse = verse[:-1]
self.add_verse(verse, verse_id)
used_verses.append(verse_id)
if verse_order_list:
self.verse_order_list = verse_order_list
# Use the verse order in the import, but remove entries that doesn't have a text
cleaned_verse_order_list = []
for verse in verse_order_list:
if verse in used_verses:
cleaned_verse_order_list.append(verse)
self.verse_order_list = cleaned_verse_order_list
if not self.finish():
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index
+ (': "' + self.title + '"' if self.title else ''))

View File

@ -339,6 +339,9 @@ class SongMediaItem(MediaManagerItem):
self.remote_song = -1
self.remote_triggered = None
if item:
if preview:
# A song can only be edited if it comes from plugin, so we set it again for the new item.
item.from_plugin = True
return item
return None

View File

@ -75,9 +75,14 @@ class OpenLyricsExport(RegistryProperties):
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename)
# Ensure the filename isn't too long for some filesystems
filename = '%s.xml' % filename[0:250 - len(self.save_path)]
filename_with_ext = '%s.xml' % filename[0:250 - len(self.save_path)]
# Make sure we're not overwriting an existing file
conflicts = 0
while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
conflicts += 1
filename_with_ext = '%s-%d.xml' % (filename[0:247 - len(self.save_path)], conflicts)
# Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename), 'wb'), encoding='utf-8', xml_declaration=True,
pretty_print=True)
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8',
xml_declaration=True, pretty_print=True)
return True

View File

@ -59,12 +59,14 @@ def songs_probably_equal(song_tupel):
:param song_tupel: A tuple of two songs to compare.
"""
song1, song2 = song_tupel
if len(song1.search_lyrics) < len(song2.search_lyrics):
small = song1.search_lyrics
large = song2.search_lyrics
pos1, lyrics1 = song1
pos2, lyrics2 = song2
if len(lyrics1) < len(lyrics2):
small = lyrics1
large = lyrics2
else:
small = song2.search_lyrics
large = song1.search_lyrics
small = lyrics2
large = lyrics1
differ = difflib.SequenceMatcher(a=large, b=small)
diff_tuples = differ.get_opcodes()
diff_no_typos = _remove_typos(diff_tuples)
@ -77,7 +79,7 @@ def songs_probably_equal(song_tupel):
length_of_equal_blocks += _op_length(element)
if length_of_equal_blocks >= MIN_BLOCK_SIZE:
return song1, song2
return pos1, pos2
# Check 2: Similarity based on the relative length of the longest equal block.
# Calculate the length of the largest equal block of the diff set.
length_of_longest_equal_block = 0
@ -85,7 +87,7 @@ def songs_probably_equal(song_tupel):
if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block:
length_of_longest_equal_block = _op_length(element)
if length_of_longest_equal_block > len(small) * 2 // 3:
return song1, song2
return pos1, pos2
# Both checks failed. We assume the songs are not equal.
return None

View File

@ -104,6 +104,7 @@ class SongsPlugin(Plugin):
log.info('Songs Initialising')
super(SongsPlugin, self).initialise()
self.songselect_form = SongSelectForm(Registry().get('main_window'), self, self.manager)
self.songselect_form.initialise()
self.song_import_item.setVisible(True)
self.song_export_item.setVisible(True)
self.tools_reindex_item.setVisible(True)

View File

@ -99,7 +99,7 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog, RegistryPrope
report_file_name = os.path.join(path, file_name)
file_handle = None
try:
file_handle = open(report_file_name, 'w')
file_handle = open(report_file_name, 'wb')
for instance in usage:
record = '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
'\"%s\",\"%s\"\n' % \

View File

@ -115,3 +115,15 @@ class TestSettings(TestCase, TestMixin):
# THEN the new value is returned when re-read
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def settings_nonexisting_test(self):
"""
Test the Settings on query for non-existing value
"""
# GIVEN: A new Settings setup
with self.assertRaises(KeyError) as cm:
# WHEN reading a setting that doesn't exists
does_not_exist_value = Settings().value('core/does not exists')
# THEN: An exception with the non-existing key should be thrown
self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')

View File

@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 openlp.core.lib.filedialog module
"""
from unittest import TestCase
from openlp.core.lib.colorbutton import ColorButton
from tests.functional import MagicMock, call, patch
class TestColorDialog(TestCase):
"""
Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class
"""
def setUp(self):
self.change_color_patcher = patch('openlp.core.lib.colorbutton.ColorButton.change_color')
self.clicked_patcher = patch('openlp.core.lib.colorbutton.ColorButton.clicked')
self.color_changed_patcher = patch('openlp.core.lib.colorbutton.ColorButton.colorChanged')
self.qt_gui_patcher = patch('openlp.core.lib.colorbutton.QtGui')
self.translate_patcher = patch('openlp.core.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'})
self.addCleanup(self.change_color_patcher.stop)
self.addCleanup(self.clicked_patcher.stop)
self.addCleanup(self.color_changed_patcher.stop)
self.addCleanup(self.qt_gui_patcher.stop)
self.addCleanup(self.translate_patcher.stop)
self.mocked_change_color = self.change_color_patcher.start()
self.mocked_clicked = self.clicked_patcher.start()
self.mocked_color_changed = self.color_changed_patcher.start()
self.mocked_qt_gui = self.qt_gui_patcher.start()
self.mocked_translate = self.translate_patcher.start()
def constructor_test(self):
"""
Test that constructing a ColorButton object works correctly
"""
# GIVEN: The ColorButton class, a mocked change_color, setToolTip methods and clicked signal
with patch('openlp.core.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip:
# WHEN: The ColorButton object is instantiated
widget = ColorButton()
# THEN: The widget __init__ method should have the correct properties and methods called
self.assertEqual(widget.parent, None,
'The parent should be the same as the one that the class was instianted with')
self.mocked_change_color.assert_called_once_with('#ffffff')
mocked_set_tool_tip.assert_called_once_with('Tool Tip Text')
self.mocked_clicked.connect.assert_called_once_with(widget.on_clicked)
def change_color_test(self):
"""
Test that change_color sets the new color and the stylesheet
"""
self.change_color_patcher.stop()
# GIVEN: An instance of the ColorButton object, and a mocked out setStyleSheet
with patch('openlp.core.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet:
widget = ColorButton()
# WHEN: Changing the color
widget.change_color('#000000')
# THEN: The _color attribute should be set to #000000 and setStyleSheet should have been called twice
self.assertEqual(widget._color, '#000000', '_color should have been set to #000000')
mocked_set_style_sheet.assert_has_calls(
[call('background-color: #ffffff'), call('background-color: #000000')])
self.mocked_change_color = self.change_color_patcher.start()
def color_test(self):
"""
Test that the color property method returns the set color
"""
# GIVEN: An instance of ColorButton, with a set _color attribute
widget = ColorButton()
widget._color = '#000000'
# WHEN: Accesing the color property
value = widget.color
# THEN: The value set in _color should be returned
self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
def color_test(self):
"""
Test that the color property method returns the set color
"""
# GIVEN: An instance of ColorButton, with a set _color attribute
widget = ColorButton()
widget._color = '#000000'
# WHEN: Accesing the color property
value = widget.color
# THEN: The value set in _color should be returned
self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
def color_setter_test(self):
"""
Test that the color property setter method sets the color
"""
# GIVEN: An instance of ColorButton, with a mocked __init__
with patch('openlp.core.lib.colorbutton.ColorButton.__init__', **{'return_value': None}):
widget = ColorButton()
# WHEN: Setting the color property
widget.color = '#000000'
# THEN: Then change_color should have been called with the value we set
self.mocked_change_color.assert_called_once_with('#000000')
def on_clicked_invalid_color_test(self):
"""
Test the on_click method when an invalid color has been supplied
"""
# GIVEN: An instance of ColorButton, and a set _color attribute
widget = ColorButton()
self.mocked_change_color.reset_mock()
self.mocked_color_changed.reset_mock()
widget._color = '#000000'
# WHEN: The on_clicked method is called, and the color is invalid
self.mocked_qt_gui.QColorDialog.getColor.return_value = MagicMock(**{'isValid.return_value': False})
widget.on_clicked()
# THEN: change_color should not have been called and the colorChanged signal should not have been emitted
self.assertEqual(
self.mocked_change_color.call_count, 0, 'change_color should not have been called with an invalid color')
self.assertEqual(
self.mocked_color_changed.emit.call_count, 0,
'colorChange signal should not have been emitted with an invalid color')
def on_clicked_same_color_test(self):
"""
Test the on_click method when a new color has not been chosen
"""
# GIVEN: An instance of ColorButton, and a set _color attribute
widget = ColorButton()
self.mocked_change_color.reset_mock()
self.mocked_color_changed.reset_mock()
widget._color = '#000000'
# WHEN: The on_clicked method is called, and the color is valid, but the same as the existing color
self.mocked_qt_gui.QColorDialog.getColor.return_value = MagicMock(
**{'isValid.return_value': True, 'name.return_value': '#000000'})
widget.on_clicked()
# THEN: change_color should not have been called and the colorChanged signal should not have been emitted
self.assertEqual(
self.mocked_change_color.call_count, 0,
'change_color should not have been called when the color has not changed')
self.assertEqual(
self.mocked_color_changed.emit.call_count, 0,
'colorChange signal should not have been emitted when the color has not changed')
def on_clicked_new_color_test(self):
"""
Test the on_click method when a new color has been chosen and is valid
"""
# GIVEN: An instance of ColorButton, and a set _color attribute
widget = ColorButton()
self.mocked_change_color.reset_mock()
self.mocked_color_changed.reset_mock()
widget._color = '#000000'
# WHEN: The on_clicked method is called, and the color is valid, and different to the existing color
self.mocked_qt_gui.QColorDialog.getColor.return_value = MagicMock(
**{'isValid.return_value': True, 'name.return_value': '#ffffff'})
widget.on_clicked()
# THEN: change_color should have been called and the colorChanged signal should have been emitted
self.mocked_change_color.assert_call_once_with('#ffffff')
self.mocked_color_changed.emit.assert_called_once_with('#ffffff')

View File

@ -31,7 +31,7 @@ class TestFileDialog(TestCase):
Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
(returns an empty QStringList)
"""
self.mocked_os.reset()
self.mocked_os.reset_mock()
# GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = []
@ -50,8 +50,8 @@ class TestFileDialog(TestCase):
Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
returns a good file name, a url encoded file name and a non-existing file
"""
self.mocked_os.rest()
self.mocked_qt_gui.reset()
self.mocked_os.rest_mock()
self.mocked_qt_gui.reset_mock()
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file
# names.
@ -59,6 +59,8 @@ class TestFileDialog(TestCase):
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
'/Valid File', '/url encoded file #1']
self.mocked_ui_strings().FileNotFound = 'File Not Found'
self.mocked_ui_strings().FileNotFoundMessage = 'File %s not found.\nPlease try selecting it individually.'
# WHEN: FileDialog.getOpenFileNames is called
result = FileDialog.getOpenFileNames(self.mocked_parent)
@ -68,6 +70,7 @@ class TestFileDialog(TestCase):
call_list = [call('/Valid File'), call('/url%20encoded%20file%20%231'), call('/url encoded file #1'),
call('/non-existing'), call('/non-existing')]
self.mocked_os.path.exists.assert_has_calls(call_list)
self.mocked_qt_gui.QmessageBox.information.called_with(self.mocked_parent, UiStrings().FileNotFound,
UiStrings().FileNotFoundMessage % '/non-existing')
self.mocked_qt_gui.QMessageBox.information.assert_called_with(
self.mocked_parent, 'File Not Found',
'File /non-existing not found.\nPlease try selecting it individually.')
self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')

View File

@ -34,7 +34,7 @@ from unittest import TestCase
from PyQt4 import QtCore
from openlp.core.common import Registry
from openlp.core.lib import Renderer, ScreenList
from openlp.core.lib import Renderer, ScreenList, ServiceItem
from tests.interfaces import MagicMock
@ -88,7 +88,7 @@ class TestRenderer(TestCase):
expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}',
'<strong><span style="-webkit-text-fill-color:red">')
# WHEN:
# WHEN: The renderer converts the start tags
result = renderer._get_start_tags(given_raw_text)
# THEN: Check if the correct tuple is returned.
@ -104,8 +104,59 @@ class TestRenderer(TestCase):
given_line = 'beginning asdf \n end asdf'
expected_words = ['beginning', 'asdf', 'end', 'asdf']
# WHEN: Split the line
# WHEN: Split the line based on word split rules
result_words = renderer._words_split(given_line)
# THEN: The word lists should be the same.
self.assertListEqual(result_words, expected_words)
def format_slide_logical_split_test(self):
"""
Test that a line with text and a logic break does not break the renderer just returns the input
"""
# GIVEN: A line of with a space text and the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = 'a\n[---]\nb'
expected_words = ['a<br>[---]<br>b']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The word lists should be the same.
self.assertListEqual(result_words, expected_words)
def format_slide_blank_before_split_test(self):
"""
Test that a line with blanks before the logical split at handled
"""
# GIVEN: A line of with a space before the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = '\n [---]\n'
expected_words = ['<br> [---]']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The blanks have been removed.
self.assertListEqual(result_words, expected_words)
def format_slide_blank_after_split_test(self):
"""
Test that a line with blanks before the logical split at handled
"""
# GIVEN: A line of with a space after the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = '\n[---] \n'
expected_words = ['<br>[---] ']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The blanks have been removed.
self.assertListEqual(result_words, expected_words)

View File

@ -157,4 +157,4 @@ class TestSettingsForm(TestCase):
# THEN: The general tab's cancel() method should have been called, but not the themes tab
mocked_general_cancel.assert_called_with()
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called')
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called')

View File

@ -95,4 +95,4 @@ class TestImageMediaItem(TestCase, TestMixin):
self.form.save()
# THEN: the post process should be requested
self.assertEqual(1, self.form.settings_form.register_post_process.call_count,
'Image Post processing should have been requested')
'Image Post processing should have been requested')

View File

@ -58,8 +58,6 @@ class TestLib(TestCase):
i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the
old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it
some day for a crown'''
self.song1 = MagicMock()
self.song2 = MagicMock()
def clean_string_test(self):
"""
@ -92,53 +90,53 @@ class TestLib(TestCase):
Test the songs_probably_equal function with twice the same song.
"""
# GIVEN: Two equal songs.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.full_lyrics
song_tuple1 = (2, self.full_lyrics)
song_tuple2 = (4, self.full_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2))
result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple..
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
assert result == (2, 4), 'The result should be the tuble of song positions'
def songs_probably_equal_short_song_test(self):
"""
Test the songs_probably_equal function with a song and a shorter version of the same song.
"""
# GIVEN: A song and a short version of the same song.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.short_lyrics
song_tuple1 = (1, self.full_lyrics)
song_tuple2 = (3, self.short_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2))
result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple..
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
assert result == (1, 3), 'The result should be the tuble of song positions'
def songs_probably_equal_error_song_test(self):
"""
Test the songs_probably_equal function with a song and a very erroneous version of the same song.
"""
# GIVEN: A song and the same song with lots of errors.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.error_lyrics
song_tuple1 = (4, self.full_lyrics)
song_tuple2 = (7, self.error_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2))
result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple of songs..
assert result == (self.song1, self.song2), 'The result should be the tuble of songs'
# THEN: The result should be a tuple of song positions.
assert result == (4, 7), 'The result should be the tuble of song positions'
def songs_probably_equal_different_song_test(self):
"""
Test the songs_probably_equal function with two different songs.
"""
# GIVEN: Two different songs.
self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.different_lyrics
song_tuple1 = (5, self.full_lyrics)
song_tuple2 = (8, self.different_lyrics)
# WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2))
result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be None.
assert result is None, 'The result should be None'

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 OpenLyrics song importer.
"""
import os
import shutil
from unittest import TestCase
from tempfile import mkdtemp
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
from openlp.core.common import Registry
class TestOpenLyricsExport(TestCase, TestMixin):
"""
Test the functions in the :mod:`openlyricsexport` module.
"""
def setUp(self):
"""
Create the registry
"""
Registry.create()
self.temp_folder = mkdtemp()
def tearDown(self):
"""
Cleanup
"""
shutil.rmtree(self.temp_folder)
def export_same_filename_test(self):
"""
Test that files is not overwritten if songs has same title and author
"""
# GIVEN: A mocked song_to_xml, 2 mocked songs, a mocked application and an OpenLyricsExport instance
with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml:
mocked_song_to_xml.return_value = '<?xml version="1.0" encoding="UTF-8"?>\n<empty/>'
author = MagicMock()
author.display_name = 'Test Author'
song = MagicMock()
song.authors = [author]
song.title = 'Test Title'
parent = MagicMock()
parent.stop_export_flag = False
mocked_application_object = MagicMock()
Registry().register('application', mocked_application_object)
ol_export = OpenLyricsExport(parent, [song, song], self.temp_folder)
# WHEN: Doing the export
ol_export.do_export()
# THEN: The exporter should have created 2 files
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s).xml' % (song.title, author.display_name))))
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s)-1.xml' % (song.title, author.display_name))))

View File

@ -31,14 +31,17 @@ This module contains tests for the CCLI SongSelect importer.
"""
from unittest import TestCase
from urllib.error import URLError
from openlp.core import Registry
from openlp.plugins.songs.forms.songselectform import SongSelectForm
from openlp.plugins.songs.lib import Author, Song
from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL
from tests.functional import MagicMock, patch, call
from tests.helpers.testmixin import TestMixin
class TestSongSelect(TestCase):
class TestSongSelectImport(TestCase, TestMixin):
"""
Test the :class:`~openlp.plugins.songs.lib.songselect.SongSelectImport` class
"""
@ -380,3 +383,87 @@ class TestSongSelect(TestCase):
mocked_db_manager.get_object_filtered.assert_called_with(MockedAuthor, False)
self.assertEqual(0, MockedAuthor.populate.call_count, 'A new author should not have been instantiated')
self.assertEqual(1, len(result.authors_songs), 'There should only be one author')
class TestSongSelectForm(TestCase, TestMixin):
"""
Test the :class:`~openlp.plugins.songs.forms.songselectform.SongSelectForm` class
"""
def setUp(self):
"""
Some set up for this test suite
"""
self.setup_application()
self.app.setApplicationVersion('0.0')
self.app.process_events = lambda: None
Registry.create()
Registry().register('application', self.app)
def create_form_test(self):
"""
Test that we can create the SongSelect form
"""
# GIVEN: The SongSelectForm class and a mocked db manager
mocked_plugin = MagicMock()
mocked_db_manager = MagicMock()
# WHEN: We create an instance
ssform = SongSelectForm(None, mocked_plugin, mocked_db_manager)
# THEN: The correct properties should have been assigned
self.assertEqual(mocked_plugin, ssform.plugin, 'The correct plugin should have been assigned')
self.assertEqual(mocked_db_manager, ssform.db_manager, 'The correct db_manager should have been assigned')
def login_fails_test(self):
"""
Test that when the login fails, the form returns to the correct state
"""
# GIVEN: A valid SongSelectForm with a mocked out SongSelectImport, and a bunch of mocked out controls
with patch('openlp.plugins.songs.forms.songselectform.SongSelectImport') as MockedSongSelectImport, \
patch('openlp.plugins.songs.forms.songselectform.QtGui.QMessageBox.critical') as mocked_critical, \
patch('openlp.plugins.songs.forms.songselectform.translate') as mocked_translate:
mocked_song_select_import = MagicMock()
mocked_song_select_import.login.return_value = False
MockedSongSelectImport.return_value = mocked_song_select_import
mocked_translate.side_effect = lambda *args: args[1]
ssform = SongSelectForm(None, MagicMock(), MagicMock())
ssform.initialise()
with patch.object(ssform, 'username_edit') as mocked_username_edit, \
patch.object(ssform, 'password_edit') as mocked_password_edit, \
patch.object(ssform, 'save_password_checkbox') as mocked_save_password_checkbox, \
patch.object(ssform, 'login_button') as mocked_login_button, \
patch.object(ssform, 'login_spacer') as mocked_login_spacer, \
patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar, \
patch.object(ssform.application, 'process_events') as mocked_process_events:
# WHEN: The login button is clicked, and the login is rigged to fail
ssform.on_login_button_clicked()
# THEN: The right things should have happened in the right order
expected_username_calls = [call(False), call(True)]
expected_password_calls = [call(False), call(True)]
expected_save_password_calls = [call(False), call(True)]
expected_login_btn_calls = [call(False), call(True)]
expected_login_spacer_calls = [call(False), call(True)]
expected_login_progress_visible_calls = [call(True), call(False)]
expected_login_progress_value_calls = [call(0), call(0)]
self.assertEqual(expected_username_calls, mocked_username_edit.setEnabled.call_args_list,
'The username edit should be disabled then enabled')
self.assertEqual(expected_password_calls, mocked_password_edit.setEnabled.call_args_list,
'The password edit should be disabled then enabled')
self.assertEqual(expected_save_password_calls, mocked_save_password_checkbox.setEnabled.call_args_list,
'The save password checkbox should be disabled then enabled')
self.assertEqual(expected_login_btn_calls, mocked_login_button.setEnabled.call_args_list,
'The login button should be disabled then enabled')
self.assertEqual(expected_login_spacer_calls, mocked_login_spacer.setVisible.call_args_list,
'Thee login spacer should be make invisible, then visible')
self.assertEqual(expected_login_progress_visible_calls,
mocked_login_progress_bar.setVisible.call_args_list,
'Thee login progress bar should be make visible, then invisible')
self.assertEqual(expected_login_progress_value_calls, mocked_login_progress_bar.setValue.call_args_list,
'Thee login progress bar should have the right values set')
self.assertEqual(2, mocked_process_events.call_count,
'The process_events() method should be called twice')
mocked_critical.assert_called_with(ssform, 'Error Logging In', 'There was a problem logging in, '
'perhaps your username or password is '
'incorrect?')

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 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 Words of Worship song importer.
"""
import os
from tests.helpers.songfileimport import SongImportTestHelper
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
TEST_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'wordsofworshipsongs'))
class TestWordsOfWorshipFileImport(SongImportTestHelper):
def __init__(self, *args, **kwargs):
self.importer_class_name = 'WordsOfWorshipImport'
self.importer_module_name = 'wordsofworship'
super(TestWordsOfWorshipFileImport, self).__init__(*args, **kwargs)
def test_song_import(self):
"""
Test that loading a Words of Worship file works correctly
"""
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')],
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))

View File

@ -54,3 +54,5 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))

View File

@ -62,6 +62,7 @@ class TestMainWindow(TestCase, TestMixin):
patch('openlp.core.ui.mainwindow.QtGui.QMainWindow.addDockWidget') as mocked_add_dock_method, \
patch('openlp.core.ui.mainwindow.ServiceManager') as mocked_service_manager, \
patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
patch('openlp.core.ui.mainwindow.ProjectorManager') as mocked_projector_manager, \
patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer:
self.main_window = MainWindow()
@ -85,3 +86,29 @@ class TestMainWindow(TestCase, TestMixin):
# THEN: The current widget should have been set.
self.main_window.media_tool_box.setCurrentIndex.assert_called_with(2)
def projector_manager_dock_locked_test(self):
"""
Projector Manager enable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = self.main_window.projector_manager_dock
# WHEN: main_window.lock_panel action is triggered
self.main_window.lock_panel.triggered.emit(True)
# THEN: Projector manager dock should have been called with disable UI features
projector_dock.setFeatures.assert_called_with(0)
def projector_manager_dock_unlocked_test(self):
"""
Projector Manager disable UI options - bug #1390702
"""
# GIVEN: A mocked projector manager dock item:
projector_dock = self.main_window.projector_manager_dock
# WHEN: main_window.lock_panel action is triggered
self.main_window.lock_panel.triggered.emit(False)
# THEN: Projector manager dock should have been called with enable UI features
projector_dock.setFeatures.assert_called_with(7)

View File

@ -105,3 +105,13 @@ class TestBibleManager(TestCase, TestMixin):
# THEN a verse array should be returned
self.assertEqual([(54, 1, 1, -1), (54, 2, 1, 1)], results,
"The bible verses should match the expected results")
def parse_reference_four_test(self):
"""
Test the parse_reference method with non existence book
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
# THEN a verse array should be returned
self.assertEqual(False, results, "The bible Search should return False")

View File

@ -0,0 +1,33 @@
{
"authors": [
"John Newton (1725-1807)"
],
"title": "Amazing Grace (6 Verses)",
"verse_order_list": [],
"verses": [
[
"Amazing grace! how sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see.",
"V"
],
[
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!",
"V"
],
[
"Through many dangers, toils and snares\nI have already come;\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
"V"
],
[
"The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures.",
"V"
],
[
"Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace.",
"V"
],
[
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise\nThan when we first begun.",
"V"
]
]
}

View File

@ -0,0 +1,29 @@
{
"authors": [
"Author Unknown. Tr. Edward Caswall"
],
"title": "When morning gilds the skies",
"verse_order_list": [],
"verses": [
[
"When morning gilds the skies\nMy heart awaking cries:\n'May Jesus Christ be prais'd!'\nAlike at work and prayer to Jesus I repair:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Does sadness fill my mind?\nA solace here I find:\n'May Jesus Christ be praised!'\nWhen evil thoughts molest,\nWith this I shield my breast:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"To God, the Word, on high\nThe hosts of angels cry:\n'May Jesus Christ be prais'd!'\nLet mortals, too, upraise\nTheir voice in hymns of praise:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Let earth's wide circle round\nIn joyful notes resound:\n'May Jesus Christ be prais'd!'\nLet air, and sea, and sky,\nFrom depth to height, reply:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Be this while life is mine\nMy canticle divine\n'May Jesus Christ be prais'd!'\nBe this the eternal song,\nThrough all the ages long:\n'May Jesus Christ be prais'd!'",
"V"
]
]
}

View File

@ -8,7 +8,7 @@
"copyright": "Public Domain",
"verses": [
[
"Would you be free from your burden of sin? \nThere's power in the blood, power in the blood \nWould you o'er evil a victory win? \nThere's wonderful power in the blood \n",
"Would you be free from your burden of sin? \nThere's power in the blood, power in the blood \nWould you o'er evil a victory win? \nThere's wonderful power in the blood \n ",
"v1"
],
[

View File

@ -0,0 +1,31 @@
SONGNR,TITLE,AUTHOR,COPYRIGHT,FIRSTLINE,PRIKEY,ALTKEY,TEMPO,FOCUS,THEME,SCRIPTURE,ACTIVE,SONGBOOK,TIMESIG,INTRODUCED,LASTUSED,TIMESUSED,CCLINR,USER1,USER2,USER3,USER4,USER5,ROADMAP,FILELINK1,OVERMAP,FILELINK2,LYRICS,INFO,LYRICS2,Background
"7","Would You Be Free","Jones, Lewis E.","Public Domain","Would you be free from your burden of sin?","G","","Moderate","Only To Others","","","N","Y","","1899-12-30","1899-12-30","","","","","","","","1, C, 1","","","",".G C G
Would you be free from your burden of sin?
. D D7 G
There's power in the blood, power in the blood
. C G
Would you o'er evil a victory win?
. D D7 G
There's wonderful power in the blood
.G C G
There is power, power, wonder working power
.D G
In the blood of the Lamb
. C G
There is power, power, wonder working power
. D G
In the precious blood of the Lamb
","","[1]
Would you be free from your burden of sin?
There's power in the blood, power in the blood
Would you o'er evil a victory win?
There's wonderful power in the blood
[C]
There is power, power, wonder working power
In the blood of the Lamb
There is power, power, wonder working power
In the precious blood of the Lamb
[x]",""
1 SONGNR TITLE AUTHOR COPYRIGHT FIRSTLINE PRIKEY ALTKEY TEMPO FOCUS THEME SCRIPTURE ACTIVE SONGBOOK TIMESIG INTRODUCED LASTUSED TIMESUSED CCLINR USER1 USER2 USER3 USER4 USER5 ROADMAP FILELINK1 OVERMAP FILELINK2 LYRICS INFO LYRICS2 Background
2 7 Would You Be Free Jones, Lewis E. Public Domain Would you be free from your burden of sin? G Moderate Only To Others N Y 1899-12-30 1899-12-30 1, C, 1 .G C G Would you be free from your burden of sin? . D D7 G There's power in the blood, power in the blood . C G Would you o'er evil a victory win? . D D7 G There's wonderful power in the blood .G C G There is power, power, wonder working power .D G In the blood of the Lamb . C G There is power, power, wonder working power . D G In the precious blood of the Lamb [1] Would you be free from your burden of sin? There's power in the blood, power in the blood Would you o'er evil a victory win? There's wonderful power in the blood [C] There is power, power, wonder working power In the blood of the Lamb There is power, power, wonder working power In the precious blood of the Lamb [x]