diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 37a713d38..503aadee3 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_cancelled: + QtCore.QCoreApplication.exit() + sys.exit() # Correct stylesheet bugs application_stylesheet = '' if not Settings().value('advanced/alternate rows'): 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/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 3f1121542..6a77e5bc9 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 @@ -51,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. + This thread downloads a theme's screenshot """ + screenshot_downloaded = QtCore.pyqtSignal(str, str) + finished = QtCore.pyqtSignal() + + def __init__(self, themes_url, title, filename, screenshot): + """ + Set up the worker object + """ + 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.parent().config.get('themes', 'files') - themes = themes.split(',') - config = self.parent().config - for theme in themes: - # Stop if the wizard has been cancelled. - if self.parent().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), - 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) + 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(self.title, self.filename) + except: + log.exception('Unable to download screenshot') + finally: + self.finished.emit() + + @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): @@ -88,22 +105,25 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): Create and set up the first time wizard. """ super(FirstTimeForm, self).__init__(parent) + self.web_access = True + self.web = '' self.setup_ui(self) def get_next_page_id(self): """ 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): @@ -111,18 +131,20 @@ 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 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: 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,11 +168,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): :param screens: The screens detected by OpenLP """ self.screens = screens + self.was_cancelled = False + self.theme_screenshot_threads = [] + self.theme_screenshot_workers = [] + self.has_run_wizard = False + + 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_access = False - self.web = 'http://openlp.org/files/frw/' self.config = ConfigParser() user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() + self.application.process_events() web_config = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent)) if web_config: files = web_config.read() @@ -165,24 +196,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): log.debug('A problem occured while parsing the downloaded config file') trace_error_handler(log) self.update_screen_list_combo() - self.was_download_cancelled = False - self.theme_screenshot_thread = None - self.has_run_wizard = False + self.application.process_events() 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') 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()) @@ -199,6 +214,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): 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) @@ -208,11 +224,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]) @@ -220,9 +238,38 @@ 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_thread = ThemeScreenshotThread(self) - self.theme_screenshot_thread.start() + 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') + 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')) def update_screen_list_combo(self): """ @@ -241,12 +288,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()): @@ -266,15 +321,12 @@ 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.Plugins: + self.back_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() @@ -284,17 +336,28 @@ 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): - QtCore.QCoreApplication.exit() - sys.exit() - 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) # 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() + 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. @@ -321,7 +384,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT) 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 @@ -341,7 +404,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties): continue break # Delete file if cancelled, it may be a partial file. - if self.was_download_cancelled: + if self.was_cancelled: os.remove(f_path) return True @@ -354,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): diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 4e83a3e74..964fdd7a6 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,27 @@ 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) + 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 +142,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 +232,11 @@ 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', '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')) @@ -257,5 +273,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')) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index d654ac172..abf3b89eb 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/openlp/core/ui/projector/sourceselectform.py b/openlp/core/ui/projector/sourceselectform.py index 6e7ab7f76..244b7adef 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/ui/projector/sourceselectform.py @@ -152,16 +152,19 @@ 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(translate('OpenLP.SourceSelectForm', + '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(translate('OpenLP.SourceSelectForm', + '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(translate('OpenLP.SourceSelectForm', + 'Discard changes and reset to previous user-defined text')) elif bar.standardButton(button) == QDialogButtonBox.Ok: - tip = "Save changes and return to OpenLP" + button.setToolTip(translate('OpenLP.SourceSelectForm', + 'Save changes and return to OpenLP')) else: - tip = "" - button.setToolTip(tip) + log.debug('No tooltip for button {}'.format(button.text())) class FingerTabBarWidget(QTabBar): @@ -237,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')) @@ -286,6 +290,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 +305,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 +327,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: @@ -331,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_() @@ -382,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) @@ -418,6 +430,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 +443,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 +466,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: @@ -462,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_() diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 9ecfac779..5f8836af7 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): """ @@ -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/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_ui/test_firsttimeform.py b/tests/functional/openlp_core_ui/test_firsttimeform.py index ead9ef4cf..07544ab7b 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 @@ -71,55 +72,117 @@ 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 + self.app.process_events = 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) + + 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() def broken_config_test(self): """ @@ -128,10 +191,11 @@ class TestFirstTimeForm(TestCase, TestMixin): # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: first_time_form = FirstTimeForm(None) + first_time_form.initialize(MagicMock()) mocked_get_web_page.return_value.read.return_value = FAKE_BROKEN_CONFIG - # WHEN: The First Time Wizard is initialised - first_time_form.initialize(MagicMock()) + # WHEN: The First Time Wizard is downloads the config file + first_time_form._download_index() # THEN: The First Time Form should not have web access self.assertFalse(first_time_form.web_access, 'There should not be web access with a broken config file') @@ -143,10 +207,11 @@ class TestFirstTimeForm(TestCase, TestMixin): # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked invalid config file with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: first_time_form = FirstTimeForm(None) + first_time_form.initialize(MagicMock()) mocked_get_web_page.return_value.read.return_value = FAKE_INVALID_CONFIG - # WHEN: The First Time Wizard is initialised - first_time_form.initialize(MagicMock()) + # WHEN: The First Time Wizard is downloads the config file + first_time_form._download_index() # THEN: The First Time Form should not have web access self.assertFalse(first_time_form.web_access, 'There should not be web access with an invalid config file') 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/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..ef04dfb4e --- /dev/null +++ b/tests/functional/openlp_plugins/custom/test_mediaitem.py @@ -0,0 +1,101 @@ +""" +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 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): + """ + 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_false_test(self): + """ + 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.mediaitem.CustomSlide'): + # WHEN: I search for the custom in the database + 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 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 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") 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, 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..ed005a7d9 --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_projectorsourceform.py @@ -0,0 +1,79 @@ +# -*- 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")