From a4f788599f65cae086c0998fded11ae6c7e7a882 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 23 Oct 2014 20:50:01 +0100 Subject: [PATCH 01/42] add custom colorbutton widget --- openlp/core/lib/__init__.py | 1 + openlp/core/lib/colorbutton.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 openlp/core/lib/colorbutton.py diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index b85070445..7e4af3fc6 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -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 diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py new file mode 100644 index 000000000..00ef9dffe --- /dev/null +++ b/openlp/core/lib/colorbutton.py @@ -0,0 +1,33 @@ +from PyQt4 import QtCore, QtGui + +class ColorButton(QtGui.QPushButton): + """ + Subclasses QPushbutton to create a "Color Chooser" button + """ + + colorChanged = QtCore.pyqtSignal(str) + + def __init__(self, parent): + super(ColorButton, self).__init__() + self._color = '#ffffff' + self.parent = parent + self.clicked.connect(self.on_clicked) + + def change_color(self, color): + self._color = color + self.setStyleSheet('background-color: %s' % color) + + @property + def color(self): + return self._color + + @color.setter + def color(self, color): + self.change_color(color) + + def on_clicked(self): + 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()) + print("changed") + self.colorChanged.emit(new_color.name()) \ No newline at end of file From c9cede9650f08f10a375b74dafd28fee46516744 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Thu, 23 Oct 2014 21:56:09 +0100 Subject: [PATCH 02/42] Implemented custom widget ColorButton OpenLP wide --- openlp/core/ui/advancedtab.py | 15 ++--- openlp/core/ui/media/playertab.py | 15 ++--- openlp/core/ui/themeform.py | 82 +++++++++++--------------- openlp/core/ui/themewizard.py | 18 +++--- openlp/plugins/alerts/lib/alertstab.py | 32 ++++------ openlp/plugins/images/lib/imagetab.py | 15 ++--- 6 files changed, 73 insertions(+), 104 deletions(-) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index a5ce09bdf..b0d48a21e 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -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_default_color_button_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) @@ -395,7 +395,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 +498,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_default_color_button_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): """ diff --git a/openlp/core/ui/media/playertab.py b/openlp/core/ui/media/playertab.py index 09bac5125..40ae41dcc 100644 --- a/openlp/core/ui/media/playertab.py +++ b/openlp/core/ui/media/playertab.py @@ -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_button_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_button_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): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 46fd227dd..f2d05e3a0 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -65,18 +65,19 @@ 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_button_changed) + #self.color_button.clicked.connect(self.on_color_button_clicked) + self.image_color_button.colorChanged.connect(self.on_image_color_button_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_button_changed) + self.outline_color_button.colorChanged.connect(self.on_outline_color_button_changed) + self.shadow_color_button.colorChanged.connect(self.on_shadow_color_button_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_button_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 +296,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 +324,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 +341,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 +400,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_button_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_button_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 +442,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_button_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_button_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_button_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_button_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 +525,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 diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 50200313f..b21ea6e7e 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -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 @@ -80,7 +80,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) @@ -92,12 +92,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) @@ -115,7 +115,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) @@ -154,7 +154,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) @@ -190,7 +190,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) @@ -207,7 +207,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) @@ -233,7 +233,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) diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index 7ac5db672..dc7accaeb 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -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_button_changed) + self.font_color_button.colorChanged.connect(self.on_font_color_button_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_button_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_button_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) diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 975d06fdb..ceac8545d 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -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_button_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_button_changed(self, color): + self.background_color = new_color.name() 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() From 8696482f39fb07ae9cfee08b07c81dd0224fecaa Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 24 Oct 2014 21:46:14 +0100 Subject: [PATCH 03/42] Few tweeks, and comments --- openlp/core/lib/colorbutton.py | 29 ++++++++++++++++++++++++--- openlp/core/ui/advancedtab.py | 1 - openlp/plugins/images/lib/imagetab.py | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py index 00ef9dffe..69ccc0027 100644 --- a/openlp/core/lib/colorbutton.py +++ b/openlp/core/lib/colorbutton.py @@ -1,5 +1,7 @@ from PyQt4 import QtCore, QtGui +from openlp.core.common import translate + class ColorButton(QtGui.QPushButton): """ Subclasses QPushbutton to create a "Color Chooser" button @@ -8,26 +10,47 @@ class ColorButton(QtGui.QPushButton): colorChanged = QtCore.pyqtSignal(str) def __init__(self, parent): + """ + Initialise the ColorButton + """ super(ColorButton, self).__init__() - self._color = '#ffffff' self.parent = parent + self._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()) - print("changed") - self.colorChanged.emit(new_color.name()) \ No newline at end of file + self.colorChanged.emit(new_color.name()) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index b0d48a21e..800bd791d 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -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.')) diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index ceac8545d..5fbfd2502 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -73,7 +73,7 @@ class ImageTab(SettingsTab): translate('ImagesPlugin.ImageTab', 'Visible background for images with aspect ratio different to screen.')) def on_background_color_button_changed(self, color): - self.background_color = new_color.name() + self.background_color = color def load(self): settings = Settings() From 04c6e27ac5b567954313dfec735a925c77e8c429 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Fri, 24 Oct 2014 22:03:49 +0100 Subject: [PATCH 04/42] PEP Fixes --- openlp/core/lib/colorbutton.py | 1 + openlp/core/ui/themeform.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py index 69ccc0027..b89bf28e2 100644 --- a/openlp/core/lib/colorbutton.py +++ b/openlp/core/lib/colorbutton.py @@ -2,6 +2,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import translate + class ColorButton(QtGui.QPushButton): """ Subclasses QPushbutton to create a "Color Chooser" button diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index f2d05e3a0..c006166c0 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -66,7 +66,6 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): 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.colorChanged.connect(self.on_color_button_changed) - #self.color_button.clicked.connect(self.on_color_button_clicked) self.image_color_button.colorChanged.connect(self.on_image_color_button_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) From d2b6d2126e862b9fb3363bccad892decfd1ad07e Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 26 Oct 2014 07:50:49 +0000 Subject: [PATCH 05/42] Add copyright and doc stying --- openlp/core/lib/colorbutton.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py index b89bf28e2..117fd368d 100644 --- a/openlp/core/lib/colorbutton.py +++ b/openlp/core/lib/colorbutton.py @@ -1,3 +1,35 @@ +# -*- 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 From 013e59c5272538b6df921b4b5852fb93d276c06f Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Wed, 29 Oct 2014 19:07:13 +0000 Subject: [PATCH 06/42] fixes to FileDialog tests --- tests/functional/openlp_core_lib/test_file_dialog.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_file_dialog.py b/tests/functional/openlp_core_lib/test_file_dialog.py index 1190810da..ad4d852d7 100644 --- a/tests/functional/openlp_core_lib/test_file_dialog.py +++ b/tests/functional/openlp_core_lib/test_file_dialog.py @@ -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,6 @@ 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') From 8c0f480b0e59d86e62f162850bd5b2b29e510ee9 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Wed, 29 Oct 2014 19:18:01 +0000 Subject: [PATCH 07/42] Start of ColorButton tests --- .../openlp_core_lib/test_color_button.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/functional/openlp_core_lib/test_color_button.py diff --git a/tests/functional/openlp_core_lib/test_color_button.py b/tests/functional/openlp_core_lib/test_color_button.py new file mode 100644 index 000000000..d721cce74 --- /dev/null +++ b/tests/functional/openlp_core_lib/test_color_button.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-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.qt_gui_patcher = patch('openlp.core.lib.colorbutton.QtGui') + self.translate_patcher = patch('openlp.core.lib.colorbutton.translate') + #self.mocked_qt_gui = self.qt_gui_patcher.start() + self.mocked_translate = self.translate_patcher.start() + self.mocked_parent = MagicMock() + + def tearDown(self): + #self.qt_gui_patcher.stop() + self.translate_patcher.stop() + + def constructor_test(self): + """ + Test that constructing a basic ColorButton object works correctly + """ + with patch.object(ColorButton, 'setToolTip') as mock_set_tool_tip: + + # GIVEN: The ColorButton class, a mocked out QtGui and mocked out parent + self.mocked_translate.return_value = 'Tool Tip Text' + + # WHEN: An object is instantiated + #ColorButton.setToolTip = MagicMock() + #ColorButton.clicked = MagicMock() + widget = ColorButton(self.mocked_parent) + + + # THEN: The widget should have the correct properties + self.assertEqual(widget.parent, self.mocked_parent, + 'The parent should be the same as the one that the class was instianted with') + self.assertEqual(widget._color, '#ffffff', 'The default value for _color should be #ffffff') + b = mock_set_tool_tip.mock_calls + mock_set_tool_tip.assert_called_once_with('Tool Tip Text') + #d = widget.clicked.mock_calls + #widget.clicked.connect.called_once_with(widget.on_clicked) + #a = 1 + print(b) From b682117f924f43fd3523f26baf0126bed5418485 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 2 Nov 2014 16:50:18 +0000 Subject: [PATCH 08/42] Completed tests for ColorButton --- openlp/core/lib/colorbutton.py | 4 +- .../openlp_core_lib/test_color_button.py | 183 +++++++++++++++--- 2 files changed, 159 insertions(+), 28 deletions(-) diff --git a/openlp/core/lib/colorbutton.py b/openlp/core/lib/colorbutton.py index 117fd368d..6ebf9ee99 100644 --- a/openlp/core/lib/colorbutton.py +++ b/openlp/core/lib/colorbutton.py @@ -42,13 +42,13 @@ class ColorButton(QtGui.QPushButton): colorChanged = QtCore.pyqtSignal(str) - def __init__(self, parent): + def __init__(self, parent=None): """ Initialise the ColorButton """ super(ColorButton, self).__init__() self.parent = parent - self._color = '#ffffff' + self.change_color('#ffffff') self.setToolTip(translate('OpenLP.ColorButton', 'Click to select a color.')) self.clicked.connect(self.on_clicked) diff --git a/tests/functional/openlp_core_lib/test_color_button.py b/tests/functional/openlp_core_lib/test_color_button.py index d721cce74..297bde82f 100644 --- a/tests/functional/openlp_core_lib/test_color_button.py +++ b/tests/functional/openlp_core_lib/test_color_button.py @@ -40,38 +40,169 @@ class TestColorDialog(TestCase): Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class """ def setUp(self): - #self.qt_gui_patcher = patch('openlp.core.lib.colorbutton.QtGui') - self.translate_patcher = patch('openlp.core.lib.colorbutton.translate') - #self.mocked_qt_gui = self.qt_gui_patcher.start() + 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() - self.mocked_parent = MagicMock() - - def tearDown(self): - #self.qt_gui_patcher.stop() - self.translate_patcher.stop() def constructor_test(self): """ - Test that constructing a basic ColorButton object works correctly + Test that constructing a ColorButton object works correctly """ - with patch.object(ColorButton, 'setToolTip') as mock_set_tool_tip: - # GIVEN: The ColorButton class, a mocked out QtGui and mocked out parent - self.mocked_translate.return_value = 'Tool Tip Text' + # 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: An object is instantiated - #ColorButton.setToolTip = MagicMock() - #ColorButton.clicked = MagicMock() - widget = ColorButton(self.mocked_parent) + # WHEN: The ColorButton object is instantiated + widget = ColorButton() - - # THEN: The widget should have the correct properties - self.assertEqual(widget.parent, self.mocked_parent, + # 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.assertEqual(widget._color, '#ffffff', 'The default value for _color should be #ffffff') - b = mock_set_tool_tip.mock_calls - mock_set_tool_tip.assert_called_once_with('Tool Tip Text') - #d = widget.clicked.mock_calls - #widget.clicked.connect.called_once_with(widget.on_clicked) - #a = 1 - print(b) + 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') + + #self.init_patcher.stop() + + 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') From b464b5613c40a3d62f3fff8637a075ae2cb3aa07 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 2 Nov 2014 17:19:02 +0000 Subject: [PATCH 09/42] pep fixes --- tests/functional/openlp_core_lib/test_color_button.py | 10 ++++------ tests/functional/openlp_core_lib/test_file_dialog.py | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_color_button.py b/tests/functional/openlp_core_lib/test_color_button.py index 297bde82f..a7b743918 100644 --- a/tests/functional/openlp_core_lib/test_color_button.py +++ b/tests/functional/openlp_core_lib/test_color_button.py @@ -109,8 +109,6 @@ class TestColorDialog(TestCase): # THEN: The value set in _color should be returned self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set') - #self.init_patcher.stop() - def color_test(self): """ Test that the color property method returns the set color @@ -158,9 +156,9 @@ class TestColorDialog(TestCase): # 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.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, \ + 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): @@ -181,10 +179,10 @@ class TestColorDialog(TestCase): # 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, \ + 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, \ + 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): diff --git a/tests/functional/openlp_core_lib/test_file_dialog.py b/tests/functional/openlp_core_lib/test_file_dialog.py index ad4d852d7..6cfb356b6 100644 --- a/tests/functional/openlp_core_lib/test_file_dialog.py +++ b/tests/functional/openlp_core_lib/test_file_dialog.py @@ -70,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.assert_called_with(self.mocked_parent, 'File Not Found', + 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') From 9c6e007ed3b7ac920b7d942db51ba16e62e1b1b6 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 2 Nov 2014 19:32:40 +0000 Subject: [PATCH 10/42] Applied changes as superfly suggested --- openlp/core/ui/advancedtab.py | 4 ++-- openlp/core/ui/media/playertab.py | 4 ++-- openlp/plugins/alerts/lib/alertstab.py | 4 ++-- openlp/plugins/images/lib/imagetab.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 800bd791d..c4d966562 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -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.colorChanged.connect(self.on_default_color_button_changed) + 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) @@ -497,7 +497,7 @@ class AdvancedTab(SettingsTab): self.service_name_edit.setText(UiStrings().DefaultServiceName) self.service_name_edit.setFocus() - def on_default_color_button_changed(self, color): + def on_background_color_changed(self, color): """ Select the background colour of the default display screen. """ diff --git a/openlp/core/ui/media/playertab.py b/openlp/core/ui/media/playertab.py index 40ae41dcc..f9667b3de 100644 --- a/openlp/core/ui/media/playertab.py +++ b/openlp/core/ui/media/playertab.py @@ -124,7 +124,7 @@ class PlayerTab(SettingsTab): self.left_layout.addStretch() self.right_layout.addStretch() # Signals and slots - self.background_color_button.colorChanged.connect(self.on_background_color_button_changed) + self.background_color_button.colorChanged.connect(self.on_background_color_changed) def retranslateUi(self): """ @@ -138,7 +138,7 @@ class PlayerTab(SettingsTab): 'Visible background for videos with aspect ratio different to screen.')) self.retranslate_players() - def on_background_color_button_changed(self, color): + def on_background_color_changed(self, color): """ Set the background color """ diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index dc7accaeb..ec8b49578 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -95,7 +95,7 @@ class AlertsTab(SettingsTab): self.right_layout.addWidget(self.preview_group_box) self.right_layout.addStretch() # Signals and slots - self.background_color_button.colorChanged.connect(self.on_background_color_button_changed) + self.background_color_button.colorChanged.connect(self.on_background_color_changed) self.font_color_button.colorChanged.connect(self.on_font_color_button_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) @@ -113,7 +113,7 @@ class AlertsTab(SettingsTab): self.preview_group_box.setTitle(UiStrings().Preview) self.font_preview.setText(UiStrings().OLPV2x) - def on_background_color_button_changed(self, color): + def on_background_color_changed(self, color): """ The background color has been changed. """ diff --git a/openlp/plugins/images/lib/imagetab.py b/openlp/plugins/images/lib/imagetab.py index 5fbfd2502..27a48eddc 100644 --- a/openlp/plugins/images/lib/imagetab.py +++ b/openlp/plugins/images/lib/imagetab.py @@ -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.colorChanged.connect(self.on_background_color_button_changed) + self.background_color_button.colorChanged.connect(self.on_background_color_changed) def retranslateUi(self): self.background_color_group_box.setTitle(UiStrings().BackgroundColor) @@ -72,7 +72,7 @@ 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_changed(self, color): + def on_background_color_changed(self, color): self.background_color = color def load(self): From f160607e17e2e1c567144b6a77d90f00a39e95b8 Mon Sep 17 00:00:00 2001 From: Phill Ridout Date: Sun, 2 Nov 2014 19:57:06 +0000 Subject: [PATCH 11/42] Renamed some methods --- openlp/core/ui/themeform.py | 24 ++++++++++++------------ openlp/plugins/alerts/lib/alertstab.py | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index c006166c0..611ac2d4d 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -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.colorChanged.connect(self.on_color_button_changed) - self.image_color_button.colorChanged.connect(self.on_image_color_button_changed) + 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.colorChanged.connect(self.on_main_color_button_changed) - self.outline_color_button.colorChanged.connect(self.on_outline_color_button_changed) - self.shadow_color_button.colorChanged.connect(self.on_shadow_color_button_changed) + 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.colorChanged.connect(self.on_footer_color_button_changed) + 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) @@ -399,13 +399,13 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): self.theme.background_direction = BackgroundGradientType.to_string(index) self.set_background_page_values() - def on_color_button_changed(self, color): + def on_color_changed(self, color): """ Background / Gradient 1 _color button pushed. """ self.theme.background_color = color - def on_image_color_button_changed(self, color): + def on_image_color_changed(self, color): """ Background / Gradient 1 _color button pushed. """ @@ -441,25 +441,25 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): """ self.theme.background_filename = str(self.image_file_edit.text()) - def on_main_color_button_changed(self, color): + def on_main_color_changed(self, color): """ Set the main colour value """ self.theme.font_main_color = color - def on_outline_color_button_changed(self, color): + def on_outline_color_changed(self, color): """ Set the outline colour value """ self.theme.font_main_outline_color = color - def on_shadow_color_button_changed(self, color): + def on_shadow_color_changed(self, color): """ Set the shadow colour value """ self.theme.font_main_shadow_color = color - def on_footer_color_button_changed(self, color): + def on_footer_color_changed(self, color): """ Set the footer colour value """ diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index ec8b49578..b92062f82 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -96,7 +96,7 @@ class AlertsTab(SettingsTab): self.right_layout.addStretch() # Signals and slots self.background_color_button.colorChanged.connect(self.on_background_color_changed) - self.font_color_button.colorChanged.connect(self.on_font_color_button_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) @@ -126,7 +126,7 @@ class AlertsTab(SettingsTab): """ self.update_display() - def on_font_color_button_changed(self, color): + def on_font_color_changed(self, color): """ The Font Color button has clicked. """ From 2ed5f2bbe48c2d36b644ed6baf42b699e10c5b9f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 3 Nov 2014 11:03:53 +0100 Subject: [PATCH 12/42] Fix for import of Words of Worship file --- .../plugins/songs/lib/importers/wordsofworship.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/songs/lib/importers/wordsofworship.py b/openlp/plugins/songs/lib/importers/wordsofworship.py index 1b398c604..8fcbc9f84 100644 --- a/openlp/plugins/songs/lib/importers/wordsofworship.py +++ b/openlp/plugins/songs/lib/importers/wordsofworship.py @@ -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 From 51f4539822b837464cfb62f69d07600cb5d2eaf7 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 3 Nov 2014 15:36:27 +0100 Subject: [PATCH 13/42] Added test for Word of Worship import --- .../songs/lib/importers/wordsofworship.py | 2 +- .../songs/test_wordsofworshipimport.py | 54 ++++++++++++++++++ .../Amazing Grace (6 Verses).json | 33 +++++++++++ .../Amazing Grace (6 Verses).wow-song | Bin 0 -> 965 bytes 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py create mode 100644 tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).json create mode 100644 tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).wow-song diff --git a/openlp/plugins/songs/lib/importers/wordsofworship.py b/openlp/plugins/songs/lib/importers/wordsofworship.py index 8fcbc9f84..29b384085 100644 --- a/openlp/plugins/songs/lib/importers/wordsofworship.py +++ b/openlp/plugins/songs/lib/importers/wordsofworship.py @@ -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): """ diff --git a/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py b/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py new file mode 100644 index 000000000..952e16971 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py @@ -0,0 +1,54 @@ +# -*- 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'))) diff --git a/tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).json b/tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).json new file mode 100644 index 000000000..563872ae7 --- /dev/null +++ b/tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).json @@ -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" + ] + ] +} diff --git a/tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).wow-song b/tests/resources/wordsofworshipsongs/Amazing Grace (6 Verses).wow-song new file mode 100644 index 0000000000000000000000000000000000000000..e45d22e2ffa3dcfafa966ae5984e40bc78698536 GIT binary patch literal 965 zcmZ9L&1)1v5XC$3Q&8L=JeSI-uWSMNpneM-q|+BY+>nB*JCXGJsKY70iOEq_IBGm zF_(JtlN4uXmlr{b>xYOOp4axbhu|{Vf)B_^N4aBesBuhL+E|+XnX?ULd&4DJ>Jm4F zqu{TZYMz+GDM>7tI-4m-ZpO4|8;a1eHB~AwHoJ2K7r|qHADvQWe%FI;{ZW*o(M&aZ z5M)Qn#P`xI{HsQZeh0WOPpU3j$*~RC8d_&Uv*;%%6fO_^-T%_;ZHE{Ix@#%+? Nx5ppfpC0ue<6q+@B>eyY literal 0 HcmV?d00001 From 88e4c2e0b9f872e4e2cf0462ca0697503b670c3a Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 3 Nov 2014 20:30:43 +0000 Subject: [PATCH 14/42] Try to fix 0 track length by waiting. Fixes bug 1387293 Fixes: https://launchpad.net/bugs/1387293 --- openlp/plugins/media/forms/mediaclipselectorform.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index 86727d336..416e2504e 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -446,6 +446,12 @@ 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() + 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) From af6a5322e22bc81031736c6d9a6b659b72d5969c Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 4 Nov 2014 12:58:03 +0100 Subject: [PATCH 15/42] Make it possible to break out of waiting loop --- openlp/plugins/media/forms/mediaclipselectorform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index 416e2504e..2cdbbc6ef 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -451,6 +451,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryPropert 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 From 77c7da2d20ff569d8a7c90eabdb554daf0a609db Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 5 Nov 2014 09:42:33 +0100 Subject: [PATCH 16/42] Change duplicate check to pass int-string tuples to workers, to workaround windows issue, see bug #1388850 Fixes: https://launchpad.net/bugs/1388850 --- .../songs/forms/duplicatesongremovalform.py | 14 ++++---- openlp/plugins/songs/lib/songcompare.py | 16 +++++---- .../openlp_plugins/songs/test_lib.py | 34 +++++++++---------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index c411c8c1c..4bcce1c44 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -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) diff --git a/openlp/plugins/songs/lib/songcompare.py b/openlp/plugins/songs/lib/songcompare.py index 8a6cc7130..ddd5e4552 100644 --- a/openlp/plugins/songs/lib/songcompare.py +++ b/openlp/plugins/songs/lib/songcompare.py @@ -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 diff --git a/tests/functional/openlp_plugins/songs/test_lib.py b/tests/functional/openlp_plugins/songs/test_lib.py index 140126f26..3b6f9bf87 100644 --- a/tests/functional/openlp_plugins/songs/test_lib.py +++ b/tests/functional/openlp_plugins/songs/test_lib.py @@ -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' From 02a159bf654073a57078240b4c730e4aff06bd21 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 5 Nov 2014 14:04:43 +0100 Subject: [PATCH 17/42] Added one more test file for WoW import --- .../songs/test_wordsofworshipimport.py | 2 ++ .../When morning gilds the skies.json | 29 ++++++++++++++++++ .../When morning gilds the skies.wsg | Bin 0 -> 999 bytes 3 files changed, 31 insertions(+) create mode 100644 tests/resources/wordsofworshipsongs/When morning gilds the skies.json create mode 100644 tests/resources/wordsofworshipsongs/When morning gilds the skies.wsg diff --git a/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py b/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py index 952e16971..853868e91 100644 --- a/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py +++ b/tests/functional/openlp_plugins/songs/test_wordsofworshipimport.py @@ -52,3 +52,5 @@ class TestWordsOfWorshipFileImport(SongImportTestHelper): """ 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'))) diff --git a/tests/resources/wordsofworshipsongs/When morning gilds the skies.json b/tests/resources/wordsofworshipsongs/When morning gilds the skies.json new file mode 100644 index 000000000..c7a4426dd --- /dev/null +++ b/tests/resources/wordsofworshipsongs/When morning gilds the skies.json @@ -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" + ] + ] +} diff --git a/tests/resources/wordsofworshipsongs/When morning gilds the skies.wsg b/tests/resources/wordsofworshipsongs/When morning gilds the skies.wsg new file mode 100644 index 0000000000000000000000000000000000000000..901a2dc4f30d8f8735d2356cd245a58f241f6a7b GIT binary patch literal 999 zcmaiz!EW0y42IJI+o7jnz_7+>R}MSmFryEU%aEq)kYYV8L(ZzjSe#`s5ak%Y?aezX z?U2)I9}Gd1ev;o$ZFmE>nP3OWs2}Kuf`X zeZK%!7T4Zr(9QWk4U+!v`L@{`jR9{kgKz)|ha)=Jp1XiDBHpBlX=+F?w4`U6w;Dj6>-%UnWtppKWS zND=l-N`W-X(1wHfQ+Q1HSoB3sku7&Kfu1ZvMX~Y-k-K2eRsnwVu&(;YWEK%Py|*$*|t(69G?K#h`{#z;`;t zl?jP*7nb(AH8Q``e`5I|V8R%T*9i-CS*)FMs2vy`zZ^BSLhu?IBaln|yy$e)BK!(X W;67Xdtm74Yv%QHHmPUG2o%{tDWjM$H literal 0 HcmV?d00001 From 992ac3bbb8e3d0830cc5c94c4eeaf06e07b553d5 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 5 Nov 2014 21:44:45 +0000 Subject: [PATCH 18/42] Second attempt to fix duplicate-song-detection on windows --- .../songs/forms/duplicatesongremovalform.py | 20 ++++++++++--------- openlp/plugins/songs/lib/songcompare.py | 11 +++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index 4bcce1c44..16867ea0b 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -33,6 +33,7 @@ The duplicate song removal logic for OpenLP. import logging import multiprocessing import os +import functools from PyQt4 import QtCore, QtGui @@ -46,17 +47,16 @@ from openlp.plugins.songs.lib.songcompare import songs_probably_equal log = logging.getLogger(__name__) -def song_generator(songs): +def tuple_generator(number_of_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. + This is a generator function to return tuples of two songs position. When completed then all songs position have + once been returned combined with any other songs position. - :param songs: All songs in the database. + :param number_of_songs: Number of songs in the DB. """ - for outer_song_counter in range(len(songs) - 1): - for inner_song_counter in range(outer_song_counter + 1, len(songs)): - yield ((outer_song_counter, songs[outer_song_counter].search_lyrics), - (inner_song_counter, songs[inner_song_counter].search_lyrics)) + for outer_song_counter in range(number_of_songs - 1): + for inner_song_counter in range(outer_song_counter + 1, number_of_songs): + yield (outer_song_counter, inner_song_counter) class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): @@ -184,7 +184,9 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): # Create a worker/process pool to check the songs. process_number = max(1, multiprocessing.cpu_count() - 1) pool = multiprocessing.Pool(process_number) - result = pool.imap_unordered(songs_probably_equal, song_generator(songs), 30) + # Create array with all lyrics + song_lyrics = [song.search_lyrics for song in songs] + result = pool.imap_unordered(functools.partial(songs_probably_equal, song_lyrics), tuple_generator(len(songs)), 30) # 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. diff --git a/openlp/plugins/songs/lib/songcompare.py b/openlp/plugins/songs/lib/songcompare.py index ddd5e4552..9101245f5 100644 --- a/openlp/plugins/songs/lib/songcompare.py +++ b/openlp/plugins/songs/lib/songcompare.py @@ -52,15 +52,14 @@ MIN_BLOCK_SIZE = 70 MAX_TYPO_SIZE = 3 -def songs_probably_equal(song_tupel): +def songs_probably_equal(songs, pos_tupel): """ Calculate and return whether two songs are probably equal. :param song_tupel: A tuple of two songs to compare. """ - song1, song2 = song_tupel - pos1, lyrics1 = song1 - pos2, lyrics2 = song2 + lyrics1 = songs[pos_tupel[0]] + lyrics2 = songs[pos_tupel[1]] if len(lyrics1) < len(lyrics2): small = lyrics1 large = lyrics2 @@ -79,7 +78,7 @@ def songs_probably_equal(song_tupel): length_of_equal_blocks += _op_length(element) if length_of_equal_blocks >= MIN_BLOCK_SIZE: - return pos1, pos2 + return pos_tupel[0], pos_tupel[1] # 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 @@ -87,7 +86,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 pos1, pos2 + return pos_tupel[0], pos_tupel[1] # Both checks failed. We assume the songs are not equal. return None From b6e0036383852404042a0f3595fbc525ce966d1f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 6 Nov 2014 10:34:07 +0100 Subject: [PATCH 19/42] Re-re-fix duplicate song check on windows, and now it actually works in builds. --- openlp.py | 9 ++++++++- .../songs/forms/duplicatesongremovalform.py | 20 +++++++++---------- openlp/plugins/songs/lib/songcompare.py | 11 +++++----- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/openlp.py b/openlp.py index 5d507606d..bbd7f1922 100755 --- a/openlp.py +++ b/openlp.py @@ -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,14 @@ 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 sys.platform.startswith('darwin'): + if is_macosx(): sys.argv = [x for x in sys.argv if not x.startswith('-psn')] main() diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index 16867ea0b..4bcce1c44 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -33,7 +33,6 @@ The duplicate song removal logic for OpenLP. import logging import multiprocessing import os -import functools from PyQt4 import QtCore, QtGui @@ -47,16 +46,17 @@ from openlp.plugins.songs.lib.songcompare import songs_probably_equal log = logging.getLogger(__name__) -def tuple_generator(number_of_songs): +def song_generator(songs): """ - This is a generator function to return tuples of two songs position. When completed then all songs position have - once been returned combined with any other songs position. + 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 number_of_songs: Number of songs in the DB. + :param songs: All songs in the database. """ - for outer_song_counter in range(number_of_songs - 1): - for inner_song_counter in range(outer_song_counter + 1, number_of_songs): - yield (outer_song_counter, inner_song_counter) + for outer_song_counter in range(len(songs) - 1): + for inner_song_counter in range(outer_song_counter + 1, len(songs)): + yield ((outer_song_counter, songs[outer_song_counter].search_lyrics), + (inner_song_counter, songs[inner_song_counter].search_lyrics)) class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): @@ -184,9 +184,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): # Create a worker/process pool to check the songs. process_number = max(1, multiprocessing.cpu_count() - 1) pool = multiprocessing.Pool(process_number) - # Create array with all lyrics - song_lyrics = [song.search_lyrics for song in songs] - result = pool.imap_unordered(functools.partial(songs_probably_equal, song_lyrics), tuple_generator(len(songs)), 30) + result = pool.imap_unordered(songs_probably_equal, song_generator(songs), 30) # 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. diff --git a/openlp/plugins/songs/lib/songcompare.py b/openlp/plugins/songs/lib/songcompare.py index 9101245f5..ddd5e4552 100644 --- a/openlp/plugins/songs/lib/songcompare.py +++ b/openlp/plugins/songs/lib/songcompare.py @@ -52,14 +52,15 @@ MIN_BLOCK_SIZE = 70 MAX_TYPO_SIZE = 3 -def songs_probably_equal(songs, pos_tupel): +def songs_probably_equal(song_tupel): """ Calculate and return whether two songs are probably equal. :param song_tupel: A tuple of two songs to compare. """ - lyrics1 = songs[pos_tupel[0]] - lyrics2 = songs[pos_tupel[1]] + song1, song2 = song_tupel + pos1, lyrics1 = song1 + pos2, lyrics2 = song2 if len(lyrics1) < len(lyrics2): small = lyrics1 large = lyrics2 @@ -78,7 +79,7 @@ def songs_probably_equal(songs, pos_tupel): length_of_equal_blocks += _op_length(element) if length_of_equal_blocks >= MIN_BLOCK_SIZE: - return pos_tupel[0], pos_tupel[1] + 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 @@ -86,7 +87,7 @@ def songs_probably_equal(songs, pos_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 pos_tupel[0], pos_tupel[1] + return pos1, pos2 # Both checks failed. We assume the songs are not equal. return None From ff4ddefa779e23cb7aefc17bf0ccd5afd1575d09 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 6 Nov 2014 10:42:54 +0100 Subject: [PATCH 20/42] pep8 fix --- openlp.py | 1 - openlp/core/ui/projector/__init__.py | 2 +- tests/functional/openlp_core_ui/test_settingsform.py | 2 +- tests/functional/openlp_plugins/images/test_imagetab.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openlp.py b/openlp.py index bbd7f1922..e383cd86a 100755 --- a/openlp.py +++ b/openlp.py @@ -45,7 +45,6 @@ if __name__ == '__main__': # 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() diff --git a/openlp/core/ui/projector/__init__.py b/openlp/core/ui/projector/__init__.py index 9b51f110b..9a09145ac 100644 --- a/openlp/core/ui/projector/__init__.py +++ b/openlp/core/ui/projector/__init__.py @@ -28,4 +28,4 @@ ############################################################################### """ The Projector driver module. -""" \ No newline at end of file +""" diff --git a/tests/functional/openlp_core_ui/test_settingsform.py b/tests/functional/openlp_core_ui/test_settingsform.py index 17a8b165f..7ea2339a7 100644 --- a/tests/functional/openlp_core_ui/test_settingsform.py +++ b/tests/functional/openlp_core_ui/test_settingsform.py @@ -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') \ No newline at end of file + self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called') diff --git a/tests/functional/openlp_plugins/images/test_imagetab.py b/tests/functional/openlp_plugins/images/test_imagetab.py index fade284a4..a4614e816 100644 --- a/tests/functional/openlp_plugins/images/test_imagetab.py +++ b/tests/functional/openlp_plugins/images/test_imagetab.py @@ -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') \ No newline at end of file + 'Image Post processing should have been requested') From a45befd8e5cd66c8fa15f89eadddf5f8c8a71297 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Thu, 6 Nov 2014 10:33:30 -0800 Subject: [PATCH 21/42] Update resources.py with projector icons From 0750071fe2afba05ab0ad45ccd95a60b3416c86a Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Sat, 8 Nov 2014 11:22:22 -0800 Subject: [PATCH 22/42] Fix spacing on View-Projector Manager --- openlp/core/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 825a12889..8a2ef2a8e 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -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')) From b6f87adb1d1c707841872de0924769fa407e776f Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Sat, 8 Nov 2014 16:39:55 -0800 Subject: [PATCH 23/42] Fix bug 1390702 --- openlp/core/ui/mainwindow.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 8a2ef2a8e..33e1f6bc7 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -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): @@ -1000,21 +1000,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. """ @@ -1024,6 +1024,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) @@ -1186,18 +1187,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) From e9ec5d202cac9f1d4b00535d5f48b0274050534e Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 9 Nov 2014 02:41:30 +0200 Subject: [PATCH 24/42] [bug 1306950] Set the username, password and "save" checkbox back to enabled when the login fails [refactor] Make the SongSelectForm slightly more testable [test] Write some tests for the form itself --- openlp/plugins/songs/forms/songselectform.py | 13 ++- openlp/plugins/songs/songsplugin.py | 1 + .../openlp_plugins/songs/test_songselect.py | 89 ++++++++++++++++++- 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/forms/songselectform.py b/openlp/plugins/songs/forms/songselectform.py index aecdf9682..0a8280f52 100755 --- a/openlp/plugins/songs/forms/songselectform.py +++ b/openlp/plugins/songs/forms/songselectform.py @@ -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() diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 56834a6eb..b7daebaab 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -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) diff --git a/tests/functional/openlp_plugins/songs/test_songselect.py b/tests/functional/openlp_plugins/songs/test_songselect.py index 0f9001342..dd3a07b0c 100644 --- a/tests/functional/openlp_plugins/songs/test_songselect.py +++ b/tests/functional/openlp_plugins/songs/test_songselect.py @@ -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?') From 84f104b7f83fc2f8580bbce967a77af42e889b2a Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Sat, 8 Nov 2014 18:54:08 -0800 Subject: [PATCH 25/42] Test for bug 1390702 --- .../openlp_core_ui/test_mainwindow.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/interfaces/openlp_core_ui/test_mainwindow.py b/tests/interfaces/openlp_core_ui/test_mainwindow.py index c775be747..c5d170fff 100644 --- a/tests/interfaces/openlp_core_ui/test_mainwindow.py +++ b/tests/interfaces/openlp_core_ui/test_mainwindow.py @@ -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) From bf382863d2c1cf73ae434606ce4cc39beaa6f3f1 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 10 Nov 2014 09:48:22 +0100 Subject: [PATCH 26/42] Fix for bug1390987 Fixes: https://launchpad.net/bugs/1390987 --- openlp/core/ui/servicemanager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 48d4de34a..3c53cf035 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -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() From 0a500eeb8dbb012777e0b2cbe5e36b35eaa96dfd Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 10 Nov 2014 10:25:31 +0100 Subject: [PATCH 27/42] Fix for WA import with escaped characters, partial fix for bug 1390986 Fixes: https://launchpad.net/bugs/1390986 --- openlp/plugins/songs/lib/importers/worshipassistant.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/songs/lib/importers/worshipassistant.py b/openlp/plugins/songs/lib/importers/worshipassistant.py index 6ddc71159..6af038c95 100644 --- a/openlp/plugins/songs/lib/importers/worshipassistant.py +++ b/openlp/plugins/songs/lib/importers/worshipassistant.py @@ -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: @@ -119,7 +119,6 @@ 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: From 6b538995f4c668832aca567141ce72cf6a7c90d0 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 10 Nov 2014 11:24:44 +0100 Subject: [PATCH 28/42] Fix for theme copy/creation on windows, fixes bug 1390917 Fixes: https://launchpad.net/bugs/1390917 --- openlp/core/ui/thememanager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 03890a028..89e269f24 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -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') From 61e42a9782ff852f17bb04f247a1ea5d480e2eda Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 10 Nov 2014 11:51:03 +0100 Subject: [PATCH 29/42] When querying values from Setting, return None if key does not exists. Fixes bug 1387278 Fixes: https://launchpad.net/bugs/1387278 --- openlp/core/common/settings.py | 12 ++++++++---- tests/functional/openlp_core_common/test_settings.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index abbf09ec3..624179826 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -431,10 +431,14 @@ class Settings(QtCore.QSettings): :param key: The key to return the value from. """ # if group() is not empty the group has not been specified together with the key. - if self.group(): - default_value = Settings.__default_settings__[self.group() + '/' + key] - else: - default_value = Settings.__default_settings__[key] + try: + if self.group(): + default_value = Settings.__default_settings__[self.group() + '/' + key] + else: + default_value = Settings.__default_settings__[key] + except KeyError: + log.warning('Key "%s" was not found in settings, returning None!' % key) + return None setting = super(Settings, self).value(key, default_value) return self._convert_value(setting, default_value) diff --git a/tests/functional/openlp_core_common/test_settings.py b/tests/functional/openlp_core_common/test_settings.py index ea4bcf849..bcd901a81 100644 --- a/tests/functional/openlp_core_common/test_settings.py +++ b/tests/functional/openlp_core_common/test_settings.py @@ -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 + + # WHEN reading a setting that doesn't exists + does_not_exist_value = Settings().value('core/does not exists') + + # THEN None should be returned + self.assertEqual(does_not_exist_value, None, 'The value should be None') From 9b517cd5693b3ba81372c2de769ec7adc37d4ac1 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 11 Nov 2014 11:38:24 +0100 Subject: [PATCH 30/42] Improved support for verseorder in WA import --- .../songs/lib/importers/worshipassistant.py | 41 ++++++++++++++----- .../songs/test_worshipassistantimport.py | 2 + .../would_you_be_free.json | 2 +- .../would_you_be_free2.csv | 31 ++++++++++++++ 4 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 tests/resources/worshipassistantsongs/would_you_be_free2.csv diff --git a/openlp/plugins/songs/lib/importers/worshipassistant.py b/openlp/plugins/songs/lib/importers/worshipassistant.py index 6af038c95..965e7d857 100644 --- a/openlp/plugins/songs/lib/importers/worshipassistant.py +++ b/openlp/plugins/songs/lib/importers/worshipassistant.py @@ -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 @@ -124,7 +126,7 @@ class WorshipAssistantImport(SongImport): 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), @@ -135,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() @@ -151,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 '')) diff --git a/tests/functional/openlp_plugins/songs/test_worshipassistantimport.py b/tests/functional/openlp_plugins/songs/test_worshipassistantimport.py index 63ead5b30..346f9c83d 100644 --- a/tests/functional/openlp_plugins/songs/test_worshipassistantimport.py +++ b/tests/functional/openlp_plugins/songs/test_worshipassistantimport.py @@ -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'))) diff --git a/tests/resources/worshipassistantsongs/would_you_be_free.json b/tests/resources/worshipassistantsongs/would_you_be_free.json index 96bc06a59..2c0a39973 100644 --- a/tests/resources/worshipassistantsongs/would_you_be_free.json +++ b/tests/resources/worshipassistantsongs/would_you_be_free.json @@ -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" ], [ diff --git a/tests/resources/worshipassistantsongs/would_you_be_free2.csv b/tests/resources/worshipassistantsongs/would_you_be_free2.csv new file mode 100644 index 000000000..ca4da9998 --- /dev/null +++ b/tests/resources/worshipassistantsongs/would_you_be_free2.csv @@ -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]","" From 2efdc97311e5c6df57aa8443389efddcfea6f6f3 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 11 Nov 2014 12:34:47 +0100 Subject: [PATCH 31/42] Fix traceback when saving service while in debug mode --- openlp/core/ui/servicemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 3c53cf035..e8b201cf6 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -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. From f609be65bceebdab81dcf95d90151ac199b2e971 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 12 Nov 2014 21:20:57 +0000 Subject: [PATCH 32/42] Make the settingswindow higher to make room for the new settings. Fixes: https://launchpad.net/bugs/1377283 --- openlp/core/ui/settingsdialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index ab5931584..61ee10e14 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -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) From b7e3a33c4ac33fc815be7839588ac6657184aaa5 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 12 Nov 2014 22:18:57 +0000 Subject: [PATCH 33/42] Mark a song edited from preview as coming from plugin. Fixes bug 1382672 Fixes: https://launchpad.net/bugs/1382672 --- openlp/plugins/songs/lib/mediaitem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 33c4f2e53..18d964115 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -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 From 28599c8f245a7dcd104bdde3ade95e66452f4e12 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 13 Nov 2014 11:26:44 +0100 Subject: [PATCH 34/42] Make sure that the slidecontroller toolbar layout is correctly adjusted to fit its size. Fixes bug 1387304 Fixes: https://launchpad.net/bugs/1387304 --- openlp/core/ui/slidecontroller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index d12506452..dfc7db2d8 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -691,7 +691,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() From ed13dcbd75b87bbe333873a4ba306e923a4c16b7 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 13 Nov 2014 12:21:54 +0100 Subject: [PATCH 35/42] Only show slide-dropdown on live-slidecontroller when it is populated. Fixes bug 1390238. Fixes: https://launchpad.net/bugs/1390238 --- openlp/core/ui/slidecontroller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index dfc7db2d8..85fe240d4 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -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) From 378132d82f299c72a8cc02ae300d727d055d0960 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 13 Nov 2014 20:15:43 +0000 Subject: [PATCH 36/42] Moved Settings KeyError handling to the export. --- openlp/core/common/settings.py | 12 ++++-------- openlp/core/ui/mainwindow.py | 9 ++++++++- tests/functional/openlp_core_common/test_settings.py | 10 +++++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 624179826..abbf09ec3 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -431,14 +431,10 @@ class Settings(QtCore.QSettings): :param key: The key to return the value from. """ # if group() is not empty the group has not been specified together with the key. - try: - if self.group(): - default_value = Settings.__default_settings__[self.group() + '/' + key] - else: - default_value = Settings.__default_settings__[key] - except KeyError: - log.warning('Key "%s" was not found in settings, returning None!' % key) - return None + if self.group(): + default_value = Settings.__default_settings__[self.group() + '/' + key] + else: + default_value = Settings.__default_settings__[key] setting = super(Settings, self).value(key, default_value) return self._convert_value(setting, default_value) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 33e1f6bc7..35d05620a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -978,7 +978,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() diff --git a/tests/functional/openlp_core_common/test_settings.py b/tests/functional/openlp_core_common/test_settings.py index bcd901a81..e3ce6990d 100644 --- a/tests/functional/openlp_core_common/test_settings.py +++ b/tests/functional/openlp_core_common/test_settings.py @@ -121,9 +121,9 @@ class TestSettings(TestCase, TestMixin): 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') - # WHEN reading a setting that doesn't exists - does_not_exist_value = Settings().value('core/does not exists') - - # THEN None should be returned - self.assertEqual(does_not_exist_value, None, 'The value should be None') + # 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') From 571b50f016e4bb7df450ec89cd51fc1618461b13 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 15 Nov 2014 22:38:36 +0000 Subject: [PATCH 37/42] Fix for overwriting files on song export. Fixes bug 1216232 Fixes: https://launchpad.net/bugs/1216232 --- openlp/plugins/songs/lib/openlyricsexport.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 0458b893b..84647dbee 100644 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -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 From c85f49a16c8a1d95b849c1135c3d36f9a6647b54 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 16 Nov 2014 22:44:44 +0000 Subject: [PATCH 38/42] Added test for openlyrics export of songs with same title and author --- .../songs/test_openlyricsexport.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/functional/openlp_plugins/songs/test_openlyricsexport.py diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py new file mode 100644 index 000000000..df28db035 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py @@ -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 = '\n' + 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)))) From 4fb0e43c041dcbeb67f6440e66d888a325ba3f00 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 20 Nov 2014 06:58:33 +0000 Subject: [PATCH 39/42] New start old bugs --- openlp/core/lib/renderer.py | 9 +++- openlp/core/ui/firsttimeform.py | 11 ++++ .../songusage/forms/songusagedetailform.py | 2 +- .../openlp_core_lib/test_renderer.py | 54 ++++++++++++++++++- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index ab4a5a4df..3f7ffc80f 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -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) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 02d9e65f7..13d7ab0cf 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -95,6 +95,17 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ self.application.process_events() if self.currentId() == FirstTimePage.Plugins: + 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()) if not self.web_access: return FirstTimePage.NoInternet else: diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py index e8111c058..5d8470a99 100644 --- a/openlp/plugins/songusage/forms/songusagedetailform.py +++ b/openlp/plugins/songusage/forms/songusagedetailform.py @@ -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' % \ diff --git a/tests/functional/openlp_core_lib/test_renderer.py b/tests/functional/openlp_core_lib/test_renderer.py index 8df4816bb..3a3eaf4cb 100644 --- a/tests/functional/openlp_core_lib/test_renderer.py +++ b/tests/functional/openlp_core_lib/test_renderer.py @@ -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 @@ -109,3 +109,55 @@ class TestRenderer(TestCase): # 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
[---]
b'] + service_item = ServiceItem(None) + + # WHEN: Split the line based on 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 = ['
[---]'] + service_item = ServiceItem(None) + + # WHEN: Split the line + 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 = ['
[---] '] + service_item = ServiceItem(None) + + # WHEN: Split the line + result_words = renderer.format_slide(given_line, service_item) + + # THEN: The blanks have been removed. + self.assertListEqual(result_words, expected_words) \ No newline at end of file From 22a4974d86d353990897ba78f71d320e6413c1ad Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 22 Nov 2014 07:07:08 +0000 Subject: [PATCH 40/42] Comments --- tests/functional/openlp_core_lib/test_renderer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_renderer.py b/tests/functional/openlp_core_lib/test_renderer.py index 3a3eaf4cb..d1a507373 100644 --- a/tests/functional/openlp_core_lib/test_renderer.py +++ b/tests/functional/openlp_core_lib/test_renderer.py @@ -88,7 +88,7 @@ class TestRenderer(TestCase): expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}', '') - # 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,7 +104,7 @@ 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. @@ -121,7 +121,7 @@ class TestRenderer(TestCase): expected_words = ['a
[---]
b'] service_item = ServiceItem(None) - # WHEN: Split the line based on rules + # WHEN: Split the line based on word split rules result_words = renderer.format_slide(given_line, service_item) @@ -139,7 +139,7 @@ class TestRenderer(TestCase): expected_words = ['
[---]'] service_item = ServiceItem(None) - # WHEN: Split the line + # WHEN: Split the line based on word split rules result_words = renderer.format_slide(given_line, service_item) # THEN: The blanks have been removed. @@ -156,8 +156,8 @@ class TestRenderer(TestCase): expected_words = ['
[---] '] service_item = ServiceItem(None) - # WHEN: Split the line + # 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) \ No newline at end of file + self.assertListEqual(result_words, expected_words) From f4ff654a654d80a12e1ed1460efcafb9a942d409 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 22 Nov 2014 20:26:59 +0000 Subject: [PATCH 41/42] If invlaid bible name stop searching Fixes: https://launchpad.net/bugs/1290246 --- openlp/plugins/bibles/lib/__init__.py | 5 ++++- .../openlp_plugins/bibles/test_lib_parse_reference.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index d67319797..fb10e6740 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -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 = [] diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py index e20105ea1..989d62583 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py @@ -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") From 21db63b03216b062fe67da6268d9a9e77f965b5e Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Tue, 25 Nov 2014 21:36:52 +0000 Subject: [PATCH 42/42] remove blank line --- tests/functional/openlp_core_lib/test_renderer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/openlp_core_lib/test_renderer.py b/tests/functional/openlp_core_lib/test_renderer.py index d1a507373..5e3d087b9 100644 --- a/tests/functional/openlp_core_lib/test_renderer.py +++ b/tests/functional/openlp_core_lib/test_renderer.py @@ -122,7 +122,6 @@ class TestRenderer(TestCase): 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.