Merge lp:~phill-ridout/openlp/ftw-refactors into lp:openlp

Proposed by Phill
Status: Superseded
Proposed branch: lp:~phill-ridout/openlp/ftw-refactors
Merge into: lp:openlp
Diff against target: 1039 lines (+328/-211)
14 files modified
openlp/core/app.py (+1/-1)
openlp/core/common/httputils.py (+16/-8)
openlp/core/display/render.py (+2/-2)
openlp/core/threading.py (+2/-2)
openlp/core/ui/firsttimeform.py (+78/-94)
openlp/core/ui/firsttimewizard.py (+64/-55)
openlp/core/ui/mainwindow.py (+1/-2)
openlp/core/widgets/widgets.py (+28/-0)
openlp/plugins/songs/lib/importers/openlp.py (+1/-1)
openlp/plugins/songs/reporting.py (+0/-7)
tests/functional/openlp_core/common/test_httputils.py (+1/-1)
tests/functional/openlp_core/test_threading.py (+5/-9)
tests/functional/openlp_core/ui/test_firsttimeform.py (+123/-23)
tests/functional/openlp_plugins/songs/test_openlpimporter.py (+6/-6)
To merge this branch: bzr merge lp:~phill-ridout/openlp/ftw-refactors
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Tomas Groth Pending
Review via email: mp+364184@code.launchpad.net

This proposal supersedes a proposal from 2019-03-08.

This proposal has been superseded by a proposal from 2019-03-08.

Commit message

Add proxy settings to ftw. Option to skip sample data

Description of the change

Add option to skip downloading.
Add dialog for proxy settings to FTW
FTW refactors.
General fixes.

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/89/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/90/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/41/ for more details

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Some code style needs fixing - see link above

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/49/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/50/ for more details

2850. By Phill

PEP fixes

Revision history for this message
Tim Bentley (trb143) :
review: Approve
2851. By Phill

mac test fix?

2852. By Phill

Test fixes

2853. By Phill

HEAD

2854. By Phill

Test fix

2855. By Phill

