diff --git a/openlp/core/app.py b/openlp/core/app.py index 7dafaaf3c..22d0f8195 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -101,7 +101,7 @@ class OpenLP(QtWidgets.QApplication): ftw.initialize(screens) if ftw.exec() == QtWidgets.QDialog.Accepted: Settings().setValue('core/has run wizard', True) - elif ftw.was_cancelled: + else: QtCore.QCoreApplication.exit() sys.exit() # Correct stylesheet bugs diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index c9a555a55..1231cb7be 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -158,16 +158,20 @@ def get_web_page(url, headers=None, update_openlp=False, proxy=None): return response.text -def get_url_file_size(url): +def get_url_file_size(url, proxy=None): """ Get the size of a file. :param url: The URL of the file we want to download. + :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types + as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'} """ retries = 0 + if not isinstance(proxy, dict): + proxy = get_proxy_settings(mode=proxy) while True: try: - response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True) + response = requests.head(url, proxies=proxy, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True) return int(response.headers['Content-Length']) except OSError: if retries > CONNECTION_RETRIES: @@ -178,7 +182,7 @@ def get_url_file_size(url): continue -def download_file(update_object, url, file_path, sha256=None): +def download_file(update_object, url, file_path, sha256=None, proxy=None): """" Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any point. Returns False on download error. @@ -187,15 +191,19 @@ def download_file(update_object, url, file_path, sha256=None): :param url: URL to download :param file_path: Destination file :param sha256: The check sum value to be checked against the download value + :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types + as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'} """ block_count = 0 block_size = 4096 retries = 0 + if not isinstance(proxy, dict): + proxy = get_proxy_settings(mode=proxy) log.debug('url_get_file: %s', url) while retries < CONNECTION_RETRIES: try: with file_path.open('wb') as saved_file: - response = requests.get(url, timeout=float(CONNECTION_TIMEOUT), stream=True) + response = requests.get(url, proxies=proxy, timeout=float(CONNECTION_TIMEOUT), stream=True) if sha256: hasher = hashlib.sha256() # Download until finished or canceled. @@ -244,21 +252,21 @@ class DownloadWorker(ThreadWorker): """ self._base_url = base_url self._file_name = file_name - self._download_cancelled = False + self.was_cancelled = False super().__init__() def start(self): """ Download the url to the temporary directory """ - if self._download_cancelled: + if self.was_cancelled: self.quit.emit() return try: dest_path = Path(gettempdir()) / 'openlp' / self._file_name url = '{url}{name}'.format(url=self._base_url, name=self._file_name) is_success = download_file(self, url, dest_path) - if is_success and not self._download_cancelled: + if is_success and not self.was_cancelled: self.download_succeeded.emit(dest_path) else: self.download_failed.emit() @@ -273,4 +281,4 @@ class DownloadWorker(ThreadWorker): """ A slot to allow the download to be cancelled from outside of the thread """ - self._download_cancelled = True + self.was_cancelled = True diff --git a/openlp/core/threading.py b/openlp/core/threading.py index 502598609..fb51674dd 100644 --- a/openlp/core/threading.py +++ b/openlp/core/threading.py @@ -76,11 +76,11 @@ def get_thread_worker(thread_name): Get the worker by the thread name :param str thread_name: The name of the thread - :returns: The worker for this thread name + :returns: The worker for this thread name, or None """ thread_info = Registry().get('application').worker_threads.get(thread_name) if not thread_info: - raise KeyError('No thread named "{}" exists'.format(thread_name)) + return return thread_info.get('worker') diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index d5f48e2d7..0826a2081 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -46,6 +46,7 @@ from openlp.core.lib.ui import critical_error_message_box from openlp.core.threading import get_thread_worker, is_thread_finished, run_thread from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard from openlp.core.ui.icons import UiIcons +from openlp.core.widgets.widgets import ProxyDialog log = logging.getLogger(__name__) @@ -91,7 +92,7 @@ class ThemeListWidgetItem(QtWidgets.QListWidgetItem): class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ - This is the Theme Import Wizard, which allows easy creation and editing of OpenLP themes. + This is the FirstTimeWizard, designed to help new users to get up and running quickly. """ log.info('ThemeWizardForm loaded') @@ -103,6 +104,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): self.web_access = True self.web = '' self.setup_ui(self) + self.customButtonClicked.connect(self._on_custom_button_clicked) self.themes_list_widget.itemSelectionChanged.connect(self.on_themes_list_widget_selection_changed) self.themes_deselect_all_button.clicked.connect(self.themes_list_widget.clearSelection) self.themes_select_all_button.clicked.connect(self.themes_list_widget.selectAll) @@ -111,13 +113,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ Returns the id of the next FirstTimePage to go to based on enabled plugins """ - if FirstTimePage.ScreenConfig < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): + if FirstTimePage.Download < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked(): # If the songs plugin is enabled then go to the songs page return FirstTimePage.Songs - elif FirstTimePage.ScreenConfig < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked(): + elif FirstTimePage.Download < 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.ScreenConfig < self.currentId() < FirstTimePage.Themes: + elif FirstTimePage.Download < 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: @@ -133,9 +135,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): if not self.web_access: return FirstTimePage.NoInternet else: - return FirstTimePage.Plugins - elif self.currentId() == FirstTimePage.Plugins: - return self.get_next_page_id() + return FirstTimePage.Songs elif self.currentId() == FirstTimePage.Progress: return -1 elif self.currentId() == FirstTimePage.NoInternet: @@ -147,7 +147,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): Run the wizard. """ self.set_defaults() - return QtWidgets.QWizard.exec(self) + return super().exec() def initialize(self, screens): """ @@ -227,17 +227,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ self.restart() self.web = 'https://get.openlp.org/ftw/' - 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.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked) self.currentIdChanged.connect(self.on_current_id_changed) Registry().register_function('config_screen_changed', self.screen_selection_widget.load) - self.no_internet_finish_button.setVisible(False) - self.no_internet_cancel_button.setVisible(False) # Check if this is a re-run of the wizard. self.has_run_wizard = Settings().value('core/has run wizard') create_paths(Path(gettempdir(), 'openlp')) self.theme_combo_box.clear() + self.button(QtWidgets.QWizard.CustomButton1).setVisible(False) 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()) @@ -260,57 +256,85 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ Detects Page changes and updates as appropriate. """ - # Keep track of the page we are at. Triggering "Cancel" causes page_id to be a -1. + back_button = self.button(QtWidgets.QWizard.BackButton) + cancel_button = self.button(QtWidgets.QWizard.CancelButton) + internet_settings_button = self.button(QtWidgets.QWizard.CustomButton1) + next_button = self.button(QtWidgets.QWizard.NextButton) + back_button.setVisible(True) + next_button.setVisible(True) + internet_settings_button.setVisible(False) self.application.process_events() - if page_id != -1: - self.last_id = page_id - 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) + if page_id == FirstTimePage.SampleOption: + internet_settings_button.setVisible(True) + elif page_id == FirstTimePage.Download: + back_button.setVisible(False) + next_button.setVisible(False) 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.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.no_internet_cancel_button.setVisible(False) - else: - self.no_internet_cancel_button.setVisible(True) - elif page_id == FirstTimePage.Plugins: - self.back_button.setVisible(False) + next_button.setVisible(False) + cancel_button.setVisible(False) + internet_settings_button.setVisible(True) elif page_id == FirstTimePage.Progress: + back_button.setVisible(False) + next_button.setVisible(False) self.application.set_busy_cursor() self._pre_wizard() self._perform_wizard() self._post_wizard() self.application.set_normal_cursor() - def on_cancel_button_clicked(self): + def accept(self): """ - Process the triggering of the cancel button. + Called when the user clicks 'Finish'. Reimplement it to to save the plugin status + + :rtype: None + """ + self._set_plugin_status(self.songs_check_box, 'songs/status') + self._set_plugin_status(self.bible_check_box, 'bibles/status') + self._set_plugin_status(self.presentation_check_box, 'presentations/status') + self._set_plugin_status(self.image_check_box, 'images/status') + self._set_plugin_status(self.media_check_box, 'media/status') + self._set_plugin_status(self.custom_check_box, 'custom/status') + self._set_plugin_status(self.song_usage_check_box, 'songusage/status') + self._set_plugin_status(self.alert_check_box, 'alerts/status') + self.screen_selection_widget.save() + if self.theme_combo_box.currentIndex() != -1: + Settings().setValue('themes/global theme', self.theme_combo_box.currentText()) + super().accept() + + def reject(self): + """ + Called when the user clicks the cancel button. Reimplement it to clean up the threads. + + :rtype: None """ self.was_cancelled = True - if self.thumbnail_download_threads: # TODO: Use main thread list - for thread_name in self.thumbnail_download_threads: - worker = get_thread_worker(thread_name) - if worker: - worker.cancel_download() + for thread_name in self.thumbnail_download_threads: + worker = get_thread_worker(thread_name) + if worker: + worker.cancel_download() # Was the thread created. if self.thumbnail_download_threads: while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]): time.sleep(0.1) self.application.set_normal_cursor() + super().reject() + + def _on_custom_button_clicked(self, which): + """ + Slot to handle the a click on one of the wizards custom buttons. + + :param int QtWidgets.QWizard which: The button pressed + :rtype: None + """ + # Internet settings button + if which == QtWidgets.QWizard.CustomButton1: + proxy_dialog = ProxyDialog(self) + proxy_dialog.retranslate_ui() + proxy_dialog.exec() def on_themes_list_widget_selection_changed(self): """ @@ -330,23 +354,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): elif not item.isSelected() and cbox_index != -1: self.theme_combo_box.removeItem(cbox_index) - def on_no_internet_finish_button_clicked(self): - """ - Process the triggering of the "Finish" button on the No Internet page. - """ - self.application.set_busy_cursor() - self._perform_wizard() - self.application.set_normal_cursor() - Settings().setValue('core/has run wizard', True) - self.close() - - def on_no_internet_cancel_button_clicked(self): - """ - Process the triggering of the "Cancel" button on the No Internet page. - """ - self.was_cancelled = True - self.close() - def update_progress(self, count, block_size): """ Calculate and display the download progress. This method is called by download_file(). @@ -373,7 +380,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): Prepare the UI for the process. """ self.max_progress = 0 - self.finish_button.setVisible(False) + self.button(QtWidgets.QWizard.FinishButton).setEnabled(False) self.application.process_events() try: # Loop through the songs list and increase for each selected item @@ -428,58 +435,36 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ Clean up the UI after the process has finished. """ + complete_str = '' if self.max_progress: self.progress_bar.setValue(self.progress_bar.maximum()) if self.has_run_wizard: text = translate('OpenLP.FirstTimeWizard', - 'Download complete. Click the {button} button to return to OpenLP.' - ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton))) - self.progress_label.setText(text) + 'Download complete. Click the \'{finish_button}\' button to return to OpenLP.') else: text = translate('OpenLP.FirstTimeWizard', - 'Download complete. Click the {button} button to start OpenLP.' - ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton))) - self.progress_label.setText(text) + 'Download complete. Click the \'{finish_button}\' button to start OpenLP.') else: if self.has_run_wizard: - text = translate('OpenLP.FirstTimeWizard', - 'Click the {button} button to return to OpenLP.' - ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton))) - self.progress_label.setText(text) + text = translate('OpenLP.FirstTimeWizard', 'Click the \'{finish_button}\' button to return to OpenLP.') else: - text = translate('OpenLP.FirstTimeWizard', - 'Click the {button} button to start OpenLP.' - ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton))) - self.progress_label.setText(text) - self.finish_button.setVisible(True) - self.finish_button.setEnabled(True) - self.cancel_button.setVisible(False) - self.next_button.setVisible(False) + text = translate('OpenLP.FirstTimeWizard', 'Click the \'{finish_button}\' button to start OpenLP.') + self.progress_label.setText(text.format(finish_button=self.finish_button_text)) + self.button(QtWidgets.QWizard.FinishButton).setEnabled(True) + self.button(QtWidgets.QWizard.CancelButton).setVisible(False) self.application.process_events() def _perform_wizard(self): """ Run the tasks in the wizard. """ - # Set plugin states - self._increment_progress_bar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...')) - self._set_plugin_status(self.songs_check_box, 'songs/status') - self._set_plugin_status(self.bible_check_box, 'bibles/status') - self._set_plugin_status(self.presentation_check_box, 'presentations/status') - self._set_plugin_status(self.image_check_box, 'images/status') - self._set_plugin_status(self.media_check_box, 'media/status') - self._set_plugin_status(self.custom_check_box, 'custom/status') - self._set_plugin_status(self.song_usage_check_box, 'songusage/status') - self._set_plugin_status(self.alert_check_box, 'alerts/status') + if self.web_access: if not self._download_selected(): critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), translate('OpenLP.FirstTimeWizard', 'There was a connection problem while ' 'downloading, so further downloads will be skipped. Try to re-run ' 'the First Time Wizard later.')) - self.screen_selection_widget.save() - if self.theme_combo_box.currentIndex() != -1: - Settings().setValue('themes/global theme', self.theme_combo_box.currentText()) def _download_selected(self): """ diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 37b9389a7..f93ce02f3 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -39,14 +39,15 @@ class FirstTimePage(object): An enumeration class with each of the pages of the wizard. """ Welcome = 0 - ScreenConfig = 1 - Download = 2 - NoInternet = 3 - Plugins = 4 - Songs = 5 - Bibles = 6 - Themes = 7 - Progress = 8 + Plugins = 1 + ScreenConfig = 2 + SampleOption = 3 + Download = 4 + NoInternet = 5 + Songs = 6 + Bibles = 7 + Themes = 8 + Progress = 9 class ThemeListWidget(QtWidgets.QListWidget): @@ -97,20 +98,13 @@ class UiFirstTimeWizard(object): first_time_wizard.resize(550, 386) first_time_wizard.setModal(True) first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage | - QtWidgets.QWizard.NoBackButtonOnLastPage | QtWidgets.QWizard.HaveCustomButton1 | - QtWidgets.QWizard.HaveCustomButton2) + QtWidgets.QWizard.NoBackButtonOnLastPage | QtWidgets.QWizard.HaveCustomButton1) if is_macosx(): # pragma: nocover first_time_wizard.setPixmap(QtWidgets.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png')) first_time_wizard.resize(634, 386) else: first_time_wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle) - self.finish_button = self.button(QtWidgets.QWizard.FinishButton) - self.no_internet_finish_button = self.button(QtWidgets.QWizard.CustomButton1) - self.cancel_button = self.button(QtWidgets.QWizard.CancelButton) - self.no_internet_cancel_button = self.button(QtWidgets.QWizard.CustomButton2) - self.next_button = self.button(QtWidgets.QWizard.NextButton) - self.back_button = self.button(QtWidgets.QWizard.BackButton) add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp') # The screen config page self.screen_page = QtWidgets.QWizardPage() @@ -121,6 +115,18 @@ class UiFirstTimeWizard(object): self.screen_selection_widget.load() self.screen_page_layout.addRow(self.screen_selection_widget) first_time_wizard.setPage(FirstTimePage.ScreenConfig, self.screen_page) + # Download Samples page + self.resource_page = QtWidgets.QWizardPage() + self.resource_page.setObjectName('resource_page') + self.resource_page.setFinalPage(True) + self.resource_layout = QtWidgets.QVBoxLayout(self.resource_page) + self.resource_layout.setContentsMargins(50, 20, 50, 20) + self.resource_layout.setObjectName('resource_layout') + self.resource_label = QtWidgets.QLabel(self.resource_page) + self.resource_label.setObjectName('resource_label') + self.resource_label.setWordWrap(True) + self.resource_layout.addWidget(self.resource_label) + first_time_wizard.setPage(FirstTimePage.SampleOption, self.resource_page) # The download page self.download_page = QtWidgets.QWizardPage() self.download_page.setObjectName('download_page') @@ -134,6 +140,7 @@ class UiFirstTimeWizard(object): # The "you don't have an internet connection" page. self.no_internet_page = QtWidgets.QWizardPage() self.no_internet_page.setObjectName('no_internet_page') + self.no_internet_page.setFinalPage(True) self.no_internet_layout = QtWidgets.QVBoxLayout(self.no_internet_page) self.no_internet_layout.setContentsMargins(50, 30, 50, 40) self.no_internet_layout.setObjectName('no_internet_layout') @@ -242,27 +249,32 @@ class UiFirstTimeWizard(object): self.progress_bar.setObjectName('progress_bar') self.progress_layout.addWidget(self.progress_bar) first_time_wizard.setPage(FirstTimePage.Progress, self.progress_page) - self.retranslate_ui(first_time_wizard) + self.retranslate_ui() - def retranslate_ui(self, first_time_wizard): + def retranslate_ui(self): """ Translate the UI on the fly :param first_time_wizard: The wizard form """ - first_time_wizard.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard')) + self.finish_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)) + back_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.BackButton)) + next_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.NextButton)) + + self.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard')) text = translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard') - first_time_wizard.title_label.setText('{text}' - ''.format(text=text)) - button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.NextButton)) - first_time_wizard.information_label.setText( + self.title_label.setText('{text}'.format(text=text)) + self.information_label.setText( translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. ' - 'Click the {button} button below to start.').format(button=button)) + 'Click the \'{next_button}\' button below to start.' + ).format(next_button=next_button_text)) + self.setButtonText( + QtWidgets.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Internet Settings')) 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.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', 'Select parts of the program you wish to use')) self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'You can also change these settings after the Wizard.')) @@ -270,11 +282,10 @@ class UiFirstTimeWizard(object): self.screen_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Choose the main display screen for OpenLP.')) self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) - self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard', - 'Custom Slides – Easier to manage than songs and they have their own' - ' list of slides')) - self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', - 'Bibles – Import and show Bibles')) + self.custom_check_box.setText( + translate('OpenLP.FirstTimeWizard', + 'Custom Slides – Easier to manage than songs and they have their own list of slides')) + self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Bibles – Import and show Bibles')) self.image_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Images – Show images or replace background with them')) self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', @@ -283,22 +294,25 @@ class UiFirstTimeWizard(object): self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor')) self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Alerts – Display informative messages while showing other slides')) + self.resource_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Resource Data')) + self.resource_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Can OpenLP download some resource data?')) + self.resource_label.setText( + translate('OpenLP.FirstTimeWizard', + 'OpenLP has collected some resources that we have permission to distribute.\n\n' + 'If you would like to download some of these resources click the \'{next_button}\' button, ' + 'otherwise click the \'{finish_button}\' button.' + ).format(next_button=next_button_text, finish_button=self.finish_button_text)) self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection')) - self.no_internet_page.setSubTitle( - translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.')) - button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton)) - self.no_internet_text = translate('OpenLP.FirstTimeWizard', - 'No Internet connection was found. The First Time Wizard needs an Internet ' - 'connection in order to be able to download sample songs, Bibles and themes.' - ' Click the {button} button now to start OpenLP with initial settings and ' - 'no sample data.\n\nTo re-run the First Time Wizard and import this sample ' - 'data at a later time, check your Internet connection and re-run this ' - 'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.' - ).format(button=button) - button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.CancelButton)) - self.cancel_wizard_text = translate('OpenLP.FirstTimeWizard', - '\n\nTo cancel the First Time Wizard completely (and not start OpenLP), ' - 'click the {button} button now.').format(button=button) + self.no_internet_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Cannot connect to the internet.')) + self.no_internet_label.setText( + translate('OpenLP.FirstTimeWizard', + 'OpenLP could not connect to the internet to get information about the sample data available.\n\n' + 'Please check your internet connection. If your church uses a proxy server click the ' + '\'Internet Settings\' button below and enter the server details there.\n\nClick the ' + '\'{back_button}\' button to try again.\n\nIf you click the \'{finish_button}\' ' + 'button you can download the data at a later time by selecting \'Re-run First Time Wizard\' ' + 'from the \'Tools\' menu in OpenLP.' + ).format(back_button=back_button_text, finish_button=self.finish_button_text)) self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs')) self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.')) self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles')) @@ -310,13 +324,8 @@ class UiFirstTimeWizard(object): self.themes_select_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Select all')) self.themes_deselect_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Deselect all')) 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(QtWidgets.QWizard.CustomButton1, - clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton))) - first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton2, - clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.CancelButton))) + self.progress_page.setSubTitle( + translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded and OpenLP is configured.')) def on_projectors_check_box_clicked(self): # When clicking projectors_check box, change the visibility setting for Projectors panel. diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index dabfca79e..29b131378 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -681,8 +681,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert return first_run_wizard = FirstTimeForm(self) first_run_wizard.initialize(ScreenList()) - first_run_wizard.exec() - if first_run_wizard.was_cancelled: + if first_run_wizard.exec() == QtWidgets.QDialog.Rejected: return self.application.set_busy_cursor() self.first_time() diff --git a/openlp/core/widgets/widgets.py b/openlp/core/widgets/widgets.py index c7697927c..1a193fe08 100644 --- a/openlp/core/widgets/widgets.py +++ b/openlp/core/widgets/widgets.py @@ -150,6 +150,34 @@ class ProxyWidget(QtWidgets.QGroupBox): settings.setValue('advanced/proxy password', self.password_edit.text()) +class ProxyDialog(QtWidgets.QDialog): + """ + A basic dialog to show proxy settingd + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.layout = QtWidgets.QVBoxLayout(self) + self.proxy_widget = ProxyWidget(self) + self.layout.addWidget(self.proxy_widget) + self.button_box = \ + QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, self) + self.layout.addWidget(self.button_box) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + + def accept(self): + """ + Reimplement the the accept slot so that the ProxyWidget settings can be saved. + :rtype: None + """ + self.proxy_widget.save() + super().accept() + + def retranslate_ui(self): + self.proxy_widget.retranslate_ui() + self.setWindowTitle(translate('OpenLP.ProxyDialog', 'Proxy Server Settings')) + + class ScreenButton(QtWidgets.QPushButton): """ A special button class that holds the screen information about it diff --git a/openlp/plugins/songs/lib/importers/openlp.py b/openlp/plugins/songs/lib/importers/openlp.py index 361406bfd..f6ea3ac0a 100644 --- a/openlp/plugins/songs/lib/importers/openlp.py +++ b/openlp/plugins/songs/lib/importers/openlp.py @@ -106,7 +106,7 @@ class OpenLPSongImport(SongImport): pass # Check the file type - if not isinstance(self.import_source, str) or not self.import_source.endswith('.sqlite'): + if self.import_source.suffix != '.sqlite': self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport', 'Not a valid OpenLP 2 song database.')) return diff --git a/openlp/plugins/songs/reporting.py b/openlp/plugins/songs/reporting.py index 985b9bc12..35521dbe9 100644 --- a/openlp/plugins/songs/reporting.py +++ b/openlp/plugins/songs/reporting.py @@ -49,13 +49,6 @@ def report_song_list(): Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')), translate('SongPlugin.ReportSongList', 'CSV format (*.csv)')) - if report_file_path is None: - main_window.error_message( - translate('SongPlugin.ReportSongList', 'Output Path Not Selected'), - translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your report. \n' - 'Please select an existing path on your computer.') - ) - return report_file_path.with_suffix('.csv') Registry().get('application').set_busy_cursor() try: diff --git a/run_openlp.py b/run_openlp.py old mode 100755 new mode 100644 diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index bb16e6800..f059d893c 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -224,7 +224,7 @@ class TestHttpUtils(TestCase, TestMixin): file_size = get_url_file_size(fake_url) # THEN: The correct methods are called with the correct arguments and a web page is returned - mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, timeout=30.0) + mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, proxies=None, timeout=30.0) assert file_size == 100 @patch('openlp.core.common.httputils.requests') diff --git a/tests/functional/openlp_core/ui/test_firsttimeform.py b/tests/functional/openlp_core/ui/test_firsttimeform.py index 220e1fff6..e5d0ce550 100644 --- a/tests/functional/openlp_core/ui/test_firsttimeform.py +++ b/tests/functional/openlp_core/ui/test_firsttimeform.py @@ -27,6 +27,8 @@ import tempfile from unittest import TestCase from unittest.mock import MagicMock, call, patch, DEFAULT +from PyQt5 import QtWidgets + from openlp.core.common.path import Path from openlp.core.common.registry import Registry from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem @@ -120,10 +122,23 @@ class TestFirstTimeForm(TestCase, TestMixin): # THEN: The screens should be set up, and the default values initialised assert expected_screens == frw.screens, 'The screens should be correct' assert frw.web_access is True, 'The default value of self.web_access should be True' - assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False' assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty' assert frw.has_run_wizard is False, 'has_run_wizard should be False' + @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.exec') + def test_exec(self, mocked_qwizard_exec): + + # GIVEN: An instance of FirstTimeForm + frw = FirstTimeForm(None) + with patch.object(frw, 'set_defaults') as mocked_set_defaults: + + # WHEN: exec is called + frw.exec() + + # THEN: The wizard should be reset and the exec methon on the super class should have been called + mocked_set_defaults.assert_called_once() + mocked_qwizard_exec.assert_called_once() + def test_set_defaults(self): """ Test that the default values are set when set_defaults() is run @@ -134,8 +149,6 @@ class TestFirstTimeForm(TestCase, TestMixin): mocked_settings = MagicMock() mocked_settings.value.side_effect = lambda key: {'core/has run wizard': False}[key] 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(frw, 'theme_combo_box') as mocked_theme_combo_box, \ patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \ @@ -153,12 +166,8 @@ class TestFirstTimeForm(TestCase, TestMixin): # THEN: The default values should have been set mocked_restart.assert_called_once() assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set' - mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) - mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( - frw.on_no_internet_finish_button_clicked) mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed) mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load) - mocked_no_internet_finish_btn.setVisible.assert_called_once_with(False) mocked_settings.value.assert_has_calls([call('core/has run wizard')]) mocked_gettempdir.assert_called_once() mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) @@ -177,8 +186,6 @@ class TestFirstTimeForm(TestCase, TestMixin): mocked_settings.value.side_effect = \ lambda key: {'core/has run wizard': True, 'themes/global theme': 'Default Theme'}[key] 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(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \ patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT, @@ -200,12 +207,8 @@ class TestFirstTimeForm(TestCase, TestMixin): # THEN: The default values should have been set mocked_restart.assert_called_once() assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set' - mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) - mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( - frw.on_no_internet_finish_button_clicked) mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed) mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load) - mocked_no_internet_finish_btn.setVisible.assert_called_once_with(False) mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')]) mocked_gettempdir.assert_called_once() mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) @@ -219,12 +222,79 @@ class TestFirstTimeForm(TestCase, TestMixin): mocked_theme_combo_box.findText.assert_called_once_with('Default Theme') mocked_theme_combo_box.setCurrentIndex(3) + @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.accept') + @patch('openlp.core.ui.firsttimewizard.Settings') + def test_accept_method(self, mocked_settings, mocked_qwizard_accept): + """ + Test the FirstTimeForm.accept method + """ + # GIVEN: An instance of FirstTimeForm + frw = FirstTimeForm(None) + with patch.object(frw, '_set_plugin_status') as mocked_set_plugin_status, \ + patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT, + image_check_box=DEFAULT, media_check_box=DEFAULT, custom_check_box=DEFAULT, + song_usage_check_box=DEFAULT, alert_check_box=DEFAULT) as mocked_check_boxes, \ + patch.object(frw, 'screen_selection_widget') as mocked_screen_selection_widget: + + # WHEN: Calling accept + frw.accept() + + # THEN: The selected plugins should be enabled, the screen selection saved and the super method called + mocked_set_plugin_status.assert_has_calls([ + call(mocked_check_boxes['songs_check_box'], 'songs/status'), + call(mocked_check_boxes['bible_check_box'], 'bibles/status'), + call(mocked_check_boxes['presentation_check_box'], 'presentations/status'), + call(mocked_check_boxes['image_check_box'], 'images/status'), + call(mocked_check_boxes['media_check_box'], 'media/status'), + call(mocked_check_boxes['custom_check_box'], 'custom/status'), + call(mocked_check_boxes['song_usage_check_box'], 'songusage/status'), + call(mocked_check_boxes['alert_check_box'], 'alerts/status')]) + mocked_screen_selection_widget.save.assert_called_once() + mocked_qwizard_accept.assert_called_once() + + @patch('openlp.core.ui.firsttimewizard.Settings') + def test_accept_method_theme_not_selected(self, mocked_settings): + """ + Test the FirstTimeForm.accept method when there is no default theme selected + """ + # GIVEN: An instance of FirstTimeForm + frw = FirstTimeForm(None) + with patch.object(frw, '_set_plugin_status'), \ + patch.object(frw, 'screen_selection_widget'), \ + patch.object(frw, 'theme_combo_box', **{'currentIndex.return_value': '-1'}): + + # WHEN: Calling accept and the currentIndex method of the theme_combo_box returns -1 + frw.accept() + + # THEN: OpenLP should not try to save a theme name + mocked_settings.setValue.assert_not_called() + + @patch('openlp.core.ui.firsttimeform.Settings') + def test_accept_method_theme_selected(self, mocked_settings): + """ + Test the FirstTimeForm.accept method when a default theme is selected + """ + # GIVEN: An instance of FirstTimeForm + frw = FirstTimeForm(None) + with patch.object(frw, '_set_plugin_status'), \ + patch.object(frw, 'screen_selection_widget'), \ + patch.object( + frw, 'theme_combo_box', **{'currentIndex.return_value': 0, 'currentText.return_value': 'Test Item'}): + + # WHEN: Calling accept and the currentIndex method of the theme_combo_box returns 0 + frw.accept() + + # THEN: The 'currentItem' in the combobox should have been set as the default theme. + mocked_settings().setValue.assert_called_once_with('themes/global theme', 'Test Item') + + @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.reject') @patch('openlp.core.ui.firsttimeform.time') @patch('openlp.core.ui.firsttimeform.get_thread_worker') @patch('openlp.core.ui.firsttimeform.is_thread_finished') - def test_on_cancel_button_clicked(self, mocked_is_thread_finished, mocked_get_thread_worker, mocked_time): + def test_reject_method( + self, mocked_is_thread_finished, mocked_get_thread_worker, mocked_time, mocked_qwizard_reject): """ - Test that the cancel button click slot shuts down the threads correctly + Test that the reject method shuts down the threads correctly """ # GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff mocked_worker = MagicMock() @@ -235,17 +305,47 @@ class TestFirstTimeForm(TestCase, TestMixin): frw.thumbnail_download_threads = ['test_thread'] with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: - # WHEN: on_cancel_button_clicked() is called - frw.on_cancel_button_clicked() + # WHEN: the reject method is called + frw.reject() # THEN: The right things should be called in the right order - assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True' mocked_get_thread_worker.assert_called_once_with('test_thread') mocked_worker.cancel_download.assert_called_once() mocked_is_thread_finished.assert_called_with('test_thread') assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice' mocked_time.sleep.assert_called_once_with(0.1) - mocked_set_normal_cursor.assert_called_once_with() + mocked_set_normal_cursor.assert_called_once() + mocked_qwizard_reject.assert_called_once() + + @patch('openlp.core.ui.firsttimeform.ProxyDialog') + def test_on_custom_button_clicked(self, mocked_proxy_dialog): + """ + Test _on_custom_button when it is called whe the 'internet settings' (CustomButton1) button is not clicked. + """ + # GIVEN: An instance of the FirstTimeForm + frw = FirstTimeForm(None) + + # WHEN: Calling _on_custom_button_clicked with a different button to the 'internet settings button. + frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton2) + + # THEN: The ProxyDialog should not be shown. + mocked_proxy_dialog.assert_not_called() + + @patch('openlp.core.ui.firsttimeform.ProxyDialog') + def test_on_custom_button_clicked_internet_settings(self, mocked_proxy_dialog): + """ + Test _on_custom_button when it is called when the 'internet settings' (CustomButton1) button is clicked. + """ + # GIVEN: An instance of the FirstTimeForm + frw = FirstTimeForm(None) + + # WHEN: Calling _on_custom_button_clicked with the constant for the 'internet settings' button (CustomButton1) + frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton1) + + # THEN: The ProxyDialog should be shown. + mocked_proxy_dialog.assert_called_with(frw) + mocked_proxy_dialog().retranslate_ui.assert_called_once() + mocked_proxy_dialog().exec.assert_called_once() @patch('openlp.core.ui.firsttimeform.critical_error_message_box') def test__parse_config_invalid_config(self, mocked_critical_error_message_box):