forked from openlp/openlp
Merge with trunk
This commit is contained in:
commit
e3bd817c5b
@ -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()
|
||||
|
@ -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
|
||||
|
89
openlp/core/lib/colorbutton.py
Normal file
89
openlp/core/lib/colorbutton.py
Normal 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())
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -28,4 +28,4 @@
|
||||
###############################################################################
|
||||
"""
|
||||
The Projector driver module.
|
||||
"""
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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 = []
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ''))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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' % \
|
||||
|
@ -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')
|
||||
|
206
tests/functional/openlp_core_lib/test_color_button.py
Normal file
206
tests/functional/openlp_core_lib/test_color_button.py
Normal 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')
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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'
|
||||
|
@ -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))))
|
@ -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?')
|
||||
|
@ -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')))
|
@ -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')))
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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"
|
||||
]
|
||||
]
|
||||
}
|
Binary file not shown.
@ -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"
|
||||
]
|
||||
]
|
||||
}
|
Binary file not shown.
@ -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"
|
||||
],
|
||||
[
|
||||
|
31
tests/resources/worshipassistantsongs/would_you_be_free2.csv
Normal file
31
tests/resources/worshipassistantsongs/would_you_be_free2.csv
Normal 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]",""
|
|
Loading…
Reference in New Issue
Block a user