PEP8

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/app.py'
--- openlp/core/app.py 2019-02-14 15:09:09 +0000
+++ openlp/core/app.py 2019-03-08 21:25:50 +0000
@@ -101,7 +101,7 @@
101 ftw.initialize(screens)101 ftw.initialize(screens)
102 if ftw.exec() == QtWidgets.QDialog.Accepted:102 if ftw.exec() == QtWidgets.QDialog.Accepted:
103 Settings().setValue('core/has run wizard', True)103 Settings().setValue('core/has run wizard', True)
104 elif ftw.was_cancelled:104 else:
105 QtCore.QCoreApplication.exit()105 QtCore.QCoreApplication.exit()
106 sys.exit()106 sys.exit()
107 # Correct stylesheet bugs107 # Correct stylesheet bugs
108108
=== modified file 'openlp/core/common/httputils.py'
--- openlp/core/common/httputils.py 2019-02-21 21:29:00 +0000
+++ openlp/core/common/httputils.py 2019-03-08 21:25:50 +0000
@@ -158,16 +158,20 @@
158 return response.text158 return response.text
159159
160160
161def get_url_file_size(url):161def get_url_file_size(url, proxy=None):
162 """162 """
163 Get the size of a file.163 Get the size of a file.
164164
165 :param url: The URL of the file we want to download.165 :param url: The URL of the file we want to download.
166 :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types
167 as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'}
166 """168 """
167 retries = 0169 retries = 0
170 if not isinstance(proxy, dict):
171 proxy = get_proxy_settings(mode=proxy)
168 while True:172 while True:
169 try:173 try:
170 response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True)174 response = requests.head(url, proxies=proxy, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True)
171 return int(response.headers['Content-Length'])175 return int(response.headers['Content-Length'])
172 except OSError:176 except OSError:
173 if retries > CONNECTION_RETRIES:177 if retries > CONNECTION_RETRIES:
@@ -178,7 +182,7 @@
178 continue182 continue
179183
180184
181def download_file(update_object, url, file_path, sha256=None):185def download_file(update_object, url, file_path, sha256=None, proxy=None):
182 """"186 """"
183 Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any187 Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
184 point. Returns False on download error.188 point. Returns False on download error.
@@ -187,15 +191,19 @@
187 :param url: URL to download191 :param url: URL to download
188 :param file_path: Destination file192 :param file_path: Destination file
189 :param sha256: The check sum value to be checked against the download value193 :param sha256: The check sum value to be checked against the download value
194 :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types
195 as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'}
190 """196 """
191 block_count = 0197 block_count = 0
192 block_size = 4096198 block_size = 4096
193 retries = 0199 retries = 0
200 if not isinstance(proxy, dict):
201 proxy = get_proxy_settings(mode=proxy)
194 log.debug('url_get_file: %s', url)202 log.debug('url_get_file: %s', url)
195 while retries < CONNECTION_RETRIES:203 while retries < CONNECTION_RETRIES:
196 try:204 try:
197 with file_path.open('wb') as saved_file:205 with file_path.open('wb') as saved_file:
198 response = requests.get(url, timeout=float(CONNECTION_TIMEOUT), stream=True)206 response = requests.get(url, proxies=proxy, timeout=float(CONNECTION_TIMEOUT), stream=True)
199 if sha256:207 if sha256:
200 hasher = hashlib.sha256()208 hasher = hashlib.sha256()
201 # Download until finished or canceled.209 # Download until finished or canceled.
@@ -244,21 +252,21 @@
244 """252 """
245 self._base_url = base_url253 self._base_url = base_url
246 self._file_name = file_name254 self._file_name = file_name
247 self._download_cancelled = False255 self.was_cancelled = False
248 super().__init__()256 super().__init__()
249257
250 def start(self):258 def start(self):
251 """259 """
252 Download the url to the temporary directory260 Download the url to the temporary directory
253 """261 """
254 if self._download_cancelled:262 if self.was_cancelled:
255 self.quit.emit()263 self.quit.emit()
256 return264 return
257 try:265 try:
258 dest_path = Path(gettempdir()) / 'openlp' / self._file_name266 dest_path = Path(gettempdir()) / 'openlp' / self._file_name
259 url = '{url}{name}'.format(url=self._base_url, name=self._file_name)267 url = '{url}{name}'.format(url=self._base_url, name=self._file_name)
260 is_success = download_file(self, url, dest_path)268 is_success = download_file(self, url, dest_path)
261 if is_success and not self._download_cancelled:269 if is_success and not self.was_cancelled:
262 self.download_succeeded.emit(dest_path)270 self.download_succeeded.emit(dest_path)
263 else:271 else:
264 self.download_failed.emit()272 self.download_failed.emit()
@@ -273,4 +281,4 @@
273 """281 """
274 A slot to allow the download to be cancelled from outside of the thread282 A slot to allow the download to be cancelled from outside of the thread
275 """283 """
276 self._download_cancelled = True284 self.was_cancelled = True
277285
=== modified file 'openlp/core/display/render.py'
--- openlp/core/display/render.py 2019-02-19 21:38:44 +0000
+++ openlp/core/display/render.py 2019-03-08 21:25:50 +0000
@@ -43,8 +43,8 @@
43log = logging.getLogger(__name__)43log = logging.getLogger(__name__)
4444
45SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'45SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
46CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)' # noqa46CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
47 '([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?')47 r'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?')
48CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'48CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
49FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>'49FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>'
50CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'50CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'
5151
=== modified file 'openlp/core/threading.py'
--- openlp/core/threading.py 2019-02-14 15:09:09 +0000
+++ openlp/core/threading.py 2019-03-08 21:25:50 +0000
@@ -76,11 +76,11 @@
76 Get the worker by the thread name76 Get the worker by the thread name
7777
78 :param str thread_name: The name of the thread78 :param str thread_name: The name of the thread
79 :returns: The worker for this thread name79 :returns: The worker for this thread name, or None
80 """80 """
81 thread_info = Registry().get('application').worker_threads.get(thread_name)81 thread_info = Registry().get('application').worker_threads.get(thread_name)
82 if not thread_info:82 if not thread_info:
83 raise KeyError('No thread named "{}" exists'.format(thread_name))83 return
84 return thread_info.get('worker')84 return thread_info.get('worker')
8585
8686
8787
=== modified file 'openlp/core/ui/firsttimeform.py'
--- openlp/core/ui/firsttimeform.py 2019-02-21 21:29:00 +0000
+++ openlp/core/ui/firsttimeform.py 2019-03-08 21:25:50 +0000
@@ -32,7 +32,7 @@
3232
33from PyQt5 import QtCore, QtWidgets33from PyQt5 import QtCore, QtWidgets
3434
35from openlp.core.common import clean_button_text, trace_error_handler35from openlp.core.common import trace_error_handler
36from openlp.core.common.applocation import AppLocation36from openlp.core.common.applocation import AppLocation
37from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page37from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page
38from openlp.core.common.i18n import translate38from openlp.core.common.i18n import translate
@@ -46,6 +46,7 @@
46from openlp.core.threading import get_thread_worker, is_thread_finished, run_thread46from openlp.core.threading import get_thread_worker, is_thread_finished, run_thread
47from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard47from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard
48from openlp.core.ui.icons import UiIcons48from openlp.core.ui.icons import UiIcons
49from openlp.core.widgets.widgets import ProxyDialog
4950
5051
51log = logging.getLogger(__name__)52log = logging.getLogger(__name__)
@@ -91,7 +92,7 @@
9192
92class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):93class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
93 """94 """
94 This is the Theme Import Wizard, which allows easy creation and editing of OpenLP themes.95 This is the FirstTimeWizard, designed to help new users to get up and running quickly.
95 """96 """
96 log.info('ThemeWizardForm loaded')97 log.info('ThemeWizardForm loaded')
9798
@@ -103,6 +104,7 @@
103 self.web_access = True104 self.web_access = True
104 self.web = ''105 self.web = ''
105 self.setup_ui(self)106 self.setup_ui(self)
107 self.customButtonClicked.connect(self._on_custom_button_clicked)
106 self.themes_list_widget.itemSelectionChanged.connect(self.on_themes_list_widget_selection_changed)108 self.themes_list_widget.itemSelectionChanged.connect(self.on_themes_list_widget_selection_changed)
107 self.themes_deselect_all_button.clicked.connect(self.themes_list_widget.clearSelection)109 self.themes_deselect_all_button.clicked.connect(self.themes_list_widget.clearSelection)
108 self.themes_select_all_button.clicked.connect(self.themes_list_widget.selectAll)110 self.themes_select_all_button.clicked.connect(self.themes_list_widget.selectAll)
@@ -111,13 +113,13 @@
111 """113 """
112 Returns the id of the next FirstTimePage to go to based on enabled plugins114 Returns the id of the next FirstTimePage to go to based on enabled plugins
113 """115 """
114 if FirstTimePage.ScreenConfig < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():116 if FirstTimePage.Download < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
115 # If the songs plugin is enabled then go to the songs page117 # If the songs plugin is enabled then go to the songs page
116 return FirstTimePage.Songs118 return FirstTimePage.Songs
117 elif FirstTimePage.ScreenConfig < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():119 elif FirstTimePage.Download < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
118 # Otherwise, if the Bibles plugin is enabled then go to the Bibles page120 # Otherwise, if the Bibles plugin is enabled then go to the Bibles page
119 return FirstTimePage.Bibles121 return FirstTimePage.Bibles
120 elif FirstTimePage.ScreenConfig < self.currentId() < FirstTimePage.Themes:122 elif FirstTimePage.Download < self.currentId() < FirstTimePage.Themes:
121 # Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes123 # Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes
122 return FirstTimePage.Themes124 return FirstTimePage.Themes
123 else:125 else:
@@ -133,9 +135,7 @@
133 if not self.web_access:135 if not self.web_access:
134 return FirstTimePage.NoInternet136 return FirstTimePage.NoInternet
135 else:137 else:
136 return FirstTimePage.Plugins138 return FirstTimePage.Songs
137 elif self.currentId() == FirstTimePage.Plugins:
138 return self.get_next_page_id()
139 elif self.currentId() == FirstTimePage.Progress:139 elif self.currentId() == FirstTimePage.Progress:
140 return -1140 return -1
141 elif self.currentId() == FirstTimePage.NoInternet:141 elif self.currentId() == FirstTimePage.NoInternet:
@@ -147,7 +147,7 @@
147 Run the wizard.147 Run the wizard.
148 """148 """
149 self.set_defaults()149 self.set_defaults()
150 return QtWidgets.QWizard.exec(self)150 return super().exec()
151151
152 def initialize(self, screens):152 def initialize(self, screens):
153 """153 """
@@ -227,17 +227,13 @@
227 """227 """
228 self.restart()228 self.restart()
229 self.web = 'https://get.openlp.org/ftw/'229 self.web = 'https://get.openlp.org/ftw/'
230 self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
231 self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
232 self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked)
233 self.currentIdChanged.connect(self.on_current_id_changed)230 self.currentIdChanged.connect(self.on_current_id_changed)
234 Registry().register_function('config_screen_changed', self.screen_selection_widget.load)231 Registry().register_function('config_screen_changed', self.screen_selection_widget.load)
235 self.no_internet_finish_button.setVisible(False)
236 self.no_internet_cancel_button.setVisible(False)
237 # Check if this is a re-run of the wizard.232 # Check if this is a re-run of the wizard.
238 self.has_run_wizard = Settings().value('core/has run wizard')233 self.has_run_wizard = Settings().value('core/has run wizard')
239 create_paths(Path(gettempdir(), 'openlp'))234 create_paths(Path(gettempdir(), 'openlp'))
240 self.theme_combo_box.clear()235 self.theme_combo_box.clear()
236 self.button(QtWidgets.QWizard.CustomButton1).setVisible(False)
241 if self.has_run_wizard:237 if self.has_run_wizard:
242 self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())238 self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
243 self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())239 self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
@@ -260,57 +256,85 @@
260 """256 """
261 Detects Page changes and updates as appropriate.257 Detects Page changes and updates as appropriate.
262 """258 """
263 # Keep track of the page we are at. Triggering "Cancel" causes page_id to be a -1.259 back_button = self.button(QtWidgets.QWizard.BackButton)
260 cancel_button = self.button(QtWidgets.QWizard.CancelButton)
261 internet_settings_button = self.button(QtWidgets.QWizard.CustomButton1)
262 next_button = self.button(QtWidgets.QWizard.NextButton)
263 back_button.setVisible(True)
264 next_button.setVisible(True)
265 internet_settings_button.setVisible(False)
264 self.application.process_events()266 self.application.process_events()
265 if page_id != -1:267 if page_id == FirstTimePage.SampleOption:
266 self.last_id = page_id268 internet_settings_button.setVisible(True)
267 if page_id == FirstTimePage.Download:269 elif page_id == FirstTimePage.Download:
268 self.back_button.setVisible(False)270 back_button.setVisible(False)
269 self.next_button.setVisible(False)271 next_button.setVisible(False)
270 # Set the no internet page text.
271 if self.has_run_wizard:
272 self.no_internet_label.setText(self.no_internet_text)
273 else:
274 self.no_internet_label.setText(self.no_internet_text + self.cancel_wizard_text)
275 self.application.set_busy_cursor()272 self.application.set_busy_cursor()
276 self._download_index()273 self._download_index()
277 self.application.set_normal_cursor()274 self.application.set_normal_cursor()
278 self.back_button.setVisible(False)
279 self.next_button.setVisible(True)
280 self.next()275 self.next()
281 elif page_id == FirstTimePage.NoInternet:276 elif page_id == FirstTimePage.NoInternet:
282 self.back_button.setVisible(False)277 next_button.setVisible(False)
283 self.next_button.setVisible(False)278 cancel_button.setVisible(False)
284 self.cancel_button.setVisible(False)279 internet_settings_button.setVisible(True)
285 self.no_internet_finish_button.setVisible(True)
286 if self.has_run_wizard:
287 self.no_internet_cancel_button.setVisible(False)
288 else:
289 self.no_internet_cancel_button.setVisible(True)
290 elif page_id == FirstTimePage.Plugins:
291 self.back_button.setVisible(False)
292 elif page_id == FirstTimePage.Progress:280 elif page_id == FirstTimePage.Progress:
281 back_button.setVisible(False)
282 next_button.setVisible(False)
293 self.application.set_busy_cursor()283 self.application.set_busy_cursor()
294 self._pre_wizard()284 self._pre_wizard()
295 self._perform_wizard()285 self._perform_wizard()
296 self._post_wizard()286 self._post_wizard()
297 self.application.set_normal_cursor()287 self.application.set_normal_cursor()
298288
299 def on_cancel_button_clicked(self):289 def accept(self):
300 """290 """
301 Process the triggering of the cancel button.291 Called when the user clicks 'Finish'. Reimplement it to to save the plugin status
292
293 :rtype: None
294 """
295 self._set_plugin_status(self.songs_check_box, 'songs/status')
296 self._set_plugin_status(self.bible_check_box, 'bibles/status')
297 self._set_plugin_status(self.presentation_check_box, 'presentations/status')
298 self._set_plugin_status(self.image_check_box, 'images/status')
299 self._set_plugin_status(self.media_check_box, 'media/status')
300 self._set_plugin_status(self.custom_check_box, 'custom/status')
301 self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
302 self._set_plugin_status(self.alert_check_box, 'alerts/status')
303 self.screen_selection_widget.save()
304 if self.theme_combo_box.currentIndex() != -1:
305 Settings().setValue('themes/global theme', self.theme_combo_box.currentText())
306 super().accept()
307
308 def reject(self):
309 """
310 Called when the user clicks the cancel button. Reimplement it to clean up the threads.
311
312 :rtype: None
302 """313 """
303 self.was_cancelled = True314 self.was_cancelled = True
304 if self.thumbnail_download_threads: # TODO: Use main thread list315 for thread_name in self.thumbnail_download_threads:
305 for thread_name in self.thumbnail_download_threads:316 worker = get_thread_worker(thread_name)
306 worker = get_thread_worker(thread_name)317 if worker:
307 if worker:318 worker.cancel_download()
308 worker.cancel_download()
309 # Was the thread created.319 # Was the thread created.
310 if self.thumbnail_download_threads:320 if self.thumbnail_download_threads:
311 while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]):321 while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]):
312 time.sleep(0.1)322 time.sleep(0.1)
313 self.application.set_normal_cursor()323 self.application.set_normal_cursor()
324 super().reject()
325
326 def _on_custom_button_clicked(self, which):
327 """
328 Slot to handle the a click on one of the wizards custom buttons.
329
330 :param int QtWidgets.QWizard which: The button pressed
331 :rtype: None
332 """
333 # Internet settings button
334 if which == QtWidgets.QWizard.CustomButton1:
335 proxy_dialog = ProxyDialog(self)
336 proxy_dialog.retranslate_ui()
337 proxy_dialog.exec()
314338
315 def on_themes_list_widget_selection_changed(self):339 def on_themes_list_widget_selection_changed(self):
316 """340 """
@@ -330,23 +354,6 @@
330 elif not item.isSelected() and cbox_index != -1:354 elif not item.isSelected() and cbox_index != -1:
331 self.theme_combo_box.removeItem(cbox_index)355 self.theme_combo_box.removeItem(cbox_index)
332356
333 def on_no_internet_finish_button_clicked(self):
334 """
335 Process the triggering of the "Finish" button on the No Internet page.
336 """
337 self.application.set_busy_cursor()
338 self._perform_wizard()
339 self.application.set_normal_cursor()
340 Settings().setValue('core/has run wizard', True)
341 self.close()
342
343 def on_no_internet_cancel_button_clicked(self):
344 """
345 Process the triggering of the "Cancel" button on the No Internet page.
346 """
347 self.was_cancelled = True
348 self.close()
349
350 def update_progress(self, count, block_size):357 def update_progress(self, count, block_size):
351 """358 """
352 Calculate and display the download progress. This method is called by download_file().359 Calculate and display the download progress. This method is called by download_file().
@@ -373,7 +380,7 @@
373 Prepare the UI for the process.380 Prepare the UI for the process.
374 """381 """
375 self.max_progress = 0382 self.max_progress = 0
376 self.finish_button.setVisible(False)383 self.button(QtWidgets.QWizard.FinishButton).setEnabled(False)
377 self.application.process_events()384 self.application.process_events()
378 try:385 try:
379 # Loop through the songs list and increase for each selected item386 # Loop through the songs list and increase for each selected item
@@ -432,54 +439,31 @@
432 self.progress_bar.setValue(self.progress_bar.maximum())439 self.progress_bar.setValue(self.progress_bar.maximum())
433 if self.has_run_wizard:440 if self.has_run_wizard:
434 text = translate('OpenLP.FirstTimeWizard',441 text = translate('OpenLP.FirstTimeWizard',
435 'Download complete. Click the {button} button to return to OpenLP.'442 'Download complete. Click the \'{finish_button}\' button to return to OpenLP.')
436 ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
437 self.progress_label.setText(text)
438 else:443 else:
439 text = translate('OpenLP.FirstTimeWizard',444 text = translate('OpenLP.FirstTimeWizard',
440 'Download complete. Click the {button} button to start OpenLP.'445 'Download complete. Click the \'{finish_button}\' button to start OpenLP.')
441 ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
442 self.progress_label.setText(text)
443 else:446 else:
444 if self.has_run_wizard:447 if self.has_run_wizard:
445 text = translate('OpenLP.FirstTimeWizard',448 text = translate('OpenLP.FirstTimeWizard', 'Click the \'{finish_button}\' button to return to OpenLP.')
446 'Click the {button} button to return to OpenLP.'
447 ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
448 self.progress_label.setText(text)
449 else:449 else:
450 text = translate('OpenLP.FirstTimeWizard',450 text = translate('OpenLP.FirstTimeWizard', 'Click the \'{finish_button}\' button to start OpenLP.')
451 'Click the {button} button to start OpenLP.'451 self.progress_label.setText(text.format(finish_button=self.finish_button_text))
452 ).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))452 self.button(QtWidgets.QWizard.FinishButton).setEnabled(True)
453 self.progress_label.setText(text)453 self.button(QtWidgets.QWizard.CancelButton).setVisible(False)
454 self.finish_button.setVisible(True)
455 self.finish_button.setEnabled(True)
456 self.cancel_button.setVisible(False)
457 self.next_button.setVisible(False)
458 self.application.process_events()454 self.application.process_events()
459455
460 def _perform_wizard(self):456 def _perform_wizard(self):
461 """457 """
462 Run the tasks in the wizard.458 Run the tasks in the wizard.
463 """459 """
464 # Set plugin states460
465 self._increment_progress_bar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...'))
466 self._set_plugin_status(self.songs_check_box, 'songs/status')
467 self._set_plugin_status(self.bible_check_box, 'bibles/status')
468 self._set_plugin_status(self.presentation_check_box, 'presentations/status')
469 self._set_plugin_status(self.image_check_box, 'images/status')
470 self._set_plugin_status(self.media_check_box, 'media/status')
471 self._set_plugin_status(self.custom_check_box, 'custom/status')
472 self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
473 self._set_plugin_status(self.alert_check_box, 'alerts/status')
474 if self.web_access:461 if self.web_access:
475 if not self._download_selected():462 if not self._download_selected():
476 critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),463 critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
477 translate('OpenLP.FirstTimeWizard', 'There was a connection problem while '464 translate('OpenLP.FirstTimeWizard', 'There was a connection problem while '
478 'downloading, so further downloads will be skipped. Try to re-run '465 'downloading, so further downloads will be skipped. Try to re-run '
479 'the First Time Wizard later.'))466 'the First Time Wizard later.'))
480 self.screen_selection_widget.save()
481 if self.theme_combo_box.currentIndex() != -1:
482 Settings().setValue('themes/global theme', self.theme_combo_box.currentText())
483467
484 def _download_selected(self):468 def _download_selected(self):
485 """469 """
486470
=== modified file 'openlp/core/ui/firsttimewizard.py'
--- openlp/core/ui/firsttimewizard.py 2019-02-15 22:34:53 +0000
+++ openlp/core/ui/firsttimewizard.py 2019-03-08 21:25:50 +0000
@@ -39,14 +39,15 @@
39 An enumeration class with each of the pages of the wizard.39 An enumeration class with each of the pages of the wizard.
40 """40 """
41 Welcome = 041 Welcome = 0
42 ScreenConfig = 142 Plugins = 1
43 Download = 243 ScreenConfig = 2
44 NoInternet = 344 SampleOption = 3
45 Plugins = 445 Download = 4
46 Songs = 546 NoInternet = 5
47 Bibles = 647 Songs = 6
48 Themes = 748 Bibles = 7
49 Progress = 849 Themes = 8
50 Progress = 9
5051
5152
52class ThemeListWidget(QtWidgets.QListWidget):53class ThemeListWidget(QtWidgets.QListWidget):
@@ -97,20 +98,13 @@
97 first_time_wizard.resize(550, 386)98 first_time_wizard.resize(550, 386)
98 first_time_wizard.setModal(True)99 first_time_wizard.setModal(True)
99 first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage |100 first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage |
100 QtWidgets.QWizard.NoBackButtonOnLastPage | QtWidgets.QWizard.HaveCustomButton1 |101 QtWidgets.QWizard.NoBackButtonOnLastPage | QtWidgets.QWizard.HaveCustomButton1)
101 QtWidgets.QWizard.HaveCustomButton2)
102 if is_macosx(): # pragma: nocover102 if is_macosx(): # pragma: nocover
103 first_time_wizard.setPixmap(QtWidgets.QWizard.BackgroundPixmap,103 first_time_wizard.setPixmap(QtWidgets.QWizard.BackgroundPixmap,
104 QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))104 QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
105 first_time_wizard.resize(634, 386)105 first_time_wizard.resize(634, 386)
106 else:106 else:
107 first_time_wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)107 first_time_wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
108 self.finish_button = self.button(QtWidgets.QWizard.FinishButton)
109 self.no_internet_finish_button = self.button(QtWidgets.QWizard.CustomButton1)
110 self.cancel_button = self.button(QtWidgets.QWizard.CancelButton)
111 self.no_internet_cancel_button = self.button(QtWidgets.QWizard.CustomButton2)
112 self.next_button = self.button(QtWidgets.QWizard.NextButton)
113 self.back_button = self.button(QtWidgets.QWizard.BackButton)
114 add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')108 add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')
115 # The screen config page109 # The screen config page
116 self.screen_page = QtWidgets.QWizardPage()110 self.screen_page = QtWidgets.QWizardPage()
@@ -121,6 +115,18 @@
121 self.screen_selection_widget.load()115 self.screen_selection_widget.load()
122 self.screen_page_layout.addRow(self.screen_selection_widget)116 self.screen_page_layout.addRow(self.screen_selection_widget)
123 first_time_wizard.setPage(FirstTimePage.ScreenConfig, self.screen_page)117 first_time_wizard.setPage(FirstTimePage.ScreenConfig, self.screen_page)
118 # Download Samples page
119 self.resource_page = QtWidgets.QWizardPage()
120 self.resource_page.setObjectName('resource_page')
121 self.resource_page.setFinalPage(True)
122 self.resource_layout = QtWidgets.QVBoxLayout(self.resource_page)
123 self.resource_layout.setContentsMargins(50, 20, 50, 20)
124 self.resource_layout.setObjectName('resource_layout')
125 self.resource_label = QtWidgets.QLabel(self.resource_page)
126 self.resource_label.setObjectName('resource_label')
127 self.resource_label.setWordWrap(True)
128 self.resource_layout.addWidget(self.resource_label)
129 first_time_wizard.setPage(FirstTimePage.SampleOption, self.resource_page)
124 # The download page130 # The download page
125 self.download_page = QtWidgets.QWizardPage()131 self.download_page = QtWidgets.QWizardPage()
126 self.download_page.setObjectName('download_page')132 self.download_page.setObjectName('download_page')
@@ -134,6 +140,7 @@
134 # The "you don't have an internet connection" page.140 # The "you don't have an internet connection" page.
135 self.no_internet_page = QtWidgets.QWizardPage()141 self.no_internet_page = QtWidgets.QWizardPage()
136 self.no_internet_page.setObjectName('no_internet_page')142 self.no_internet_page.setObjectName('no_internet_page')
143 self.no_internet_page.setFinalPage(True)
137 self.no_internet_layout = QtWidgets.QVBoxLayout(self.no_internet_page)144 self.no_internet_layout = QtWidgets.QVBoxLayout(self.no_internet_page)
138 self.no_internet_layout.setContentsMargins(50, 30, 50, 40)145 self.no_internet_layout.setContentsMargins(50, 30, 50, 40)
139 self.no_internet_layout.setObjectName('no_internet_layout')146 self.no_internet_layout.setObjectName('no_internet_layout')
@@ -242,27 +249,32 @@
242 self.progress_bar.setObjectName('progress_bar')249 self.progress_bar.setObjectName('progress_bar')
243 self.progress_layout.addWidget(self.progress_bar)250 self.progress_layout.addWidget(self.progress_bar)
244 first_time_wizard.setPage(FirstTimePage.Progress, self.progress_page)251 first_time_wizard.setPage(FirstTimePage.Progress, self.progress_page)
245 self.retranslate_ui(first_time_wizard)252 self.retranslate_ui()
246253
247 def retranslate_ui(self, first_time_wizard):254 def retranslate_ui(self):
248 """255 """
249 Translate the UI on the fly256 Translate the UI on the fly
250257
251 :param first_time_wizard: The wizard form258 :param first_time_wizard: The wizard form
252 """259 """
253 first_time_wizard.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard'))260 self.finish_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton))
261 back_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.BackButton))
262 next_button_text = clean_button_text(self.buttonText(QtWidgets.QWizard.NextButton))
263
264 self.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard'))
254 text = translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard')265 text = translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard')
255 first_time_wizard.title_label.setText('<span style="font-size:14pt; font-weight:600;">{text}'266 self.title_label.setText('<span style="font-size:14pt; font-weight:600;">{text}</span>'.format(text=text))
256 '</span>'.format(text=text))267 self.information_label.setText(
257 button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.NextButton))
258 first_time_wizard.information_label.setText(
259 translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. '268 translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. '
260 'Click the {button} button below to start.').format(button=button))269 'Click the \'{next_button}\' button below to start.'
270 ).format(next_button=next_button_text))
271 self.setButtonText(
272 QtWidgets.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Internet Settings'))
261 self.download_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading Resource Index'))273 self.download_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading Resource Index'))
262 self.download_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while the resource index is '274 self.download_page.setSubTitle(translate('OpenLP.FirstTimeWizard',
263 'downloaded.'))275 'Please wait while the resource index is downloaded.'))
264 self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Please wait while OpenLP downloads the '276 self.download_label.setText(translate('OpenLP.FirstTimeWizard',
265 'resource index file...'))277 'Please wait while OpenLP downloads the resource index file...'))
266 self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Select parts of the program you wish to use'))278 self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Select parts of the program you wish to use'))
267 self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard',279 self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard',
268 'You can also change these settings after the Wizard.'))280 'You can also change these settings after the Wizard.'))
@@ -270,11 +282,10 @@
270 self.screen_page.setSubTitle(translate('OpenLP.FirstTimeWizard',282 self.screen_page.setSubTitle(translate('OpenLP.FirstTimeWizard',
271 'Choose the main display screen for OpenLP.'))283 'Choose the main display screen for OpenLP.'))
272 self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))284 self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
273 self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard',285 self.custom_check_box.setText(
274 'Custom Slides – Easier to manage than songs and they have their own'286 translate('OpenLP.FirstTimeWizard',
275 ' list of slides'))287 'Custom Slides – Easier to manage than songs and they have their own list of slides'))
276 self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard',288 self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Bibles – Import and show Bibles'))
277 'Bibles – Import and show Bibles'))
278 self.image_check_box.setText(translate('OpenLP.FirstTimeWizard',289 self.image_check_box.setText(translate('OpenLP.FirstTimeWizard',
279 'Images – Show images or replace background with them'))290 'Images – Show images or replace background with them'))
280 self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',291 self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
@@ -283,22 +294,25 @@
283 self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))294 self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
284 self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',295 self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
285 'Alerts – Display informative messages while showing other slides'))296 'Alerts – Display informative messages while showing other slides'))
297 self.resource_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Resource Data'))
298 self.resource_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Can OpenLP download some resource data?'))
299 self.resource_label.setText(
300 translate('OpenLP.FirstTimeWizard',
301 'OpenLP has collected some resources that we have permission to distribute.\n\n'
302 'If you would like to download some of these resources click the \'{next_button}\' button, '
303 'otherwise click the \'{finish_button}\' button.'
304 ).format(next_button=next_button_text, finish_button=self.finish_button_text))
286 self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))305 self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))
287 self.no_internet_page.setSubTitle(306 self.no_internet_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Cannot connect to the internet.'))
288 translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.'))307 self.no_internet_label.setText(
289 button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton))308 translate('OpenLP.FirstTimeWizard',
290 self.no_internet_text = translate('OpenLP.FirstTimeWizard',309 'OpenLP could not connect to the internet to get information about the sample data available.\n\n'
291 'No Internet connection was found. The First Time Wizard needs an Internet '310 'Please check your internet connection. If your church uses a proxy server click the '
292 'connection in order to be able to download sample songs, Bibles and themes.'311 '\'Internet Settings\' button below and enter the server details there.\n\nClick the '
293 ' Click the {button} button now to start OpenLP with initial settings and '312 '\'{back_button}\' button to try again.\n\nIf you click the \'{finish_button}\' '
294 'no sample data.\n\nTo re-run the First Time Wizard and import this sample '313 'button you can download the data at a later time by selecting \'Re-run First Time Wizard\' '
295 'data at a later time, check your Internet connection and re-run this '314 'from the \'Tools\' menu in OpenLP.'
296 'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.'315 ).format(back_button=back_button_text, finish_button=self.finish_button_text))
297 ).format(button=button)
298 button = clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.CancelButton))
299 self.cancel_wizard_text = translate('OpenLP.FirstTimeWizard',
300 '\n\nTo cancel the First Time Wizard completely (and not start OpenLP), '
301 'click the {button} button now.').format(button=button)
302 self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))316 self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))
303 self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))317 self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
304 self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))318 self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))
@@ -310,13 +324,8 @@
310 self.themes_select_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Select all'))324 self.themes_select_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Select all'))
311 self.themes_deselect_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Deselect all'))325 self.themes_deselect_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Deselect all'))
312 self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring'))326 self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring'))
313 self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded '327 self.progress_page.setSubTitle(
314 'and OpenLP is configured.'))328 translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded and OpenLP is configured.'))
315 self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
316 first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton1,
317 clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton)))
318 first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton2,
319 clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.CancelButton)))
320329
321 def on_projectors_check_box_clicked(self):330 def on_projectors_check_box_clicked(self):
322 # When clicking projectors_check box, change the visibility setting for Projectors panel.331 # When clicking projectors_check box, change the visibility setting for Projectors panel.
323332
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2019-02-14 15:09:09 +0000
+++ openlp/core/ui/mainwindow.py 2019-03-08 21:25:50 +0000
@@ -681,8 +681,7 @@
681 return681 return
682 first_run_wizard = FirstTimeForm(self)682 first_run_wizard = FirstTimeForm(self)
683 first_run_wizard.initialize(ScreenList())683 first_run_wizard.initialize(ScreenList())
684 first_run_wizard.exec()684 if first_run_wizard.exec() == QtWidgets.QDialog.Rejected:
685 if first_run_wizard.was_cancelled:
686 return685 return
687 self.application.set_busy_cursor()686 self.application.set_busy_cursor()
688 self.first_time()687 self.first_time()
689688
=== modified file 'openlp/core/widgets/widgets.py'
--- openlp/core/widgets/widgets.py 2019-02-14 15:09:09 +0000
+++ openlp/core/widgets/widgets.py 2019-03-08 21:25:50 +0000
@@ -150,6 +150,34 @@
150 settings.setValue('advanced/proxy password', self.password_edit.text())150 settings.setValue('advanced/proxy password', self.password_edit.text())
151151
152152
153class ProxyDialog(QtWidgets.QDialog):
154 """
155 A basic dialog to show proxy settingd
156 """
157 def __init__(self, *args, **kwargs):
158 super().__init__(*args, **kwargs)
159 self.layout = QtWidgets.QVBoxLayout(self)
160 self.proxy_widget = ProxyWidget(self)
161 self.layout.addWidget(self.proxy_widget)
162 self.button_box = \
163 QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, self)
164 self.layout.addWidget(self.button_box)
165 self.button_box.accepted.connect(self.accept)
166 self.button_box.rejected.connect(self.reject)
167
168 def accept(self):
169 """
170 Reimplement the the accept slot so that the ProxyWidget settings can be saved.
171 :rtype: None
172 """
173 self.proxy_widget.save()
174 super().accept()
175
176 def retranslate_ui(self):
177 self.proxy_widget.retranslate_ui()
178 self.setWindowTitle(translate('OpenLP.ProxyDialog', 'Proxy Server Settings'))
179
180
153class ScreenButton(QtWidgets.QPushButton):181class ScreenButton(QtWidgets.QPushButton):
154 """182 """
155 A special button class that holds the screen information about it183 A special button class that holds the screen information about it
156184
=== modified file 'openlp/plugins/songs/lib/importers/openlp.py'
--- openlp/plugins/songs/lib/importers/openlp.py 2019-02-14 15:09:09 +0000
+++ openlp/plugins/songs/lib/importers/openlp.py 2019-03-08 21:25:50 +0000
@@ -106,7 +106,7 @@
106 pass106 pass
107107
108 # Check the file type108 # Check the file type
109 if not isinstance(self.import_source, str) or not self.import_source.endswith('.sqlite'):109 if self.import_source.suffix != '.sqlite':
110 self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',110 self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
111 'Not a valid OpenLP 2 song database.'))111 'Not a valid OpenLP 2 song database.'))
112 return112 return
113113
=== modified file 'openlp/plugins/songs/reporting.py'
--- openlp/plugins/songs/reporting.py 2019-02-14 15:09:09 +0000
+++ openlp/plugins/songs/reporting.py 2019-03-08 21:25:50 +0000
@@ -49,13 +49,6 @@
49 Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')),49 Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')),
50 translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))50 translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
5151
52 if report_file_path is None:
53 main_window.error_message(
54 translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
55 translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your report. \n'
56 'Please select an existing path on your computer.')
57 )
58 return
59 report_file_path.with_suffix('.csv')52 report_file_path.with_suffix('.csv')
60 Registry().get('application').set_busy_cursor()53 Registry().get('application').set_busy_cursor()
61 try:54 try:
6255
=== modified file 'run_openlp.py' (properties changed: +x to -x)
=== modified file 'setup.py' (properties changed: +x to -x)
=== modified file 'tests/functional/openlp_core/common/test_httputils.py'
--- tests/functional/openlp_core/common/test_httputils.py 2019-02-14 15:09:09 +0000
+++ tests/functional/openlp_core/common/test_httputils.py 2019-03-08 21:25:50 +0000
@@ -224,7 +224,7 @@
224 file_size = get_url_file_size(fake_url)224 file_size = get_url_file_size(fake_url)
225225
226 # THEN: The correct methods are called with the correct arguments and a web page is returned226 # THEN: The correct methods are called with the correct arguments and a web page is returned
227 mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, timeout=30.0)227 mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, proxies=None, timeout=30.0)
228 assert file_size == 100228 assert file_size == 100
229229
230 @patch('openlp.core.common.httputils.requests')230 @patch('openlp.core.common.httputils.requests')
231231
=== modified file 'tests/functional/openlp_core/test_threading.py'
--- tests/functional/openlp_core/test_threading.py 2019-02-14 15:09:09 +0000
+++ tests/functional/openlp_core/test_threading.py 2019-03-08 21:25:50 +0000
@@ -133,15 +133,11 @@
133 # GIVEN: A mocked thread worker133 # GIVEN: A mocked thread worker
134 MockRegistry.return_value.get.return_value.worker_threads = {}134 MockRegistry.return_value.get.return_value.worker_threads = {}
135135
136 try:136 # WHEN: get_thread_worker() is called
137 # WHEN: get_thread_worker() is called137 result = get_thread_worker('test_thread')
138 get_thread_worker('test_thread')138
139 assert False, 'A KeyError should have been raised'139 # THEN: None should have been returned
140 except KeyError:140 assert result is None
141 # THEN: The mocked worker is returned
142 pass
143 except Exception:
144 assert False, 'A KeyError should have been raised'
145141
146142
147@patch('openlp.core.threading.Registry')143@patch('openlp.core.threading.Registry')
148144
=== modified file 'tests/functional/openlp_core/ui/test_firsttimeform.py'
--- tests/functional/openlp_core/ui/test_firsttimeform.py 2019-02-16 08:57:11 +0000
+++ tests/functional/openlp_core/ui/test_firsttimeform.py 2019-03-08 21:25:50 +0000
@@ -27,6 +27,8 @@
27from unittest import TestCase27from unittest import TestCase
28from unittest.mock import MagicMock, call, patch, DEFAULT28from unittest.mock import MagicMock, call, patch, DEFAULT
2929
30from PyQt5 import QtWidgets
31
30from openlp.core.common.path import Path32from openlp.core.common.path import Path
31from openlp.core.common.registry import Registry33from openlp.core.common.registry import Registry
32from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem34from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem
@@ -45,6 +47,7 @@
45 """47 """
46 Test the :class:`ThemeListWidgetItem` class48 Test the :class:`ThemeListWidgetItem` class
47 """49 """
50
48 def setUp(self):51 def setUp(self):
49 self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',52 self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash',
50 'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}53 'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'}
@@ -59,7 +62,7 @@
59 """62 """
60 Test that the theme data is loaded correctly in to a ThemeListWidgetItem object when instantiated63 Test that the theme data is loaded correctly in to a ThemeListWidgetItem object when instantiated
61 """64 """
62 # GIVEN: A sample theme dictanary object65 # GIVEN: A sample theme dictionary object
63 # WHEN: Creating an instance of `ThemeListWidgetItem`66 # WHEN: Creating an instance of `ThemeListWidgetItem`
64 instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock())67 instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock())
6568
@@ -74,7 +77,7 @@
74 """77 """
75 Test that the `DownloadWorker` worker is set up correctly and that the thread is started.78 Test that the `DownloadWorker` worker is set up correctly and that the thread is started.
76 """79 """
77 # GIVEN: A sample theme dictanary object80 # GIVEN: A sample theme dictionary object
78 mocked_ftw = MagicMock(spec=FirstTimeForm)81 mocked_ftw = MagicMock(spec=FirstTimeForm)
79 mocked_ftw.thumbnail_download_threads = []82 mocked_ftw.thumbnail_download_threads = []
8083
@@ -120,10 +123,23 @@
120 # THEN: The screens should be set up, and the default values initialised123 # THEN: The screens should be set up, and the default values initialised
121 assert expected_screens == frw.screens, 'The screens should be correct'124 assert expected_screens == frw.screens, 'The screens should be correct'
122 assert frw.web_access is True, 'The default value of self.web_access should be True'125 assert frw.web_access is True, 'The default value of self.web_access should be True'
123 assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False'
124 assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty'126 assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty'
125 assert frw.has_run_wizard is False, 'has_run_wizard should be False'127 assert frw.has_run_wizard is False, 'has_run_wizard should be False'
126128
129 @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.exec')
130 def test_exec(self, mocked_qwizard_exec):
131
132 # GIVEN: An instance of FirstTimeForm
133 frw = FirstTimeForm(None)
134 with patch.object(frw, 'set_defaults') as mocked_set_defaults:
135
136 # WHEN: exec is called
137 frw.exec()
138
139 # THEN: The wizard should be reset and the exec methon on the super class should have been called
140 mocked_set_defaults.assert_called_once()
141 mocked_qwizard_exec.assert_called_once()
142
127 def test_set_defaults(self):143 def test_set_defaults(self):
128 """144 """
129 Test that the default values are set when set_defaults() is run145 Test that the default values are set when set_defaults() is run
@@ -134,8 +150,6 @@
134 mocked_settings = MagicMock()150 mocked_settings = MagicMock()
135 mocked_settings.value.side_effect = lambda key: {'core/has run wizard': False}[key]151 mocked_settings.value.side_effect = lambda key: {'core/has run wizard': False}[key]
136 with patch.object(frw, 'restart') as mocked_restart, \152 with patch.object(frw, 'restart') as mocked_restart, \
137 patch.object(frw, 'cancel_button') as mocked_cancel_button, \
138 patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
139 patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \153 patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
140 patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \154 patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \
141 patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \155 patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \
@@ -153,12 +167,8 @@
153 # THEN: The default values should have been set167 # THEN: The default values should have been set
154 mocked_restart.assert_called_once()168 mocked_restart.assert_called_once()
155 assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'169 assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'
156 mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked)
157 mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with(
158 frw.on_no_internet_finish_button_clicked)
159 mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed)170 mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed)
160 mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load)171 mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load)
161 mocked_no_internet_finish_btn.setVisible.assert_called_once_with(False)
162 mocked_settings.value.assert_has_calls([call('core/has run wizard')])172 mocked_settings.value.assert_has_calls([call('core/has run wizard')])
163 mocked_gettempdir.assert_called_once()173 mocked_gettempdir.assert_called_once()
164 mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))174 mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
@@ -177,8 +187,6 @@
177 mocked_settings.value.side_effect = \187 mocked_settings.value.side_effect = \
178 lambda key: {'core/has run wizard': True, 'themes/global theme': 'Default Theme'}[key]188 lambda key: {'core/has run wizard': True, 'themes/global theme': 'Default Theme'}[key]
179 with patch.object(frw, 'restart') as mocked_restart, \189 with patch.object(frw, 'restart') as mocked_restart, \
180 patch.object(frw, 'cancel_button') as mocked_cancel_button, \
181 patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
182 patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \190 patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
183 patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \191 patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \
184 patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT,192 patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT,
@@ -200,12 +208,8 @@
200 # THEN: The default values should have been set208 # THEN: The default values should have been set
201 mocked_restart.assert_called_once()209 mocked_restart.assert_called_once()
202 assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'210 assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set'
203 mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked)
204 mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with(
205 frw.on_no_internet_finish_button_clicked)
206 mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed)211 mocked_currentIdChanged.connect.assert_called_once_with(frw.on_current_id_changed)
207 mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load)212 mocked_register_function.assert_called_once_with('config_screen_changed', frw.screen_selection_widget.load)
208 mocked_no_internet_finish_btn.setVisible.assert_called_once_with(False)
209 mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')])213 mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')])
210 mocked_gettempdir.assert_called_once()214 mocked_gettempdir.assert_called_once()
211 mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))215 mocked_create_paths.assert_called_once_with(Path('temp', 'openlp'))
@@ -219,12 +223,78 @@
219 mocked_theme_combo_box.findText.assert_called_once_with('Default Theme')223 mocked_theme_combo_box.findText.assert_called_once_with('Default Theme')
220 mocked_theme_combo_box.setCurrentIndex(3)224 mocked_theme_combo_box.setCurrentIndex(3)
221225
226 @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.accept')
227 @patch('openlp.core.ui.firsttimewizard.Settings')
228 def test_accept_method(self, mocked_settings, mocked_qwizard_accept):
229 """
230 Test the FirstTimeForm.accept method
231 """
232 # GIVEN: An instance of FirstTimeForm
233 frw = FirstTimeForm(None)
234 with patch.object(frw, '_set_plugin_status') as mocked_set_plugin_status, \
235 patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT,
236 image_check_box=DEFAULT, media_check_box=DEFAULT, custom_check_box=DEFAULT,
237 song_usage_check_box=DEFAULT, alert_check_box=DEFAULT) as mocked_check_boxes, \
238 patch.object(frw, 'screen_selection_widget') as mocked_screen_selection_widget:
239
240 # WHEN: Calling accept
241 frw.accept()
242
243 # THEN: The selected plugins should be enabled, the screen selection saved and the super method called
244 mocked_set_plugin_status.assert_has_calls([
245 call(mocked_check_boxes['songs_check_box'], 'songs/status'),
246 call(mocked_check_boxes['bible_check_box'], 'bibles/status'),
247 call(mocked_check_boxes['presentation_check_box'], 'presentations/status'),
248 call(mocked_check_boxes['image_check_box'], 'images/status'),
249 call(mocked_check_boxes['media_check_box'], 'media/status'),
250 call(mocked_check_boxes['custom_check_box'], 'custom/status'),
251 call(mocked_check_boxes['song_usage_check_box'], 'songusage/status'),
252 call(mocked_check_boxes['alert_check_box'], 'alerts/status')])
253 mocked_screen_selection_widget.save.assert_called_once()
254 mocked_qwizard_accept.assert_called_once()
255
256 @patch('openlp.core.ui.firsttimewizard.Settings')
257 def test_accept_method_theme_not_selected(self, mocked_settings):
258 """
259 Test the FirstTimeForm.accept method when there is no default theme selected
260 """
261 # GIVEN: An instance of FirstTimeForm
262 frw = FirstTimeForm(None)
263 with patch.object(frw, '_set_plugin_status'), patch.object(frw, 'screen_selection_widget'), \
264 patch.object(frw, 'theme_combo_box', **{'currentIndex.return_value': '-1'}):
265
266 # WHEN: Calling accept and the currentIndex method of the theme_combo_box returns -1
267 frw.accept()
268
269 # THEN: OpenLP should not try to save a theme name
270 mocked_settings.setValue.assert_not_called()
271
272 @patch('openlp.core.ui.firsttimeform.Settings')
273 def test_accept_method_theme_selected(self, mocked_settings):
274 """
275 Test the FirstTimeForm.accept method when a default theme is selected
276 """
277 # GIVEN: An instance of FirstTimeForm
278 frw = FirstTimeForm(None)
279 with patch.object(frw, '_set_plugin_status'), \
280 patch.object(frw, 'screen_selection_widget'), \
281 patch.object(
282 frw, 'theme_combo_box', **{'currentIndex.return_value': 0, 'currentText.return_value': 'Test Item'}):
283
284 # WHEN: Calling accept and the currentIndex method of the theme_combo_box returns 0
285 frw.accept()
286
287 # THEN: The 'currentItem' in the combobox should have been set as the default theme.
288 mocked_settings().setValue.assert_called_once_with('themes/global theme', 'Test Item')
289
290 @patch('openlp.core.ui.firsttimewizard.QtWidgets.QWizard.reject')
222 @patch('openlp.core.ui.firsttimeform.time')291 @patch('openlp.core.ui.firsttimeform.time')
223 @patch('openlp.core.ui.firsttimeform.get_thread_worker')292 @patch('openlp.core.ui.firsttimeform.get_thread_worker')
224 @patch('openlp.core.ui.firsttimeform.is_thread_finished')293 @patch('openlp.core.ui.firsttimeform.is_thread_finished')
225 def test_on_cancel_button_clicked(self, mocked_is_thread_finished, mocked_get_thread_worker, mocked_time):294 def test_reject_method(
295 self, mocked_is_thread_finished, mocked_get_thread_worker, mocked_time, mocked_qwizard_reject):
226 """296 """
227 Test that the cancel button click slot shuts down the threads correctly297 Test that the reject method shuts down the threads correctly
228 """298 """
229 # GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff299 # GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff
230 mocked_worker = MagicMock()300 mocked_worker = MagicMock()
@@ -235,17 +305,47 @@
235 frw.thumbnail_download_threads = ['test_thread']305 frw.thumbnail_download_threads = ['test_thread']
236 with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:306 with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
237307
238 # WHEN: on_cancel_button_clicked() is called308 # WHEN: the reject method is called
239 frw.on_cancel_button_clicked()309 frw.reject()
240310
241 # THEN: The right things should be called in the right order311 # THEN: The right things should be called in the right order
242 assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True'
243 mocked_get_thread_worker.assert_called_once_with('test_thread')312 mocked_get_thread_worker.assert_called_once_with('test_thread')
244 mocked_worker.cancel_download.assert_called_once()313 mocked_worker.cancel_download.assert_called_once()
245 mocked_is_thread_finished.assert_called_with('test_thread')314 mocked_is_thread_finished.assert_called_with('test_thread')
246 assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice'315 assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice'
247 mocked_time.sleep.assert_called_once_with(0.1)316 mocked_time.sleep.assert_called_once_with(0.1)
248 mocked_set_normal_cursor.assert_called_once_with()317 mocked_set_normal_cursor.assert_called_once()
318 mocked_qwizard_reject.assert_called_once()
319
320 @patch('openlp.core.ui.firsttimeform.ProxyDialog')
321 def test_on_custom_button_clicked(self, mocked_proxy_dialog):
322 """
323 Test _on_custom_button when it is called whe the 'internet settings' (CustomButton1) button is not clicked.
324 """
325 # GIVEN: An instance of the FirstTimeForm
326 frw = FirstTimeForm(None)
327
328 # WHEN: Calling _on_custom_button_clicked with a different button to the 'internet settings button.
329 frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton2)
330
331 # THEN: The ProxyDialog should not be shown.
332 mocked_proxy_dialog.assert_not_called()
333
334 @patch('openlp.core.ui.firsttimeform.ProxyDialog')
335 def test_on_custom_button_clicked_internet_settings(self, mocked_proxy_dialog):
336 """
337 Test _on_custom_button when it is called when the 'internet settings' (CustomButton1) button is clicked.
338 """
339 # GIVEN: An instance of the FirstTimeForm
340 frw = FirstTimeForm(None)
341
342 # WHEN: Calling _on_custom_button_clicked with the constant for the 'internet settings' button (CustomButton1)
343 frw._on_custom_button_clicked(QtWidgets.QWizard.CustomButton1)
344
345 # THEN: The ProxyDialog should be shown.
346 mocked_proxy_dialog.assert_called_with(frw)
347 mocked_proxy_dialog().retranslate_ui.assert_called_once()
348 mocked_proxy_dialog().exec.assert_called_once()
249349
250 @patch('openlp.core.ui.firsttimeform.critical_error_message_box')350 @patch('openlp.core.ui.firsttimeform.critical_error_message_box')
251 def test__parse_config_invalid_config(self, mocked_critical_error_message_box):351 def test__parse_config_invalid_config(self, mocked_critical_error_message_box):
@@ -279,8 +379,8 @@
279379
280 # THEN: the critical_error_message_box should have been called380 # THEN: the critical_error_message_box should have been called
281 mocked_message_box.critical.assert_called_once_with(381 mocked_message_box.critical.assert_called_once_with(
282 first_time_form, 'Network Error', 'There was a network error attempting to connect to retrieve '382 first_time_form, 'Network Error',
283 'initial configuration information', 'OK')383 'There was a network error attempting to connect to retrieve initial configuration information', 'OK')
284384
285 @patch('openlp.core.ui.firsttimewizard.Settings')385 @patch('openlp.core.ui.firsttimewizard.Settings')
286 def test_on_projectors_check_box_checked(self, MockSettings):386 def test_on_projectors_check_box_checked(self, MockSettings):
287387
=== modified file 'tests/functional/openlp_plugins/songs/test_openlpimporter.py'
--- tests/functional/openlp_plugins/songs/test_openlpimporter.py 2019-02-14 15:09:09 +0000
+++ tests/functional/openlp_plugins/songs/test_openlpimporter.py 2019-03-08 21:25:50 +0000
@@ -22,6 +22,7 @@
22"""22"""
23This module contains tests for the OpenLP song importer.23This module contains tests for the OpenLP song importer.
24"""24"""
25from pathlib import Path
25from unittest import TestCase26from unittest import TestCase
26from unittest.mock import MagicMock, patch27from unittest.mock import MagicMock, patch
2728
@@ -66,10 +67,9 @@
66 importer.stop_import_flag = True67 importer.stop_import_flag = True
6768
68 # WHEN: Import source is not a list69 # WHEN: Import source is not a list
69 for source in ['not a list', 0]:70 importer.import_source = Path()
70 importer.import_source = source
7171
72 # THEN: do_import should return none and the progress bar maximum should not be set.72 # THEN: do_import should return none and the progress bar maximum should not be set.
73 assert importer.do_import() is None, 'do_import should return None when import_source is not a list'73 assert importer.do_import() is None, 'do_import should return None when import_source is not a list'
74 assert mocked_import_wizard.progress_bar.setMaximum.called is False, \74 assert mocked_import_wizard.progress_bar.setMaximum.called is False, \
75 'setMaximum on import_wizard.progress_bar should not have been called'75 'setMaximum on import_wizard.progress_bar should not have been called'