From 435a299b19202f9f100445257b3a95a21aca6910 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 6 Nov 2014 23:08:56 +0200 Subject: [PATCH 01/14] First (failed) stab at using threads correctly. --- openlp/core/ui/firsttimeform.py | 59 ++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 02d9e65f7..420131dbc 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -50,30 +50,48 @@ from .firsttimewizard import UiFirstTimeWizard, FirstTimePage log = logging.getLogger(__name__) -class ThemeScreenshotThread(QtCore.QThread): +class ThemeScreenshotWorker(QtCore.QObject): """ This thread downloads the theme screenshots. """ + screenshot_downloaded = QtCore.pyqtSignal(str, str) + + def __init__(self, config, themes_url): + """ + Set up the worker object + """ + self.config = config + self.themes_url = themes_url + self.was_download_cancelled = False + super(ThemeScreenshotWorker, self).__init__() + def run(self): """ Overridden method to run the thread. """ - themes = self.parent().config.get('themes', 'files') + themes = self.config.get('themes', 'files') themes = themes.split(',') - config = self.parent().config + config = self.config for theme in themes: # Stop if the wizard has been cancelled. - if self.parent().was_download_cancelled: + if self.was_download_cancelled: return title = config.get('theme_%s' % theme, 'title') filename = config.get('theme_%s' % theme, 'filename') screenshot = config.get('theme_%s' % theme, 'screenshot') - urllib.request.urlretrieve('%s%s' % (self.parent().themes_url, screenshot), + urllib.request.urlretrieve('%s%s' % (self.themes_url, screenshot), os.path.join(gettempdir(), 'openlp', screenshot)) - item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget) - item.setData(QtCore.Qt.UserRole, filename) - item.setCheckState(QtCore.Qt.Unchecked) - item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + # Signal that the screenshot has been downloaded + self.screenshot_downloaded.emit(title, filename) + + @QtCore.pyqtSlot(bool) + def set_download_canceled(self, toggle): + """ + Externally set if the download was canceled + + :param toggle: Set if the download was canceled or not + """ + self.was_download_cancelled = toggle class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): @@ -142,8 +160,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' self.themes_url = self.web + self.config.get('themes', 'directory') + '/' self.update_screen_list_combo() - self.was_download_cancelled = False self.theme_screenshot_thread = None + self.theme_screenshot_worker = None self.has_run_wizard = False self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...') self.cancel_button.clicked.connect(self.on_cancel_button_clicked) @@ -187,7 +205,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) self.bibles_tree_widget.expandAll() # Download the theme screenshots. - self.theme_screenshot_thread = ThemeScreenshotThread(self) + self.theme_screenshot_worker = ThemeScreenshotWorker(self.config, self.themes_url) + self.theme_screenshot_worker.screenshot_downloaded.connect(self.on_screenshot_downloaded) + self.theme_screenshot_thread = QtCore.QThread(self) + self.theme_screenshot_thread.started.connect(self.theme_screenshot_worker.run) + self.theme_screenshot_worker.moveToThread(self.theme_screenshot_thread) self.theme_screenshot_thread.start() self.application.set_normal_cursor() @@ -255,13 +277,26 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): (self.last_id <= FirstTimePage.Plugins and not self.has_run_wizard): QtCore.QCoreApplication.exit() sys.exit() - self.was_download_cancelled = True + if self.theme_screenshot_worker: + self.theme_screenshot_worker.set_download_canceled(True) # Was the thread created. if self.theme_screenshot_thread: while self.theme_screenshot_thread.isRunning(): time.sleep(0.1) self.application.set_normal_cursor() + def on_screenshot_downloaded(self, title, filename): + """ + Add an item to the list when a theme has been downloaded + + :param title: The title of the theme + :param filename: The filename of the theme + """ + item = QtGui.QListWidgetItem(title, self.themes_list_widget) + item.setData(QtCore.Qt.UserRole, filename) + item.setCheckState(QtCore.Qt.Unchecked) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + def on_no_internet_finish_button_clicked(self): """ Process the triggering of the "Finish" button on the No Internet page. From c89b54e4a4239f5c31d4b33ba991845f098a22f9 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 7 Nov 2014 14:24:20 -0800 Subject: [PATCH 02/14] Add basic test for projector.sourceselectform --- .../test_projectorsourceform.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/interfaces/openlp_core_ui/test_projectorsourceform.py diff --git a/tests/interfaces/openlp_core_ui/test_projectorsourceform.py b/tests/interfaces/openlp_core_ui/test_projectorsourceform.py new file mode 100644 index 000000000..88b475d7e --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_projectorsourceform.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, Ken Roberts, 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 # +############################################################################### +""" + :mod: `tests.interfaces.openlp_core_ui.test_projectorsourceform` module + + Tests for the Projector Source Select form. +""" +import logging +log = logging.getLogger(__name__) +log.debug('test_projectorsourceform loaded') + +from unittest import TestCase + +from tests.helpers.testmixin import TestMixin +from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES + +from openlp.core.ui.projector.sourceselectform import source_group + +def build_source_dict(): + """ + Builds a source dictionary to verify source_group returns a valid dictionary of dictionary items + + :returns: dictionary of valid PJLink source codes grouped by PJLink source group + """ + test_group = {} + for group in PJLINK_DEFAULT_SOURCES.keys(): + test_group[group] = {} + for key in PJLINK_DEFAULT_CODES: + test_group[key[0]][key] = PJLINK_DEFAULT_CODES[key] + return test_group + +class ProjectorSourceFormTest(TestCase, TestMixin): + """ + Test class for the Projector Source Select form module + """ + def source_dict_test(self): + """ + Test that source list dict returned from sourceselectform module is a valid dict with proper entries + """ + # GIVEN: A list of inputs + codes = [] + for item in PJLINK_DEFAULT_CODES.keys(): + codes.append(item) + codes.sort() + + # WHEN: projector.sourceselectform.source_select() is called + check = source_group(codes, PJLINK_DEFAULT_CODES) + + # THEN: return dictionary should match test dictionary + self.assertEquals(check, build_source_dict(), + "Source group dictionary should match test dictionary") From f2cd0a974352d05f70a750ac44e9e12eacbb7efb Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 7 Nov 2014 14:44:40 -0800 Subject: [PATCH 03/14] Fix "Discard" and "Reset" button calls, pep8 --- openlp/core/ui/projector/sourceselectform.py | 39 ++++++++++--------- .../test_projectorsourceform.py | 2 + 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/ui/projector/sourceselectform.py index 6e7ab7f76..6c03473ad 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/ui/projector/sourceselectform.py @@ -152,16 +152,15 @@ def set_button_tooltip(bar): """ for button in bar.buttons(): if bar.standardButton(button) == QDialogButtonBox.Cancel: - tip = "Ignoring current changes and return to OpenLP" + button.setToolTip("Ignoring current changes and return to OpenLP") elif bar.standardButton(button) == QDialogButtonBox.Reset: - tip = "Delete all user-defined text and revert to PJLink default text" + button.setToolTip("Delete all user-defined text and revert to PJLink default text") elif bar.standardButton(button) == QDialogButtonBox.Discard: - tip = "Discard changes and reset to previous user-defined text" + button.setToolTip("Discard changes and reset to previous user-defined text") elif bar.standardButton(button) == QDialogButtonBox.Ok: - tip = "Save changes and return to OpenLP" + button.setToolTip("Save changes and return to OpenLP") else: - tip = "" - button.setToolTip(tip) + log.debug('No tooltip for button {}'.format(button.text())) class FingerTabBarWidget(QTabBar): @@ -286,6 +285,10 @@ class SourceSelectTabs(QDialog): thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key]) if buttonchecked: self.tabwidget.setCurrentIndex(thistab) + self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset | + QtGui.QDialogButtonBox.Discard | + QtGui.QDialogButtonBox.Ok | + QtGui.QDialogButtonBox.Cancel) else: for key in keys: (tab, button_count, buttonchecked) = Build_Tab(group=self.button_group, @@ -297,10 +300,8 @@ class SourceSelectTabs(QDialog): thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key]) if buttonchecked: self.tabwidget.setCurrentIndex(thistab) - self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset | - QtGui.QDialogButtonBox.Discard | - QtGui.QDialogButtonBox.Ok | - QtGui.QDialogButtonBox.Cancel) + self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Ok | + QtGui.QDialogButtonBox.Cancel) self.button_box.clicked.connect(self.button_clicked) self.layout.addWidget(self.button_box) set_button_tooltip(self.button_box) @@ -321,9 +322,9 @@ class SourceSelectTabs(QDialog): if self.button_box.standardButton(button) == self.button_box.Cancel: self.done(0) elif self.button_box.standardButton(button) == self.button_box.Reset: - self.delete_sources() - elif self.button_box.standardButton(button) == self.button_box.Discard: self.done(100) + elif self.button_box.standardButton(button) == self.button_box.Discard: + self.delete_sources() elif self.button_box.standardButton(button) == self.button_box.Ok: return self.accept_me() else: @@ -418,6 +419,10 @@ class SourceSelectSingle(QDialog): item.setText(source_item.text) self.layout.addRow(PJLINK_DEFAULT_CODES[key], item) self.button_group.append(item) + self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset | + QtGui.QDialogButtonBox.Discard | + QtGui.QDialogButtonBox.Ok | + QtGui.QDialogButtonBox.Cancel) else: for key in keys: source_text = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id) @@ -427,10 +432,8 @@ class SourceSelectSingle(QDialog): self.layout.addWidget(button) self.button_group.addButton(button, int(key)) button_list.append(key) - self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset | - QtGui.QDialogButtonBox.Discard | - QtGui.QDialogButtonBox.Ok | - QtGui.QDialogButtonBox.Cancel) + self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Ok | + QtGui.QDialogButtonBox.Cancel) self.button_box.clicked.connect(self.button_clicked) self.layout.addWidget(self.button_box) self.setMinimumHeight(key_count*25) @@ -452,9 +455,9 @@ class SourceSelectSingle(QDialog): if self.button_box.standardButton(button) == self.button_box.Cancel: self.done(0) elif self.button_box.standardButton(button) == self.button_box.Reset: - self.delete_sources() - elif self.button_box.standardButton(button) == self.button_box.Discard: self.done(100) + elif self.button_box.standardButton(button) == self.button_box.Discard: + self.delete_sources() elif self.button_box.standardButton(button) == self.button_box.Ok: return self.accept_me() else: diff --git a/tests/interfaces/openlp_core_ui/test_projectorsourceform.py b/tests/interfaces/openlp_core_ui/test_projectorsourceform.py index 88b475d7e..ed005a7d9 100644 --- a/tests/interfaces/openlp_core_ui/test_projectorsourceform.py +++ b/tests/interfaces/openlp_core_ui/test_projectorsourceform.py @@ -42,6 +42,7 @@ from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEF from openlp.core.ui.projector.sourceselectform import source_group + def build_source_dict(): """ Builds a source dictionary to verify source_group returns a valid dictionary of dictionary items @@ -55,6 +56,7 @@ def build_source_dict(): test_group[key[0]][key] = PJLINK_DEFAULT_CODES[key] return test_group + class ProjectorSourceFormTest(TestCase, TestMixin): """ Test class for the Projector Source Select form module From dec7390ef5c47a18c002ec8d7d4efa463c625a6c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 8 Nov 2014 01:46:18 +0200 Subject: [PATCH 04/14] [bug 1389571] Use Qt threads properly, and kick off a separate thread for each download. [fix] Finally fixed the "configuring OpenLP" page by adding headers [refactor] Make the Wizard start up faster by only downloading the config file after the wizard has started --- openlp/core/ui/firsttimeform.py | 133 +++++++++++++++++------------- openlp/core/ui/firsttimewizard.py | 58 +++++++++---- 2 files changed, 117 insertions(+), 74 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 420131dbc..5d3b91762 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -52,37 +52,37 @@ log = logging.getLogger(__name__) class ThemeScreenshotWorker(QtCore.QObject): """ - This thread downloads the theme screenshots. + This thread downloads a theme's screenshot """ screenshot_downloaded = QtCore.pyqtSignal(str, str) + finished = QtCore.pyqtSignal() - def __init__(self, config, themes_url): + def __init__(self, themes_url, title, filename, screenshot): """ Set up the worker object """ - self.config = config - self.themes_url = themes_url self.was_download_cancelled = False + self.themes_url = themes_url + self.title = title + self.filename = filename + self.screenshot = screenshot super(ThemeScreenshotWorker, self).__init__() def run(self): """ Overridden method to run the thread. """ - themes = self.config.get('themes', 'files') - themes = themes.split(',') - config = self.config - for theme in themes: - # Stop if the wizard has been cancelled. - if self.was_download_cancelled: - return - title = config.get('theme_%s' % theme, 'title') - filename = config.get('theme_%s' % theme, 'filename') - screenshot = config.get('theme_%s' % theme, 'screenshot') - urllib.request.urlretrieve('%s%s' % (self.themes_url, screenshot), - os.path.join(gettempdir(), 'openlp', screenshot)) + if self.was_download_cancelled: + return + try: + urllib.request.urlretrieve('%s%s' % (self.themes_url, self.screenshot), + os.path.join(gettempdir(), 'openlp', self.screenshot)) # Signal that the screenshot has been downloaded - self.screenshot_downloaded.emit(title, filename) + self.screenshot_downloaded.emit(self.title, self.filename) + except: + log.exception('Unable to download screenshot') + finally: + self.finished.emit() @QtCore.pyqtSlot(bool) def set_download_canceled(self, toggle): @@ -105,6 +105,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): Create and set up the first time wizard. """ super(FirstTimeForm, self).__init__(parent) + self.web_access = True self.setup_ui(self) def nextId(self): @@ -112,18 +113,18 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): Determine the next page in the Wizard to go to. """ self.application.process_events() - if self.currentId() == FirstTimePage.Plugins: + if self.currentId() == FirstTimePage.Download: if not self.web_access: return FirstTimePage.NoInternet else: - return FirstTimePage.Songs + return FirstTimePage.Plugins elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: return FirstTimePage.Progress elif self.currentId() == FirstTimePage.Themes: self.application.set_busy_cursor() - while not self.theme_screenshot_thread.isFinished(): + while not all([thread.isFinished() for thread in self.theme_screenshot_threads]): time.sleep(0.1) self.application.process_events() # Build the screenshot icons, as this can not be done in the thread. @@ -146,9 +147,14 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): :param screens: The screens detected by OpenLP """ + self.was_download_cancelled = False self.screens = screens + + def _download_index(self): + """ + Download the configuration file and kick off the theme screenshot download threads + """ # check to see if we have web access - self.web = 'http://openlp.org/files/frw/' self.config = ConfigParser() user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent)) @@ -160,24 +166,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' self.themes_url = self.web + self.config.get('themes', 'directory') + '/' self.update_screen_list_combo() - self.theme_screenshot_thread = None - self.theme_screenshot_worker = None + self.theme_screenshot_threads = [] + self.theme_screenshot_workers = [] self.has_run_wizard = False self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...') - self.cancel_button.clicked.connect(self.on_cancel_button_clicked) - self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) - self.currentIdChanged.connect(self.on_current_id_changed) - Registry().register_function('config_screen_changed', self.update_screen_list_combo) - - def set_defaults(self): - """ - Set up display at start of theme edit. - """ - self.restart() - check_directory_exists(os.path.join(gettempdir(), 'openlp')) - self.no_internet_finish_button.setVisible(False) - # Check if this is a re-run of the wizard. - self.has_run_wizard = Settings().value('core/has run wizard') # Sort out internet access for downloads if self.web_access: songs = self.config.get('songs', 'languages') @@ -204,13 +196,36 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): item.setCheckState(0, QtCore.Qt.Unchecked) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) self.bibles_tree_widget.expandAll() - # Download the theme screenshots. - self.theme_screenshot_worker = ThemeScreenshotWorker(self.config, self.themes_url) - self.theme_screenshot_worker.screenshot_downloaded.connect(self.on_screenshot_downloaded) - self.theme_screenshot_thread = QtCore.QThread(self) - self.theme_screenshot_thread.started.connect(self.theme_screenshot_worker.run) - self.theme_screenshot_worker.moveToThread(self.theme_screenshot_thread) - self.theme_screenshot_thread.start() + # Download the theme screenshots + themes = self.config.get('themes', 'files').split(',') + for theme in themes: + title = self.config.get('theme_%s' % theme, 'title') + filename = self.config.get('theme_%s' % theme, 'filename') + screenshot = self.config.get('theme_%s' % theme, 'screenshot') + worker = ThemeScreenshotWorker(self.themes_url, title, filename, screenshot) + self.theme_screenshot_workers.append(worker) + worker.screenshot_downloaded.connect(self.on_screenshot_downloaded) + thread = QtCore.QThread(self) + self.theme_screenshot_threads.append(thread) + thread.started.connect(worker.run) + worker.finished.connect(thread.quit) + worker.moveToThread(thread) + thread.start() + + def set_defaults(self): + """ + Set up display at start of theme edit. + """ + self.restart() + self.web = 'http://openlp.org/files/frw/' + self.cancel_button.clicked.connect(self.on_cancel_button_clicked) + self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) + self.currentIdChanged.connect(self.on_current_id_changed) + Registry().register_function('config_screen_changed', self.update_screen_list_combo) + self.no_internet_finish_button.setVisible(False) + # Check if this is a re-run of the wizard. + self.has_run_wizard = Settings().value('core/has run wizard') + check_directory_exists(os.path.join(gettempdir(), 'openlp')) self.application.set_normal_cursor() def update_screen_list_combo(self): @@ -230,12 +245,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.application.process_events() if page_id != -1: self.last_id = page_id - if page_id == FirstTimePage.Plugins: + if page_id == FirstTimePage.Download: + self.back_button.setVisible(False) + self.next_button.setVisible(False) # Set the no internet page text. if self.has_run_wizard: self.no_internet_label.setText(self.no_internet_text) else: self.no_internet_label.setText(self.no_internet_text + self.cancel_wizard_text) + self.application.set_busy_cursor() + self._download_index() + self.application.set_normal_cursor() + self.back_button.setVisible(False) + self.next_button.setVisible(True) + self.next() elif page_id == FirstTimePage.Defaults: self.theme_combo_box.clear() for index in range(self.themes_list_widget.count()): @@ -255,15 +278,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): elif page_id == FirstTimePage.NoInternet: self.back_button.setVisible(False) self.next_button.setVisible(False) + self.cancel_button.setVisible(False) self.no_internet_finish_button.setVisible(True) - if self.has_run_wizard: - self.cancel_button.setVisible(False) elif page_id == FirstTimePage.Progress: self.application.set_busy_cursor() - self.repaint() - self.application.process_events() - # Try to give the wizard a chance to redraw itself - time.sleep(0.2) self._pre_wizard() self._perform_wizard() self._post_wizard() @@ -273,15 +291,16 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ Process the triggering of the cancel button. """ - if self.last_id == FirstTimePage.NoInternet or \ - (self.last_id <= FirstTimePage.Plugins and not self.has_run_wizard): + self.was_download_cancelled = True + if not self.has_run_wizard: QtCore.QCoreApplication.exit() sys.exit() - if self.theme_screenshot_worker: - self.theme_screenshot_worker.set_download_canceled(True) + if self.theme_screenshot_workers: + for worker in self.theme_screenshot_workers: + worker.set_download_canceled(True) # Was the thread created. - if self.theme_screenshot_thread: - while self.theme_screenshot_thread.isRunning(): + if self.theme_screenshot_threads: + while any([thread.isRunning() for thread in self.theme_screenshot_threads]): time.sleep(0.1) self.application.set_normal_cursor() diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 4e83a3e74..792e36a50 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -41,13 +41,14 @@ class FirstTimePage(object): An enumeration class with each of the pages of the wizard. """ Welcome = 0 - Plugins = 1 + Download = 1 NoInternet = 2 - Songs = 3 - Bibles = 4 - Themes = 5 - Defaults = 6 - Progress = 7 + Plugins = 3 + Songs = 4 + Bibles = 5 + Themes = 6 + Defaults = 7 + Progress = 8 class UiFirstTimeWizard(object): @@ -78,6 +79,33 @@ class UiFirstTimeWizard(object): self.next_button = self.button(QtGui.QWizard.NextButton) self.back_button = self.button(QtGui.QWizard.BackButton) add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp') + # The download page + self.download_page = QtGui.QWizardPage() + self.download_page.setObjectName('download_page') + self.download_layout = QtGui.QVBoxLayout(self.download_page) + self.download_layout.setMargin(48) + self.download_layout.setObjectName('download_layout') + self.download_label = QtGui.QLabel(self.download_page) + self.download_label.setObjectName('download_label') + self.download_layout.addWidget(self.download_label) + self.download_progress_bar = QtGui.QProgressBar(self.download_page) + self.download_progress_bar.setMinimum(0) + self.download_progress_bar.setMaximum(0) + self.download_progress_bar.setValue(0) + self.download_progress_bar.setObjectName('download_progress_bar') + self.download_layout.addWidget(self.download_progress_bar) + first_time_wizard.setPage(FirstTimePage.Download, self.download_page) + # The "you don't have an internet connection" page. + self.no_internet_page = QtGui.QWizardPage() + self.no_internet_page.setObjectName('no_internet_page') + self.no_internet_layout = QtGui.QVBoxLayout(self.no_internet_page) + self.no_internet_layout.setContentsMargins(50, 30, 50, 40) + self.no_internet_layout.setObjectName('no_internet_layout') + self.no_internet_label = QtGui.QLabel(self.no_internet_page) + self.no_internet_label.setWordWrap(True) + self.no_internet_label.setObjectName('no_internet_label') + self.no_internet_layout.addWidget(self.no_internet_label) + first_time_wizard.setPage(FirstTimePage.NoInternet, self.no_internet_page) # The plugins page self.plugin_page = QtGui.QWizardPage() self.plugin_page.setObjectName('plugin_page') @@ -120,17 +148,6 @@ class UiFirstTimeWizard(object): self.alert_check_box.setObjectName('alert_check_box') self.plugin_layout.addWidget(self.alert_check_box) first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page) - # The "you don't have an internet connection" page. - self.no_internet_page = QtGui.QWizardPage() - self.no_internet_page.setObjectName('no_internet_page') - self.no_internet_layout = QtGui.QVBoxLayout(self.no_internet_page) - self.no_internet_layout.setContentsMargins(50, 30, 50, 40) - self.no_internet_layout.setObjectName('no_internet_layout') - self.no_internet_label = QtGui.QLabel(self.no_internet_page) - self.no_internet_label.setWordWrap(True) - self.no_internet_label.setObjectName('no_internet_label') - self.no_internet_layout.addWidget(self.no_internet_label) - first_time_wizard.setPage(FirstTimePage.NoInternet, self.no_internet_page) # The song samples page self.songs_page = QtGui.QWizardPage() self.songs_page.setObjectName('songs_page') @@ -221,6 +238,10 @@ class UiFirstTimeWizard(object): translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. ' 'Click the %s button below to start.') % clean_button_text(first_time_wizard.buttonText(QtGui.QWizard.NextButton))) + self.download_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading Resource Index')) + self.download_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while the resource index is ' + 'downloaded.')) + self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Downloading resource index...')) self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins')) self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. ')) self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) @@ -257,5 +278,8 @@ class UiFirstTimeWizard(object): 'Set up default settings to be used by OpenLP.')) self.display_label.setText(translate('OpenLP.FirstTimeWizard', 'Default output display:')) self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:')) + self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring')) + self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded ' + 'and OpenLP is configured.')) self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...')) first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish')) From 4f4c76ae8b061ed2719b5ba328ea6bb1721f16c0 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 8 Nov 2014 21:25:06 +0200 Subject: [PATCH 05/14] [fix] Don't exit OpenLP when you cancel the FRW from the Tools menu --- openlp/core/__init__.py | 3 +++ openlp/core/ui/firsttimeform.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 37a713d38..9f5c69770 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -122,6 +122,9 @@ class OpenLP(OpenLPMixin, QtGui.QApplication): ftw.initialize(screens) if ftw.exec_() == QtGui.QDialog.Accepted: Settings().setValue('core/has run wizard', True) + elif ftw.was_download_cancelled: + QtCore.QCoreApplication.exit() + sys.exit() # Correct stylesheet bugs application_stylesheet = '' if not Settings().value('advanced/alternate rows'): diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 5d3b91762..06a238fed 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -292,9 +292,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): Process the triggering of the cancel button. """ self.was_download_cancelled = True - if not self.has_run_wizard: - QtCore.QCoreApplication.exit() - sys.exit() if self.theme_screenshot_workers: for worker in self.theme_screenshot_workers: worker.set_download_canceled(True) From 14377f332963d6066695e743aa261e0afa98cf4b Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 8 Nov 2014 23:15:25 +0200 Subject: [PATCH 06/14] [refactor] Renamed "was_download_cancelled" to "was_cancelled" [refactor] Put some more stuff back into initalise() where it belongs [refactor] Rewrote old tests, wrote new tests --- openlp/core/__init__.py | 2 +- openlp/core/ui/firsttimeform.py | 17 +-- openlp/core/ui/mainwindow.py | 2 +- .../openlp_core_ui/test_firsttimeform.py | 137 +++++++++++++----- 4 files changed, 110 insertions(+), 48 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 9f5c69770..503aadee3 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -122,7 +122,7 @@ class OpenLP(OpenLPMixin, QtGui.QApplication): ftw.initialize(screens) if ftw.exec_() == QtGui.QDialog.Accepted: Settings().setValue('core/has run wizard', True) - elif ftw.was_download_cancelled: + elif ftw.was_cancelled: QtCore.QCoreApplication.exit() sys.exit() # Correct stylesheet bugs diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 06a238fed..75f661c3a 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -31,7 +31,6 @@ This module contains the first time wizard. """ import logging import os -import sys import time import urllib.request import urllib.parse @@ -105,7 +104,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): Create and set up the first time wizard. """ super(FirstTimeForm, self).__init__(parent) - self.web_access = True self.setup_ui(self) def nextId(self): @@ -147,8 +145,12 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): :param screens: The screens detected by OpenLP """ - self.was_download_cancelled = False self.screens = screens + self.web_access = True + self.was_cancelled = False + self.theme_screenshot_threads = [] + self.theme_screenshot_workers = [] + self.has_run_wizard = False def _download_index(self): """ @@ -166,9 +168,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' self.themes_url = self.web + self.config.get('themes', 'directory') + '/' self.update_screen_list_combo() - self.theme_screenshot_threads = [] - self.theme_screenshot_workers = [] - self.has_run_wizard = False self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...') # Sort out internet access for downloads if self.web_access: @@ -291,7 +290,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ Process the triggering of the cancel button. """ - self.was_download_cancelled = True + self.was_cancelled = True if self.theme_screenshot_workers: for worker in self.theme_screenshot_workers: worker.set_download_canceled(True) @@ -333,7 +332,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): url_file = urllib.request.urlopen(url) filename = open(f_path, "wb") # Download until finished or canceled. - while not self.was_download_cancelled: + while not self.was_cancelled: data = url_file.read(block_size) if not data: break @@ -342,7 +341,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self._download_progress(block_count, block_size) filename.close() # Delete file if cancelled, it may be a partial file. - if self.was_download_cancelled: + if self.was_cancelled: os.remove(f_path) def _build_theme_screenshots(self): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 825a12889..48fc1fcbf 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -690,7 +690,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): first_run_wizard = FirstTimeForm(self) first_run_wizard.initialize(ScreenList()) first_run_wizard.exec_() - if first_run_wizard.was_download_cancelled: + if first_run_wizard.was_cancelled: return self.application.set_busy_cursor() self.first_time() diff --git a/tests/functional/openlp_core_ui/test_firsttimeform.py b/tests/functional/openlp_core_ui/test_firsttimeform.py index ed7a7a9e8..da5305bda 100644 --- a/tests/functional/openlp_core_ui/test_firsttimeform.py +++ b/tests/functional/openlp_core_ui/test_firsttimeform.py @@ -30,6 +30,7 @@ Package to test the openlp.core.ui.firsttimeform package. """ from configparser import ConfigParser +import os from unittest import TestCase from openlp.core.common import Registry @@ -55,52 +56,114 @@ class TestFirstTimeForm(TestCase, TestMixin): def setUp(self): self.setup_application() self.app.setApplicationVersion('0.0') + # Set up a fake "set_normal_cursor" method since we're not dealing with an actual OpenLP application object + self.app.set_normal_cursor = lambda: None Registry.create() Registry().register('application', self.app) - def basic_initialise_test(self): + def initialise_test(self): """ - Test if we can intialise the FirstTimeForm without a config file + Test if we can intialise the FirstTimeForm """ - # GIVEN: A mocked get_web_page, a First Time Wizard and an expected screen object - with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: - first_time_form = FirstTimeForm(None) - expected_screens = MagicMock() - expected_web_url = 'http://openlp.org/files/frw/' - expected_user_agent = 'OpenLP/0.0' - mocked_get_web_page.return_value = None + # GIVEN: A First Time Wizard and an expected screen object + frw = FirstTimeForm(None) + expected_screens = MagicMock() - # WHEN: The First Time Wizard is initialised - first_time_form.initialize(expected_screens) + # WHEN: The First Time Wizard is initialised + frw.initialize(expected_screens) - # THEN: The First Time Form web configuration file should be accessible and parseable - self.assertEqual(expected_screens, first_time_form.screens, 'The screens should be correct') - self.assertEqual(expected_web_url, first_time_form.web, 'The base path of the URL should be correct') - self.assertIsInstance(first_time_form.config, ConfigParser, 'The config object should be a ConfigParser') - mocked_get_web_page.assert_called_with(expected_web_url + 'download.cfg', - header=('User-Agent', expected_user_agent)) + # THEN: The screens should be set up, and the default values initialised + self.assertEqual(expected_screens, frw.screens, 'The screens should be correct') + self.assertTrue(frw.web_access, 'The default value of self.web_access should be True') + self.assertFalse(frw.was_cancelled, 'The default value of self.was_cancelled should be False') + self.assertListEqual([], frw.theme_screenshot_threads, 'The list of threads should be empty') + self.assertListEqual([], frw.theme_screenshot_workers, 'The list of workers should be empty') + self.assertFalse(frw.has_run_wizard, 'has_run_wizard should be False') - def config_initialise_test(self): + def set_defaults_test(self): """ - Test if we can intialise the FirstTimeForm with a config file + Test that the default values are set when set_defaults() is run """ - # GIVEN: A mocked get_web_page, a First Time Wizard and an expected screen object - with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: - first_time_form = FirstTimeForm(None) - expected_web_url = 'http://openlp.org/files/frw/' - expected_songs_url = 'http://example.com/frw/songs/' - expected_bibles_url = 'http://example.com/frw/bibles/' - expected_themes_url = 'http://example.com/frw/themes/' - expected_user_agent = 'OpenLP/0.0' - mocked_get_web_page.return_value.read.return_value = FAKE_CONFIG + # GIVEN: An initialised FRW and a whole lot of stuff mocked out + frw = FirstTimeForm(None) + frw.initialize(MagicMock()) + with patch.object(frw, 'restart') as mocked_restart, \ + patch.object(frw, 'cancel_button') as mocked_cancel_button, \ + patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ + patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ + patch.object(Registry, 'register_function') as mocked_register_function, \ + patch('openlp.core.ui.firsttimeform.Settings') as MockedSettings, \ + patch('openlp.core.ui.firsttimeform.gettempdir') as mocked_gettempdir, \ + patch('openlp.core.ui.firsttimeform.check_directory_exists') as mocked_check_directory_exists, \ + patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: + mocked_settings = MagicMock() + mocked_settings.value.return_value = True + MockedSettings.return_value = mocked_settings + mocked_gettempdir.return_value = 'temp' + expected_temp_path = os.path.join('temp', 'openlp') - # WHEN: The First Time Wizard is initialised - first_time_form.initialize(MagicMock()) + # WHEN: The set_defaults() method is run + frw.set_defaults() - # THEN: The First Time Form web configuration file should be accessible and parseable - self.assertIsInstance(first_time_form.config, ConfigParser, 'The config object should be a ConfigParser') - mocked_get_web_page.assert_called_with(expected_web_url + 'download.cfg', - header=('User-Agent', expected_user_agent)) - self.assertEqual(expected_songs_url, first_time_form.songs_url, 'The songs URL should be correct') - self.assertEqual(expected_bibles_url, first_time_form.bibles_url, 'The bibles URL should be correct') - self.assertEqual(expected_themes_url, first_time_form.themes_url, 'The themes URL should be correct') + # THEN: The default values should have been set + mocked_restart.assert_called_with() + self.assertEqual('http://openlp.org/files/frw/', frw.web, 'The default URL should be set') + mocked_cancel_button.clicked.connect.assert_called_with(frw.on_cancel_button_clicked) + mocked_no_internet_finish_btn.clicked.connect.assert_called_with(frw.on_no_internet_finish_button_clicked) + mocked_currentIdChanged.connect.assert_called_with(frw.on_current_id_changed) + mocked_register_function.assert_called_with('config_screen_changed', frw.update_screen_list_combo) + mocked_no_internet_finish_btn.setVisible.assert_called_with(False) + mocked_settings.value.assert_called_with('core/has run wizard') + mocked_gettempdir.assert_called_with() + mocked_check_directory_exists.assert_called_with(expected_temp_path) + mocked_set_normal_cursor.assert_called_with() + + def update_screen_list_combo_test(self): + """ + Test that the update_screen_list_combo() method works correctly + """ + # GIVEN: A mocked Screen() object and an initialised First Run Wizard and a mocked display_combo_box + expected_screen_list = ['Screen 1', 'Screen 2'] + mocked_screens = MagicMock() + mocked_screens.get_screen_list.return_value = expected_screen_list + frw = FirstTimeForm(None) + frw.initialize(mocked_screens) + with patch.object(frw, 'display_combo_box') as mocked_display_combo_box: + mocked_display_combo_box.count.return_value = 2 + + # WHEN: update_screen_list_combo() is called + frw.update_screen_list_combo() + + # THEN: The combobox should have been updated + mocked_display_combo_box.clear.assert_called_with() + mocked_screens.get_screen_list.assert_called_with() + mocked_display_combo_box.addItems.assert_called_with(expected_screen_list) + mocked_display_combo_box.count.assert_called_with() + mocked_display_combo_box.setCurrentIndex.assert_called_with(1) + + def on_cancel_button_clicked_test(self): + """ + Test that the cancel button click slot shuts down the threads correctly + """ + # GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff + frw = FirstTimeForm(None) + frw.initialize(MagicMock()) + mocked_worker = MagicMock() + mocked_thread = MagicMock() + mocked_thread.isRunning.side_effect = [True, False] + frw.theme_screenshot_workers.append(mocked_worker) + frw.theme_screenshot_threads.append(mocked_thread) + with patch('openlp.core.ui.firsttimeform.time') as mocked_time, \ + patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: + + # WHEN: on_cancel_button_clicked() is called + frw.on_cancel_button_clicked() + + # THEN: The right things should be called in the right order + self.assertTrue(frw.was_cancelled, 'The was_cancelled property should have been set to True') + mocked_worker.set_download_canceled.assert_called_with(True) + mocked_thread.isRunning.assert_called_with() + self.assertEqual(2, mocked_thread.isRunning.call_count, 'isRunning() should have been called twice') + mocked_time.sleep.assert_called_with(0.1) + self.assertEqual(1, mocked_time.sleep.call_count, 'sleep() should have only been called once') + mocked_set_normal_cursor.assert_called_with() From aadbce133e9c1e39e0759b7864e46f49fe857c98 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Mon, 10 Nov 2014 16:14:17 -0800 Subject: [PATCH 07/14] Add missing tranlsate to text/fix select/edit title --- openlp/core/ui/projector/sourceselectform.py | 39 +++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/ui/projector/sourceselectform.py index 6c03473ad..e7e0c2201 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/ui/projector/sourceselectform.py @@ -152,13 +152,17 @@ def set_button_tooltip(bar): """ for button in bar.buttons(): if bar.standardButton(button) == QDialogButtonBox.Cancel: - button.setToolTip("Ignoring current changes and return to OpenLP") + button.setToolTip(translate('OpenLP.SourceSelectForm', + "Ignoring current changes and return to OpenLP")) elif bar.standardButton(button) == QDialogButtonBox.Reset: - button.setToolTip("Delete all user-defined text and revert to PJLink default text") + button.setToolTip(translate('OpenLP.SourceSelectForm', + "Delete all user-defined text and revert to PJLink default text")) elif bar.standardButton(button) == QDialogButtonBox.Discard: - button.setToolTip("Discard changes and reset to previous user-defined text") + button.setToolTip(translate('OpenLP.SourceSelectForm', + "Discard changes and reset to previous user-defined text")) elif bar.standardButton(button) == QDialogButtonBox.Ok: - button.setToolTip("Save changes and return to OpenLP") + button.setToolTip(translate('OpenLP.SourceSelectForm', + "Save changes and return to OpenLP")) else: log.debug('No tooltip for button {}'.format(button.text())) @@ -236,12 +240,13 @@ class SourceSelectTabs(QDialog): """ log.debug('Initializing SourceSelectTabs()') super(SourceSelectTabs, self).__init__(parent) + self.setMinimumWidth(350) self.projectordb = projectordb self.edit = edit if self.edit: - title = translate('OpenLP.SourceSelectForm', 'Select Projector Source') - else: title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text') + else: + title = translate('OpenLP.SourceSelectForm', 'Select Projector Source') self.setWindowTitle(title) self.setObjectName('source_select_tabs') self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png')) @@ -332,9 +337,11 @@ class SourceSelectTabs(QDialog): def delete_sources(self): msg = QtGui.QMessageBox() - msg.setText('Delete entries for this projector') - msg.setInformativeText('Are you sure you want to delete ALL user-defined ' - 'source input text for this projector?') + msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector')) + msg.setInformativeText(translate('OpenLP.SourceSelectForm', + 'Are you sure you want to delete ALL user-defined '), + translate('OpenLP.SourceSelectForm', + 'source input text for this projector?')) msg.setStandardButtons(msg.Cancel | msg.Ok) msg.setDefaultButton(msg.Cancel) ans = msg.exec_() @@ -383,7 +390,11 @@ class SourceSelectSingle(QDialog): log.debug('Initializing SourceSelectSingle()') self.projectordb = projectordb super(SourceSelectSingle, self).__init__(parent) - self.setWindowTitle(translate('OpenLP.SourceSelectSingle', 'Select Projector Source')) + self.edit = edit + if self.edit: + title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text') + else: + title = translate('OpenLP.SourceSelectForm', 'Select Projector Source') self.setObjectName('source_select_single') self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png')) self.setModal(True) @@ -465,9 +476,11 @@ class SourceSelectSingle(QDialog): def delete_sources(self): msg = QtGui.QMessageBox() - msg.setText('Delete entries for this projector') - msg.setInformativeText('Are you sure you want to delete ALL user-defined ' - 'source input text for this projector?') + msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector')) + msg.setInformativeText(translate('OpenLP.SourceSelectForm', + 'Are you sure you want to delete ALL user-defined '), + translate('OpenLP.SourceSelectForm', + 'source input text for this projector?')) msg.setStandardButtons(msg.Cancel | msg.Ok) msg.setDefaultButton(msg.Cancel) ans = msg.exec_() From ea14deaa873ceb2076ad5979e618764d907d06e2 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Wed, 26 Nov 2014 09:24:05 -0800 Subject: [PATCH 08/14] Fix quotes --- .bzrignore | 42 ------------------- openlp/core/ui/projector/sourceselectform.py | 8 ++-- .../openlp_core_ui/test_projectormanager.py | 4 +- 3 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 .bzrignore diff --git a/.bzrignore b/.bzrignore deleted file mode 100644 index 6b7b989a6..000000000 --- a/.bzrignore +++ /dev/null @@ -1,42 +0,0 @@ -*.pyc -*.*~ -\#*\# -*.eric4project -*.eric5project -*.ropeproject -*.e4* -.eric4project -.komodotools -*.komodoproject -list -openlp.org 2.0.e4* -documentation/build/html -documentation/build/doctrees -*.log* -dist -OpenLP.egg-info -build -resources/innosetup/Output -_eric4project -.pylint.d -*.qm -openlp/core/resources.py.old -*.qm -resources/windows/warnOpenLP.txt -openlp.cfg -.idea -openlp.pro -.kdev4 -tests.kdev4 -*.nja -*.orig -__pycache__ -*.dll -.directory -*.kate-swp -# Git files -.git -.gitignore -# Rejected diff's -*.rej -*.~\?~ diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/ui/projector/sourceselectform.py index e7e0c2201..244b7adef 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/ui/projector/sourceselectform.py @@ -153,16 +153,16 @@ def set_button_tooltip(bar): for button in bar.buttons(): if bar.standardButton(button) == QDialogButtonBox.Cancel: button.setToolTip(translate('OpenLP.SourceSelectForm', - "Ignoring current changes and return to OpenLP")) + 'Ignoring current changes and return to OpenLP')) elif bar.standardButton(button) == QDialogButtonBox.Reset: button.setToolTip(translate('OpenLP.SourceSelectForm', - "Delete all user-defined text and revert to PJLink default text")) + 'Delete all user-defined text and revert to PJLink default text')) elif bar.standardButton(button) == QDialogButtonBox.Discard: button.setToolTip(translate('OpenLP.SourceSelectForm', - "Discard changes and reset to previous user-defined text")) + 'Discard changes and reset to previous user-defined text')) elif bar.standardButton(button) == QDialogButtonBox.Ok: button.setToolTip(translate('OpenLP.SourceSelectForm', - "Save changes and return to OpenLP")) + 'Save changes and return to OpenLP')) else: log.debug('No tooltip for button {}'.format(button.text())) diff --git a/tests/interfaces/openlp_core_ui/test_projectormanager.py b/tests/interfaces/openlp_core_ui/test_projectormanager.py index a46b0b93c..58638458e 100644 --- a/tests/interfaces/openlp_core_ui/test_projectormanager.py +++ b/tests/interfaces/openlp_core_ui/test_projectormanager.py @@ -94,11 +94,9 @@ class TestProjectorManager(TestCase, TestMixin): self.projector_manager.bootstrap_initialise() self.projector_manager.bootstrap_post_set_up() - # THEN: verify calls to retrieve saved projectors + # THEN: verify calls to retrieve saved projectors and edit page initialized self.assertEqual(1, self.projector_manager._load_projectors.call_count, 'Initialization should have called load_projectors()') - - # THEN: Verify edit page is initialized self.assertEqual(type(self.projector_manager.projector_form), ProjectorEditForm, 'Initialization should have created a Projector Edit Form') self.assertIs(self.projector_manager.projectordb, From a517ed5fea0e6e876b927cf44843477ceb0d612c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 26 Nov 2014 22:02:48 +0200 Subject: [PATCH 09/14] [refactor] Remove the progress bar on the first download page, and update the label to be slightly more descriptive [fix] Hide the back button on the plugins page so that folks can't go back and re-download the config file --- openlp/core/ui/firsttimeform.py | 13 ++++++++++++- openlp/core/ui/firsttimewizard.py | 9 ++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 75f661c3a..148cb0f60 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -159,7 +159,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): # check to see if we have web access self.config = ConfigParser() user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() - self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent)) + self.application.process_events() + self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent), + update_openlp=True) + self.application.process_events() if self.web_access: files = self.web_access.read() self.config.read_string(files.decode()) @@ -168,12 +171,14 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' self.themes_url = self.web + self.config.get('themes', 'directory') + '/' self.update_screen_list_combo() + self.application.process_events() self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...') # Sort out internet access for downloads if self.web_access: songs = self.config.get('songs', 'languages') songs = songs.split(',') for song in songs: + self.application.process_events() title = self.config.get('songs_%s' % song, 'title') filename = self.config.get('songs_%s' % song, 'filename') item = QtGui.QListWidgetItem(title, self.songs_list_widget) @@ -183,11 +188,13 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): bible_languages = self.config.get('bibles', 'languages') bible_languages = bible_languages.split(',') for lang in bible_languages: + self.application.process_events() language = self.config.get('bibles_%s' % lang, 'title') lang_item = QtGui.QTreeWidgetItem(self.bibles_tree_widget, [language]) bibles = self.config.get('bibles_%s' % lang, 'translations') bibles = bibles.split(',') for bible in bibles: + self.application.process_events() title = self.config.get('bible_%s' % bible, 'title') filename = self.config.get('bible_%s' % bible, 'filename') item = QtGui.QTreeWidgetItem(lang_item, [title]) @@ -195,9 +202,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): item.setCheckState(0, QtCore.Qt.Unchecked) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) self.bibles_tree_widget.expandAll() + self.application.process_events() # Download the theme screenshots themes = self.config.get('themes', 'files').split(',') for theme in themes: + self.application.process_events() title = self.config.get('theme_%s' % theme, 'title') filename = self.config.get('theme_%s' % theme, 'filename') screenshot = self.config.get('theme_%s' % theme, 'screenshot') @@ -279,6 +288,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): self.next_button.setVisible(False) self.cancel_button.setVisible(False) self.no_internet_finish_button.setVisible(True) + elif page_id == FirstTimePage.Plugins: + self.back_button.setVisible(False) elif page_id == FirstTimePage.Progress: self.application.set_busy_cursor() self._pre_wizard() diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 792e36a50..964fdd7a6 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -88,12 +88,6 @@ class UiFirstTimeWizard(object): self.download_label = QtGui.QLabel(self.download_page) self.download_label.setObjectName('download_label') self.download_layout.addWidget(self.download_label) - self.download_progress_bar = QtGui.QProgressBar(self.download_page) - self.download_progress_bar.setMinimum(0) - self.download_progress_bar.setMaximum(0) - self.download_progress_bar.setValue(0) - self.download_progress_bar.setObjectName('download_progress_bar') - self.download_layout.addWidget(self.download_progress_bar) first_time_wizard.setPage(FirstTimePage.Download, self.download_page) # The "you don't have an internet connection" page. self.no_internet_page = QtGui.QWizardPage() @@ -241,7 +235,8 @@ class UiFirstTimeWizard(object): self.download_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading Resource Index')) self.download_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while the resource index is ' 'downloaded.')) - self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Downloading resource index...')) + self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Please wait while OpenLP downloads the ' + 'resource index file...')) self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins')) self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. ')) self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) From f9a052ac8527921c5e5dc9ebec23926514ac3de5 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Mon, 1 Dec 2014 07:39:05 -0800 Subject: [PATCH 10/14] Add .bzrignore from trunk --- .bzrignore | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .bzrignore diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 000000000..6b7b989a6 --- /dev/null +++ b/.bzrignore @@ -0,0 +1,42 @@ +*.pyc +*.*~ +\#*\# +*.eric4project +*.eric5project +*.ropeproject +*.e4* +.eric4project +.komodotools +*.komodoproject +list +openlp.org 2.0.e4* +documentation/build/html +documentation/build/doctrees +*.log* +dist +OpenLP.egg-info +build +resources/innosetup/Output +_eric4project +.pylint.d +*.qm +openlp/core/resources.py.old +*.qm +resources/windows/warnOpenLP.txt +openlp.cfg +.idea +openlp.pro +.kdev4 +tests.kdev4 +*.nja +*.orig +__pycache__ +*.dll +.directory +*.kate-swp +# Git files +.git +.gitignore +# Rejected diff's +*.rej +*.~\?~ From ba8114609337b8119550bb85a14c8218ab8357f9 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 14 Dec 2014 22:37:24 +0200 Subject: [PATCH 11/14] Fix a bug inadvertently introduced in the merge conflict. --- openlp/core/ui/firsttimeform.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 33b721af4..6a77e5bc9 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -113,16 +113,17 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): """ Returns the id of the next FirstTimePage to go to based on enabled plugins """ - # The songs plugin is enabled if FirstTimePage.Welcome < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): - print('Go for songs! %r' % self.songs_check_box.isChecked()) + # If the songs plugin is enabled then go to the songs page return FirstTimePage.Songs - # The Bibles plugin is enabled elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked(): + # Otherwise, if the Bibles plugin is enabled then go to the Bibles page return FirstTimePage.Bibles elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Themes: + # Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes return FirstTimePage.Themes else: + # If all else fails, go to the next page return self.currentId() + 1 def nextId(self): @@ -134,7 +135,9 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): if not self.web_access: return FirstTimePage.NoInternet else: - return self.get_next_page_id() + return FirstTimePage.Plugins + elif self.currentId() == FirstTimePage.Plugins: + return self.get_next_page_id() elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: @@ -414,6 +417,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): for index, theme in enumerate(themes): screenshot = self.config.get('theme_%s' % theme, 'screenshot') item = self.themes_list_widget.item(index) + # if item: item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot))) def _get_file_size(self, url): From 5686e85384c7218a8621bacedb3423d565739066 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Tue, 16 Dec 2014 07:47:46 +0000 Subject: [PATCH 12/14] Fix custom duplication bug --- openlp/plugins/custom/lib/mediaitem.py | 13 ++-- .../openlp_plugins/custom/__init__.py | 28 ++++++++ .../openlp_plugins/custom/test_mediaitem.py | 71 +++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 tests/functional/openlp_plugins/custom/__init__.py create mode 100644 tests/functional/openlp_plugins/custom/test_mediaitem.py diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 9ecfac779..9b358453b 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -287,10 +287,15 @@ class CustomMediaItem(MediaManagerItem): log.debug('service_load') if self.plugin.status != PluginStatus.Active: return - custom = self.plugin.db_manager.get_object_filtered(CustomSlide, and_(CustomSlide.title == item.title, - CustomSlide.theme_name == item.theme, - CustomSlide.credits == - item.raw_footer[0][len(item.title) + 1:])) + if item.theme: + custom = self.plugin.db_manager.get_object_filtered(CustomSlide, and_(CustomSlide.title == item.title, + CustomSlide.theme_name == item.theme, + CustomSlide.credits == + item.raw_footer[0][len(item.title) + 1:])) + else: + custom = self.plugin.db_manager.get_object_filtered(CustomSlide, and_(CustomSlide.title == item.title, + CustomSlide.credits == + item.raw_footer[0][len(item.title) + 1:])) if custom: item.edit_id = custom.id return item diff --git a/tests/functional/openlp_plugins/custom/__init__.py b/tests/functional/openlp_plugins/custom/__init__.py new file mode 100644 index 000000000..6b241e7fc --- /dev/null +++ b/tests/functional/openlp_plugins/custom/__init__.py @@ -0,0 +1,28 @@ +# -*- 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 # +############################################################################### diff --git a/tests/functional/openlp_plugins/custom/test_mediaitem.py b/tests/functional/openlp_plugins/custom/test_mediaitem.py new file mode 100644 index 000000000..4624441aa --- /dev/null +++ b/tests/functional/openlp_plugins/custom/test_mediaitem.py @@ -0,0 +1,71 @@ +""" +This module contains tests for the lib submodule of the Songs plugin. +""" +from unittest import TestCase + +from PyQt4 import QtCore, QtGui + +from openlp.core.common import Registry, Settings +from openlp.core.lib import ServiceItem, PluginStatus +from openlp.plugins.custom.lib import CustomMediaItem +from tests.functional import patch, MagicMock +from tests.helpers.testmixin import TestMixin + + +class TestMediaItem(TestCase, TestMixin): + """ + Test the functions in the :mod:`lib` module. + """ + def setUp(self): + """ + Set up the components need for all tests. + """ + Registry.create() + Registry().register('service_list', MagicMock()) + Registry().register('main_window', MagicMock()) + with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \ + patch('openlp.core.lib.mediamanageritem.MediaManagerItem.setup_item'), \ + patch('openlp.plugins.custom.forms.editcustomform.EditCustomForm.__init__'), \ + patch('openlp.plugins.custom.lib.mediaitem.CustomMediaItem.setup_item'): + self.media_item = CustomMediaItem(None, MagicMock()) + self.setup_application() + self.build_settings() + QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + self.destroy_settings() + + def service_load_inactive_test(self): + """ + Test the service load in custom with a default service item + """ + # GIVEN: An empty Service Item + service_item = ServiceItem(None) + + # WHEN: I search for the custom in the database + item = self.media_item.service_load(service_item) + + # THEN: the processing should be ignored + self.assertEqual(item, None, 'The Service item is inactive so processing should be bypassed') + + def service_load_basic_custom_test(self): + """ + Test the service load in custom with a default service item + """ + # GIVEN: An empty Service Item and an active plugin + service_item = ServiceItem(None) + self.media_item.plugin = MagicMock() + self.media_item.plugin.status = PluginStatus.Active + self.media_item.plugin.db_manager = MagicMock() + self.media_item.plugin.db_manager.get_object_filtered = MagicMock() + self.media_item.plugin.db_manager.get_object_filtered.return_value = None + + with patch('openlp.plugins.custom.lib.db.CustomSlide'): + # WHEN: I search for the custom in the database + item = self.media_item.service_load(service_item) + + # THEN: the processing should be ignored + self.assertEqual(item, None, 'The Service item is inactive so processing should be bypassed') From 927e61097748f7afce0a19c1e049f2508aead627 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 18 Dec 2014 21:15:14 +0000 Subject: [PATCH 13/14] Fix tests --- .../openlp_plugins/custom/test_mediaitem.py | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/functional/openlp_plugins/custom/test_mediaitem.py b/tests/functional/openlp_plugins/custom/test_mediaitem.py index 4624441aa..ef04dfb4e 100644 --- a/tests/functional/openlp_plugins/custom/test_mediaitem.py +++ b/tests/functional/openlp_plugins/custom/test_mediaitem.py @@ -8,9 +8,12 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import Registry, Settings from openlp.core.lib import ServiceItem, PluginStatus from openlp.plugins.custom.lib import CustomMediaItem +from openlp.plugins.custom.lib.db import CustomSlide from tests.functional import patch, MagicMock from tests.helpers.testmixin import TestMixin +FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] + class TestMediaItem(TestCase, TestMixin): """ @@ -51,21 +54,48 @@ class TestMediaItem(TestCase, TestMixin): # THEN: the processing should be ignored self.assertEqual(item, None, 'The Service item is inactive so processing should be bypassed') - def service_load_basic_custom_test(self): + def service_load_basic_custom_false_test(self): """ - Test the service load in custom with a default service item + Test the service load in custom with a default service item and no requirement to add to the database """ # GIVEN: An empty Service Item and an active plugin service_item = ServiceItem(None) + service_item.raw_footer = FOOTER self.media_item.plugin = MagicMock() self.media_item.plugin.status = PluginStatus.Active self.media_item.plugin.db_manager = MagicMock() self.media_item.plugin.db_manager.get_object_filtered = MagicMock() self.media_item.plugin.db_manager.get_object_filtered.return_value = None - with patch('openlp.plugins.custom.lib.db.CustomSlide'): + with patch('openlp.plugins.custom.lib.mediaitem.CustomSlide'): # WHEN: I search for the custom in the database - item = self.media_item.service_load(service_item) + self.media_item.add_custom_from_service = False + self.media_item.create_from_service_item = MagicMock() + self.media_item.service_load(service_item) - # THEN: the processing should be ignored - self.assertEqual(item, None, 'The Service item is inactive so processing should be bypassed') + # THEN: the item should not be added to the database. + self.assertEqual(self.media_item.create_from_service_item.call_count, 0, + 'The item should not have been added to the database') + + def service_load_basic_custom_true_test(self): + """ + Test the service load in custom with a default service item and a requirement to add to the database + """ + # GIVEN: An empty Service Item and an active plugin + service_item = ServiceItem(None) + service_item.raw_footer = FOOTER + self.media_item.plugin = MagicMock() + self.media_item.plugin.status = PluginStatus.Active + self.media_item.plugin.db_manager = MagicMock() + self.media_item.plugin.db_manager.get_object_filtered = MagicMock() + self.media_item.plugin.db_manager.get_object_filtered.return_value = None + + with patch('openlp.plugins.custom.lib.mediaitem.CustomSlide'): + # WHEN: I search for the custom in the database + self.media_item.add_custom_from_service = True + self.media_item.create_from_service_item = MagicMock() + self.media_item.service_load(service_item) + + # THEN: the item should not be added to the database. + self.assertEqual(self.media_item.create_from_service_item.call_count, 1, + 'The item should have been added to the database') \ No newline at end of file From 93d0314b7e493e22602395d9a1b1493fbe715a08 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Mon, 22 Dec 2014 17:34:43 +0000 Subject: [PATCH 14/14] General fixes --- openlp/core/common/uistrings.py | 2 +- openlp/plugins/custom/lib/mediaitem.py | 2 +- openlp/plugins/media/lib/mediaitem.py | 8 +-- openlp/plugins/remotes/lib/httprouter.py | 6 +- openlp/plugins/songs/forms/editsongform.py | 10 +++- .../openlp_core_utils/test_utils.py | 2 +- .../openlp_plugins/songs/test_editsongform.py | 57 +++++++++++++++++++ 7 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 tests/functional/openlp_plugins/songs/test_editsongform.py diff --git a/openlp/core/common/uistrings.py b/openlp/core/common/uistrings.py index 6b952cc39..e9f0a78e7 100644 --- a/openlp/core/common/uistrings.py +++ b/openlp/core/common/uistrings.py @@ -115,7 +115,7 @@ class UiStrings(object): self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural') self.OLPV2 = translate('OpenLP.Ui', 'OpenLP 2') - self.OLPV2x = translate('OpenLP.Ui', 'OpenLP 2.1') + self.OLPV2x = translate('OpenLP.Ui', 'OpenLP 2.2') self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?') self.OpenService = translate('OpenLP.Ui', 'Open service.') self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop') diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 9ecfac779..46e412b10 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -96,7 +96,6 @@ class CustomMediaItem(MediaManagerItem): def retranslateUi(self): """ - """ self.search_text_label.setText('%s:' % UiStrings().Search) self.search_text_button.setText(UiStrings().Search) @@ -134,6 +133,7 @@ class CustomMediaItem(MediaManagerItem): # Called to redisplay the custom list screen edith from a search # or from the exit of the Custom edit dialog. If remote editing is # active trigger it and clean up so it will not update again. + self.check_search_result() def on_new_click(self): """ diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 679180c15..7b95bdd4b 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -29,7 +29,6 @@ import logging import os -from datetime import time from PyQt4 import QtCore, QtGui @@ -126,18 +125,18 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): Adds buttons to the start of the header bar. """ if 'vlc' in get_media_players()[0]: - diable_optical_button_text = False + disable_optical_button_text = False optical_button_text = translate('MediaPlugin.MediaItem', 'Load CD/DVD') optical_button_tooltip = translate('MediaPlugin.MediaItem', 'Load CD/DVD') else: - diable_optical_button_text = True + disable_optical_button_text = True optical_button_text = translate('MediaPlugin.MediaItem', 'Load CD/DVD') optical_button_tooltip = translate('MediaPlugin.MediaItem', 'Load CD/DVD - only supported when VLC is installed and enabled') self.load_optical = self.toolbar.add_toolbar_action('load_optical', icon=OPTICAL_ICON, text=optical_button_text, tooltip=optical_button_tooltip, triggers=self.on_load_optical) - if diable_optical_button_text: + if disable_optical_button_text: self.load_optical.setDisabled(True) def add_end_header_bar(self): @@ -282,7 +281,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): Initialize media item. """ self.list_view.clear() - self.list_view.setIconSize(QtCore.QSize(88, 50)) self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') check_directory_exists(self.service_path) self.load_list(Settings().value(self.settings_section + '/media files')) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 1313c9f9c..a031b3a2b 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -306,9 +306,9 @@ class HttpRouter(RegistryProperties): Translate various strings in the mobile app. """ self.template_vars = { - 'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'), - 'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'), - 'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'), + 'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.2 Remote'), + 'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.2 Stage View'), + 'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.2 Live View'), 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 624b409ec..4525951d6 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -265,7 +265,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): return False return True - def _validate_tags(self, tags): + def _validate_tags(self, tags, first_time=True): """ Validates a list of tags Deletes the first affiliated tag pair which is located side by side in the list @@ -277,6 +277,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): :param tags: A list of tags :return: True if the function can't find any mismatched tags. Else False. """ + if first_time: + fixed_tags = [] + for i in range(len(tags)): + if tags[i] != '{br}': + fixed_tags.append(tags[i]) + tags = fixed_tags if len(tags) == 0: return True if len(tags) % 2 != 0: @@ -284,7 +290,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): for i in range(len(tags)-1): if tags[i+1] == "{/" + tags[i][1:]: del tags[i:i+2] - return self._validate_tags(tags) + return self._validate_tags(tags, False) return False def _process_lyrics(self): diff --git a/tests/functional/openlp_core_utils/test_utils.py b/tests/functional/openlp_core_utils/test_utils.py index de3e68791..5bd742266 100644 --- a/tests/functional/openlp_core_utils/test_utils.py +++ b/tests/functional/openlp_core_utils/test_utils.py @@ -382,7 +382,7 @@ class TestUtils(TestCase): mocked_page_object = MagicMock() mock_urlopen.return_value = mocked_page_object fake_url = 'this://is.a.fake/url' - user_agent_header = ('User-Agent', 'OpenLP/2.1.0') + user_agent_header = ('User-Agent', 'OpenLP/2.2.0') # WHEN: The get_web_page() method is called returned_page = get_web_page(fake_url, header=user_agent_header) diff --git a/tests/functional/openlp_plugins/songs/test_editsongform.py b/tests/functional/openlp_plugins/songs/test_editsongform.py new file mode 100644 index 000000000..48cd127bf --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_editsongform.py @@ -0,0 +1,57 @@ +""" +This module contains tests for the lib submodule of the Songs plugin. +""" +from unittest import TestCase + +from PyQt4 import QtCore, QtGui + +from openlp.core.common import Registry, Settings +from openlp.core.lib import ServiceItem +from openlp.plugins.songs.forms.editsongform import EditSongForm +from openlp.plugins.songs.lib.db import AuthorType +from tests.functional import patch, MagicMock +from tests.helpers.testmixin import TestMixin + + +class TestEditSongForm(TestCase, TestMixin): + """ + Test the functions in the :mod:`lib` module. + """ + def setUp(self): + """ + Set up the components need for all tests. + """ + Registry.create() + Registry().register('service_list', MagicMock()) + Registry().register('main_window', MagicMock()) + with patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__', return_value=None): + self.edit_song_form = EditSongForm(None, MagicMock(), MagicMock()) + self.setup_application() + self.build_settings() + QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + self.destroy_settings() + + def validate_matching_tags_test(self): + # Given a set of tags + tags = ['{r}', '{/r}', '{bl}', '{/bl}', '{su}', '{/su}'] + + # WHEN we validate them + valid = self.edit_song_form._validate_tags(tags) + + # THEN they should be valid + self.assertTrue(valid, "The tags list should be valid") + + def validate_nonmatching_tags_test(self): + # Given a set of tags + tags = ['{r}', '{/r}', '{bl}', '{/bl}', '{br}', '{su}', '{/su}'] + + # WHEN we validate them + valid = self.edit_song_form._validate_tags(tags) + + # THEN they should be valid + self.assertTrue(valid, "The tags list should be valid")