This commit is contained in:
Tim Bentley 2014-12-15 22:03:02 +00:00
commit 8c209fbb86
10 changed files with 467 additions and 159 deletions

View File

@ -122,6 +122,9 @@ class OpenLP(OpenLPMixin, QtGui.QApplication):
ftw.initialize(screens)
if ftw.exec_() == QtGui.QDialog.Accepted:
Settings().setValue('core/has run wizard', True)
elif ftw.was_cancelled:
QtCore.QCoreApplication.exit()
sys.exit()
# Correct stylesheet bugs
application_stylesheet = ''
if not Settings().value('advanced/alternate rows'):

View File

@ -31,7 +31,6 @@ This module contains the first time wizard.
"""
import logging
import os
import sys
import time
import urllib.request
import urllib.parse
@ -51,30 +50,48 @@ from .firsttimewizard import UiFirstTimeWizard, FirstTimePage
log = logging.getLogger(__name__)
class ThemeScreenshotThread(QtCore.QThread):
class ThemeScreenshotWorker(QtCore.QObject):
"""
This thread downloads the theme screenshots.
This thread downloads a theme's screenshot
"""
screenshot_downloaded = QtCore.pyqtSignal(str, str)
finished = QtCore.pyqtSignal()
def __init__(self, themes_url, title, filename, screenshot):
"""
Set up the worker object
"""
self.was_download_cancelled = False
self.themes_url = themes_url
self.title = title
self.filename = filename
self.screenshot = screenshot
super(ThemeScreenshotWorker, self).__init__()
def run(self):
"""
Overridden method to run the thread.
"""
themes = self.parent().config.get('themes', 'files')
themes = themes.split(',')
config = self.parent().config
for theme in themes:
# Stop if the wizard has been cancelled.
if self.parent().was_download_cancelled:
return
title = config.get('theme_%s' % theme, 'title')
filename = config.get('theme_%s' % theme, 'filename')
screenshot = config.get('theme_%s' % theme, 'screenshot')
urllib.request.urlretrieve('%s%s' % (self.parent().themes_url, screenshot),
os.path.join(gettempdir(), 'openlp', screenshot))
item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget)
item.setData(QtCore.Qt.UserRole, filename)
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
if self.was_download_cancelled:
return
try:
urllib.request.urlretrieve('%s%s' % (self.themes_url, self.screenshot),
os.path.join(gettempdir(), 'openlp', self.screenshot))
# Signal that the screenshot has been downloaded
self.screenshot_downloaded.emit(self.title, self.filename)
except:
log.exception('Unable to download screenshot')
finally:
self.finished.emit()
@QtCore.pyqtSlot(bool)
def set_download_canceled(self, toggle):
"""
Externally set if the download was canceled
:param toggle: Set if the download was canceled or not
"""
self.was_download_cancelled = toggle
class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
@ -88,22 +105,25 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
Create and set up the first time wizard.
"""
super(FirstTimeForm, self).__init__(parent)
self.web_access = True
self.web = ''
self.setup_ui(self)
def get_next_page_id(self):
"""
Returns the id of the next FirstTimePage to go to based on enabled plugins
"""
# The songs plugin is enabled
if FirstTimePage.Welcome < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
print('Go for songs! %r' % self.songs_check_box.isChecked())
# If the songs plugin is enabled then go to the songs page
return FirstTimePage.Songs
# The Bibles plugin is enabled
elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
# Otherwise, if the Bibles plugin is enabled then go to the Bibles page
return FirstTimePage.Bibles
elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Themes:
# Otherwise, if the current page is somewhere between the Welcome and the Themes pages, go to the themes
return FirstTimePage.Themes
else:
# If all else fails, go to the next page
return self.currentId() + 1
def nextId(self):
@ -111,18 +131,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
Determine the next page in the Wizard to go to.
"""
self.application.process_events()
if self.currentId() == FirstTimePage.Plugins:
if self.currentId() == FirstTimePage.Download:
if not self.web_access:
return FirstTimePage.NoInternet
else:
return self.get_next_page_id()
return FirstTimePage.Plugins
elif self.currentId() == FirstTimePage.Plugins:
return self.get_next_page_id()
elif self.currentId() == FirstTimePage.Progress:
return -1
elif self.currentId() == FirstTimePage.NoInternet:
return FirstTimePage.Progress
elif self.currentId() == FirstTimePage.Themes:
self.application.set_busy_cursor()
while not self.theme_screenshot_thread.isFinished():
while not all([thread.isFinished() for thread in self.theme_screenshot_threads]):
time.sleep(0.1)
self.application.process_events()
# Build the screenshot icons, as this can not be done in the thread.
@ -146,11 +168,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
:param screens: The screens detected by OpenLP
"""
self.screens = screens
self.was_cancelled = False
self.theme_screenshot_threads = []
self.theme_screenshot_workers = []
self.has_run_wizard = False
def _download_index(self):
"""
Download the configuration file and kick off the theme screenshot download threads
"""
# check to see if we have web access
self.web_access = False
self.web = 'http://openlp.org/files/frw/'
self.config = ConfigParser()
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
self.application.process_events()
web_config = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent))
if web_config:
files = web_config.read()
@ -165,24 +196,8 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
log.debug('A problem occured while parsing the downloaded config file')
trace_error_handler(log)
self.update_screen_list_combo()
self.was_download_cancelled = False
self.theme_screenshot_thread = None
self.has_run_wizard = False
self.application.process_events()
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading %s...')
self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function('config_screen_changed', self.update_screen_list_combo)
def set_defaults(self):
"""
Set up display at start of theme edit.
"""
self.restart()
check_directory_exists(os.path.join(gettempdir(), 'openlp'))
self.no_internet_finish_button.setVisible(False)
# Check if this is a re-run of the wizard.
self.has_run_wizard = Settings().value('core/has run wizard')
if self.has_run_wizard:
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
@ -199,6 +214,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
songs = self.config.get('songs', 'languages')
songs = songs.split(',')
for song in songs:
self.application.process_events()
title = self.config.get('songs_%s' % song, 'title')
filename = self.config.get('songs_%s' % song, 'filename')
item = QtGui.QListWidgetItem(title, self.songs_list_widget)
@ -208,11 +224,13 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
bible_languages = self.config.get('bibles', 'languages')
bible_languages = bible_languages.split(',')
for lang in bible_languages:
self.application.process_events()
language = self.config.get('bibles_%s' % lang, 'title')
lang_item = QtGui.QTreeWidgetItem(self.bibles_tree_widget, [language])
bibles = self.config.get('bibles_%s' % lang, 'translations')
bibles = bibles.split(',')
for bible in bibles:
self.application.process_events()
title = self.config.get('bible_%s' % bible, 'title')
filename = self.config.get('bible_%s' % bible, 'filename')
item = QtGui.QTreeWidgetItem(lang_item, [title])
@ -220,9 +238,38 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
item.setCheckState(0, QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
self.bibles_tree_widget.expandAll()
# Download the theme screenshots.
self.theme_screenshot_thread = ThemeScreenshotThread(self)
self.theme_screenshot_thread.start()
self.application.process_events()
# Download the theme screenshots
themes = self.config.get('themes', 'files').split(',')
for theme in themes:
self.application.process_events()
title = self.config.get('theme_%s' % theme, 'title')
filename = self.config.get('theme_%s' % theme, 'filename')
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
worker = ThemeScreenshotWorker(self.themes_url, title, filename, screenshot)
self.theme_screenshot_workers.append(worker)
worker.screenshot_downloaded.connect(self.on_screenshot_downloaded)
thread = QtCore.QThread(self)
self.theme_screenshot_threads.append(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.moveToThread(thread)
thread.start()
def set_defaults(self):
"""
Set up display at start of theme edit.
"""
self.restart()
self.web = 'http://openlp.org/files/frw/'
self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function('config_screen_changed', self.update_screen_list_combo)
self.no_internet_finish_button.setVisible(False)
# Check if this is a re-run of the wizard.
self.has_run_wizard = Settings().value('core/has run wizard')
check_directory_exists(os.path.join(gettempdir(), 'openlp'))
def update_screen_list_combo(self):
"""
@ -241,12 +288,20 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.application.process_events()
if page_id != -1:
self.last_id = page_id
if page_id == FirstTimePage.Plugins:
if page_id == FirstTimePage.Download:
self.back_button.setVisible(False)
self.next_button.setVisible(False)
# Set the no internet page text.
if self.has_run_wizard:
self.no_internet_label.setText(self.no_internet_text)
else:
self.no_internet_label.setText(self.no_internet_text + self.cancel_wizard_text)
self.application.set_busy_cursor()
self._download_index()
self.application.set_normal_cursor()
self.back_button.setVisible(False)
self.next_button.setVisible(True)
self.next()
elif page_id == FirstTimePage.Defaults:
self.theme_combo_box.clear()
for index in range(self.themes_list_widget.count()):
@ -266,15 +321,12 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
elif page_id == FirstTimePage.NoInternet:
self.back_button.setVisible(False)
self.next_button.setVisible(False)
self.cancel_button.setVisible(False)
self.no_internet_finish_button.setVisible(True)
if self.has_run_wizard:
self.cancel_button.setVisible(False)
elif page_id == FirstTimePage.Plugins:
self.back_button.setVisible(False)
elif page_id == FirstTimePage.Progress:
self.application.set_busy_cursor()
self.repaint()
self.application.process_events()
# Try to give the wizard a chance to redraw itself
time.sleep(0.2)
self._pre_wizard()
self._perform_wizard()
self._post_wizard()
@ -284,17 +336,28 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
"""
Process the triggering of the cancel button.
"""
if self.last_id == FirstTimePage.NoInternet or \
(self.last_id <= FirstTimePage.Plugins and not self.has_run_wizard):
QtCore.QCoreApplication.exit()
sys.exit()
self.was_download_cancelled = True
self.was_cancelled = True
if self.theme_screenshot_workers:
for worker in self.theme_screenshot_workers:
worker.set_download_canceled(True)
# Was the thread created.
if self.theme_screenshot_thread:
while self.theme_screenshot_thread.isRunning():
if self.theme_screenshot_threads:
while any([thread.isRunning() for thread in self.theme_screenshot_threads]):
time.sleep(0.1)
self.application.set_normal_cursor()
def on_screenshot_downloaded(self, title, filename):
"""
Add an item to the list when a theme has been downloaded
:param title: The title of the theme
:param filename: The filename of the theme
"""
item = QtGui.QListWidgetItem(title, self.themes_list_widget)
item.setData(QtCore.Qt.UserRole, filename)
item.setCheckState(QtCore.Qt.Unchecked)
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
def on_no_internet_finish_button_clicked(self):
"""
Process the triggering of the "Finish" button on the No Internet page.
@ -321,7 +384,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
filename = open(f_path, "wb")
# Download until finished or canceled.
while not self.was_download_cancelled:
while not self.was_cancelled:
data = url_file.read(block_size)
if not data:
break
@ -341,7 +404,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
continue
break
# Delete file if cancelled, it may be a partial file.
if self.was_download_cancelled:
if self.was_cancelled:
os.remove(f_path)
return True
@ -354,6 +417,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
for index, theme in enumerate(themes):
screenshot = self.config.get('theme_%s' % theme, 'screenshot')
item = self.themes_list_widget.item(index)
# if item:
item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot)))
def _get_file_size(self, url):

View File

@ -41,13 +41,14 @@ class FirstTimePage(object):
An enumeration class with each of the pages of the wizard.
"""
Welcome = 0
Plugins = 1
Download = 1
NoInternet = 2
Songs = 3
Bibles = 4
Themes = 5
Defaults = 6
Progress = 7
Plugins = 3
Songs = 4
Bibles = 5
Themes = 6
Defaults = 7
Progress = 8
class UiFirstTimeWizard(object):
@ -78,6 +79,27 @@ class UiFirstTimeWizard(object):
self.next_button = self.button(QtGui.QWizard.NextButton)
self.back_button = self.button(QtGui.QWizard.BackButton)
add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')
# The download page
self.download_page = QtGui.QWizardPage()
self.download_page.setObjectName('download_page')
self.download_layout = QtGui.QVBoxLayout(self.download_page)
self.download_layout.setMargin(48)
self.download_layout.setObjectName('download_layout')
self.download_label = QtGui.QLabel(self.download_page)
self.download_label.setObjectName('download_label')
self.download_layout.addWidget(self.download_label)
first_time_wizard.setPage(FirstTimePage.Download, self.download_page)
# The "you don't have an internet connection" page.
self.no_internet_page = QtGui.QWizardPage()
self.no_internet_page.setObjectName('no_internet_page')
self.no_internet_layout = QtGui.QVBoxLayout(self.no_internet_page)
self.no_internet_layout.setContentsMargins(50, 30, 50, 40)
self.no_internet_layout.setObjectName('no_internet_layout')
self.no_internet_label = QtGui.QLabel(self.no_internet_page)
self.no_internet_label.setWordWrap(True)
self.no_internet_label.setObjectName('no_internet_label')
self.no_internet_layout.addWidget(self.no_internet_label)
first_time_wizard.setPage(FirstTimePage.NoInternet, self.no_internet_page)
# The plugins page
self.plugin_page = QtGui.QWizardPage()
self.plugin_page.setObjectName('plugin_page')
@ -120,17 +142,6 @@ class UiFirstTimeWizard(object):
self.alert_check_box.setObjectName('alert_check_box')
self.plugin_layout.addWidget(self.alert_check_box)
first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page)
# The "you don't have an internet connection" page.
self.no_internet_page = QtGui.QWizardPage()
self.no_internet_page.setObjectName('no_internet_page')
self.no_internet_layout = QtGui.QVBoxLayout(self.no_internet_page)
self.no_internet_layout.setContentsMargins(50, 30, 50, 40)
self.no_internet_layout.setObjectName('no_internet_layout')
self.no_internet_label = QtGui.QLabel(self.no_internet_page)
self.no_internet_label.setWordWrap(True)
self.no_internet_label.setObjectName('no_internet_label')
self.no_internet_layout.addWidget(self.no_internet_label)
first_time_wizard.setPage(FirstTimePage.NoInternet, self.no_internet_page)
# The song samples page
self.songs_page = QtGui.QWizardPage()
self.songs_page.setObjectName('songs_page')
@ -221,6 +232,11 @@ class UiFirstTimeWizard(object):
translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. '
'Click the %s button below to start.') %
clean_button_text(first_time_wizard.buttonText(QtGui.QWizard.NextButton)))
self.download_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading Resource Index'))
self.download_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while the resource index is '
'downloaded.'))
self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Please wait while OpenLP downloads the '
'resource index file...'))
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
@ -257,5 +273,8 @@ class UiFirstTimeWizard(object):
'Set up default settings to be used by OpenLP.'))
self.display_label.setText(translate('OpenLP.FirstTimeWizard', 'Default output display:'))
self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:'))
self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring'))
self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded '
'and OpenLP is configured.'))
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))

View File

@ -690,7 +690,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
first_run_wizard = FirstTimeForm(self)
first_run_wizard.initialize(ScreenList())
first_run_wizard.exec_()
if first_run_wizard.was_download_cancelled:
if first_run_wizard.was_cancelled:
return
self.application.set_busy_cursor()
self.first_time()

View File

@ -152,16 +152,19 @@ def set_button_tooltip(bar):
"""
for button in bar.buttons():
if bar.standardButton(button) == QDialogButtonBox.Cancel:
tip = "Ignoring current changes and return to OpenLP"
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Ignoring current changes and return to OpenLP'))
elif bar.standardButton(button) == QDialogButtonBox.Reset:
tip = "Delete all user-defined text and revert to PJLink default text"
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Delete all user-defined text and revert to PJLink default text'))
elif bar.standardButton(button) == QDialogButtonBox.Discard:
tip = "Discard changes and reset to previous user-defined text"
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Discard changes and reset to previous user-defined text'))
elif bar.standardButton(button) == QDialogButtonBox.Ok:
tip = "Save changes and return to OpenLP"
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Save changes and return to OpenLP'))
else:
tip = ""
button.setToolTip(tip)
log.debug('No tooltip for button {}'.format(button.text()))
class FingerTabBarWidget(QTabBar):
@ -237,12 +240,13 @@ class SourceSelectTabs(QDialog):
"""
log.debug('Initializing SourceSelectTabs()')
super(SourceSelectTabs, self).__init__(parent)
self.setMinimumWidth(350)
self.projectordb = projectordb
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
else:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
else:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.setWindowTitle(title)
self.setObjectName('source_select_tabs')
self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
@ -286,6 +290,10 @@ class SourceSelectTabs(QDialog):
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
else:
for key in keys:
(tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
@ -297,10 +305,8 @@ class SourceSelectTabs(QDialog):
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
set_button_tooltip(self.button_box)
@ -321,9 +327,9 @@ class SourceSelectTabs(QDialog):
if self.button_box.standardButton(button) == self.button_box.Cancel:
self.done(0)
elif self.button_box.standardButton(button) == self.button_box.Reset:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.done(100)
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Ok:
return self.accept_me()
else:
@ -331,9 +337,11 @@ class SourceSelectTabs(QDialog):
def delete_sources(self):
msg = QtGui.QMessageBox()
msg.setText('Delete entries for this projector')
msg.setInformativeText('Are you sure you want to delete ALL user-defined '
'source input text for this projector?')
msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector'))
msg.setInformativeText(translate('OpenLP.SourceSelectForm',
'Are you sure you want to delete ALL user-defined '),
translate('OpenLP.SourceSelectForm',
'source input text for this projector?'))
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()
@ -382,7 +390,11 @@ class SourceSelectSingle(QDialog):
log.debug('Initializing SourceSelectSingle()')
self.projectordb = projectordb
super(SourceSelectSingle, self).__init__(parent)
self.setWindowTitle(translate('OpenLP.SourceSelectSingle', 'Select Projector Source'))
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
else:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.setObjectName('source_select_single')
self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
self.setModal(True)
@ -418,6 +430,10 @@ class SourceSelectSingle(QDialog):
item.setText(source_item.text)
self.layout.addRow(PJLINK_DEFAULT_CODES[key], item)
self.button_group.append(item)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
else:
for key in keys:
source_text = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
@ -427,10 +443,8 @@ class SourceSelectSingle(QDialog):
self.layout.addWidget(button)
self.button_group.addButton(button, int(key))
button_list.append(key)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
self.setMinimumHeight(key_count*25)
@ -452,9 +466,9 @@ class SourceSelectSingle(QDialog):
if self.button_box.standardButton(button) == self.button_box.Cancel:
self.done(0)
elif self.button_box.standardButton(button) == self.button_box.Reset:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.done(100)
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Ok:
return self.accept_me()
else:
@ -462,9 +476,11 @@ class SourceSelectSingle(QDialog):
def delete_sources(self):
msg = QtGui.QMessageBox()
msg.setText('Delete entries for this projector')
msg.setInformativeText('Are you sure you want to delete ALL user-defined '
'source input text for this projector?')
msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector'))
msg.setInformativeText(translate('OpenLP.SourceSelectForm',
'Are you sure you want to delete ALL user-defined '),
translate('OpenLP.SourceSelectForm',
'source input text for this projector?'))
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()

View File

@ -64,7 +64,6 @@ log = logging.getLogger(__name__ + '.__init__')
APPLICATION_VERSION = {}
IMAGES_FILTER = None
ICU_COLLATOR = None
UNO_CONNECTION_TYPE = 'pipe'
CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
@ -423,7 +422,7 @@ def get_web_page(url, header=None, update_openlp=False):
return page
def get_uno_command():
def get_uno_command(connection_type='pipe'):
"""
Returns the UNO command to launch an openoffice.org instance.
"""
@ -434,21 +433,21 @@ def get_uno_command():
raise FileNotFoundError('Command not found')
OPTIONS = '--nologo --norestore --minimized --nodefault --nofirststartwizard'
if UNO_CONNECTION_TYPE == 'pipe':
if connection_type == 'pipe':
CONNECTION = '"--accept=pipe,name=openlp_pipe;urp;"'
else:
CONNECTION = '"--accept=socket,host=localhost,port=2002;urp;"'
return '%s %s %s' % (command, OPTIONS, CONNECTION)
def get_uno_instance(resolver):
def get_uno_instance(resolver, connection_type='pipe'):
"""
Returns a running openoffice.org instance.
:param resolver: The UNO resolver to use to find a running instance.
"""
log.debug('get UNO Desktop Openoffice - resolve')
if UNO_CONNECTION_TYPE == 'pipe':
if connection_type == 'pipe':
return resolver.resolve('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext')
else:
return resolver.resolve('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext')

View File

@ -30,6 +30,7 @@
Package to test the openlp.core.ui.firsttimeform package.
"""
from configparser import ConfigParser
import os
from unittest import TestCase
from openlp.core.common import Registry
@ -71,55 +72,117 @@ class TestFirstTimeForm(TestCase, TestMixin):
def setUp(self):
self.setup_application()
self.app.setApplicationVersion('0.0')
# Set up a fake "set_normal_cursor" method since we're not dealing with an actual OpenLP application object
self.app.set_normal_cursor = lambda: None
self.app.process_events = lambda: None
Registry.create()
Registry().register('application', self.app)
def basic_initialise_test(self):
def initialise_test(self):
"""
Test if we can intialise the FirstTimeForm without a config file
Test if we can intialise the FirstTimeForm
"""
# GIVEN: A mocked get_web_page, a First Time Wizard and an expected screen object
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
first_time_form = FirstTimeForm(None)
expected_screens = MagicMock()
expected_web_url = 'http://openlp.org/files/frw/'
expected_user_agent = 'OpenLP/0.0'
mocked_get_web_page.return_value = None
# GIVEN: A First Time Wizard and an expected screen object
frw = FirstTimeForm(None)
expected_screens = MagicMock()
# WHEN: The First Time Wizard is initialised
first_time_form.initialize(expected_screens)
# WHEN: The First Time Wizard is initialised
frw.initialize(expected_screens)
# THEN: The First Time Form web configuration file should be accessible and parseable
self.assertEqual(expected_screens, first_time_form.screens, 'The screens should be correct')
self.assertEqual(expected_web_url, first_time_form.web, 'The base path of the URL should be correct')
self.assertIsInstance(first_time_form.config, ConfigParser, 'The config object should be a ConfigParser')
mocked_get_web_page.assert_called_with(expected_web_url + 'download.cfg',
header=('User-Agent', expected_user_agent))
# THEN: The screens should be set up, and the default values initialised
self.assertEqual(expected_screens, frw.screens, 'The screens should be correct')
self.assertTrue(frw.web_access, 'The default value of self.web_access should be True')
self.assertFalse(frw.was_cancelled, 'The default value of self.was_cancelled should be False')
self.assertListEqual([], frw.theme_screenshot_threads, 'The list of threads should be empty')
self.assertListEqual([], frw.theme_screenshot_workers, 'The list of workers should be empty')
self.assertFalse(frw.has_run_wizard, 'has_run_wizard should be False')
def config_initialise_test(self):
def set_defaults_test(self):
"""
Test if we can intialise the FirstTimeForm with a config file
Test that the default values are set when set_defaults() is run
"""
# GIVEN: A mocked get_web_page, a First Time Wizard and an expected screen object
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
first_time_form = FirstTimeForm(None)
expected_web_url = 'http://openlp.org/files/frw/'
expected_songs_url = 'http://example.com/frw/songs/'
expected_bibles_url = 'http://example.com/frw/bibles/'
expected_themes_url = 'http://example.com/frw/themes/'
expected_user_agent = 'OpenLP/0.0'
mocked_get_web_page.return_value.read.return_value = FAKE_CONFIG
# GIVEN: An initialised FRW and a whole lot of stuff mocked out
frw = FirstTimeForm(None)
frw.initialize(MagicMock())
with patch.object(frw, 'restart') as mocked_restart, \
patch.object(frw, 'cancel_button') as mocked_cancel_button, \
patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \
patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \
patch.object(Registry, 'register_function') as mocked_register_function, \
patch('openlp.core.ui.firsttimeform.Settings') as MockedSettings, \
patch('openlp.core.ui.firsttimeform.gettempdir') as mocked_gettempdir, \
patch('openlp.core.ui.firsttimeform.check_directory_exists') as mocked_check_directory_exists, \
patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
MockedSettings.return_value = mocked_settings
mocked_gettempdir.return_value = 'temp'
expected_temp_path = os.path.join('temp', 'openlp')
# WHEN: The First Time Wizard is initialised
first_time_form.initialize(MagicMock())
# WHEN: The set_defaults() method is run
frw.set_defaults()
# THEN: The First Time Form web configuration file should be accessible and parseable
self.assertIsInstance(first_time_form.config, ConfigParser, 'The config object should be a ConfigParser')
mocked_get_web_page.assert_called_with(expected_web_url + 'download.cfg',
header=('User-Agent', expected_user_agent))
self.assertEqual(expected_songs_url, first_time_form.songs_url, 'The songs URL should be correct')
self.assertEqual(expected_bibles_url, first_time_form.bibles_url, 'The bibles URL should be correct')
self.assertEqual(expected_themes_url, first_time_form.themes_url, 'The themes URL should be correct')
# THEN: The default values should have been set
mocked_restart.assert_called_with()
self.assertEqual('http://openlp.org/files/frw/', frw.web, 'The default URL should be set')
mocked_cancel_button.clicked.connect.assert_called_with(frw.on_cancel_button_clicked)
mocked_no_internet_finish_btn.clicked.connect.assert_called_with(frw.on_no_internet_finish_button_clicked)
mocked_currentIdChanged.connect.assert_called_with(frw.on_current_id_changed)
mocked_register_function.assert_called_with('config_screen_changed', frw.update_screen_list_combo)
mocked_no_internet_finish_btn.setVisible.assert_called_with(False)
mocked_settings.value.assert_called_with('core/has run wizard')
mocked_gettempdir.assert_called_with()
mocked_check_directory_exists.assert_called_with(expected_temp_path)
def update_screen_list_combo_test(self):
"""
Test that the update_screen_list_combo() method works correctly
"""
# GIVEN: A mocked Screen() object and an initialised First Run Wizard and a mocked display_combo_box
expected_screen_list = ['Screen 1', 'Screen 2']
mocked_screens = MagicMock()
mocked_screens.get_screen_list.return_value = expected_screen_list
frw = FirstTimeForm(None)
frw.initialize(mocked_screens)
with patch.object(frw, 'display_combo_box') as mocked_display_combo_box:
mocked_display_combo_box.count.return_value = 2
# WHEN: update_screen_list_combo() is called
frw.update_screen_list_combo()
# THEN: The combobox should have been updated
mocked_display_combo_box.clear.assert_called_with()
mocked_screens.get_screen_list.assert_called_with()
mocked_display_combo_box.addItems.assert_called_with(expected_screen_list)
mocked_display_combo_box.count.assert_called_with()
mocked_display_combo_box.setCurrentIndex.assert_called_with(1)
def on_cancel_button_clicked_test(self):
"""
Test that the cancel button click slot shuts down the threads correctly
"""
# GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff
frw = FirstTimeForm(None)
frw.initialize(MagicMock())
mocked_worker = MagicMock()
mocked_thread = MagicMock()
mocked_thread.isRunning.side_effect = [True, False]
frw.theme_screenshot_workers.append(mocked_worker)
frw.theme_screenshot_threads.append(mocked_thread)
with patch('openlp.core.ui.firsttimeform.time') as mocked_time, \
patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
# WHEN: on_cancel_button_clicked() is called
frw.on_cancel_button_clicked()
# THEN: The right things should be called in the right order
self.assertTrue(frw.was_cancelled, 'The was_cancelled property should have been set to True')
mocked_worker.set_download_canceled.assert_called_with(True)
mocked_thread.isRunning.assert_called_with()
self.assertEqual(2, mocked_thread.isRunning.call_count, 'isRunning() should have been called twice')
mocked_time.sleep.assert_called_with(0.1)
self.assertEqual(1, mocked_time.sleep.call_count, 'sleep() should have only been called once')
mocked_set_normal_cursor.assert_called_with()
def broken_config_test(self):
"""
@ -128,10 +191,11 @@ class TestFirstTimeForm(TestCase, TestMixin):
# GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
first_time_form = FirstTimeForm(None)
first_time_form.initialize(MagicMock())
mocked_get_web_page.return_value.read.return_value = FAKE_BROKEN_CONFIG
# WHEN: The First Time Wizard is initialised
first_time_form.initialize(MagicMock())
# WHEN: The First Time Wizard is downloads the config file
first_time_form._download_index()
# THEN: The First Time Form should not have web access
self.assertFalse(first_time_form.web_access, 'There should not be web access with a broken config file')
@ -143,10 +207,11 @@ class TestFirstTimeForm(TestCase, TestMixin):
# GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked invalid config file
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
first_time_form = FirstTimeForm(None)
first_time_form.initialize(MagicMock())
mocked_get_web_page.return_value.read.return_value = FAKE_INVALID_CONFIG
# WHEN: The First Time Wizard is initialised
first_time_form.initialize(MagicMock())
# WHEN: The First Time Wizard is downloads the config file
first_time_form._download_index()
# THEN: The First Time Form should not have web access
self.assertFalse(first_time_form.web_access, 'There should not be web access with an invalid config file')

View File

@ -32,7 +32,7 @@ Package to test the openlp.core.utils.actions package.
from unittest import TestCase
from openlp.core.common.settings import Settings
from openlp.core.utils import VersionThread, get_application_version
from openlp.core.utils import VersionThread, get_application_version, get_uno_command
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
@ -62,9 +62,74 @@ class TestInitFunctions(TestMixin, TestCase):
# WHEN: We check to see if the version is different .
with patch('PyQt4.QtCore.QThread'),\
patch('openlp.core.utils.get_application_version') as mocked_get_application_version:
mocked_get_application_version.return_value = \
{'version': '1.0.0', 'build': '', 'full': '2.0.4'}
mocked_get_application_version.return_value = {'version': '1.0.0', 'build': '', 'full': '2.0.4'}
version_thread = VersionThread(mocked_main_window)
version_thread.run()
# THEN: If the version has changed the main window is notified
self.assertTrue(mocked_main_window.emit.called, 'The main windows should have been notified')
def get_uno_command_libreoffice_command_exists_test(self):
"""
Test the ``get_uno_command`` function uses the libreoffice command when available.
:return:
"""
# GIVEN: A patched 'which' method which returns a path when called with 'libreoffice'
with patch('openlp.core.utils.which',
**{'side_effect': lambda command: {'libreoffice': '/usr/bin/libreoffice'}[command]}):
# WHEN: Calling get_uno_command
result = get_uno_command()
# THEN: The command 'libreoffice' should be called with the appropriate parameters
self.assertEquals(result, 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard'
' "--accept=pipe,name=openlp_pipe;urp;"')
def get_uno_command_only_soffice_command_exists_test(self):
"""
Test the ``get_uno_command`` function uses the soffice command when the libreoffice command is not available.
:return:
"""
# GIVEN: A patched 'which' method which returns None when called with 'libreoffice' and a path when called with
# 'soffice'
with patch('openlp.core.utils.which',
**{'side_effect': lambda command: {'libreoffice': None, 'soffice': '/usr/bin/soffice'}[command]}):
# WHEN: Calling get_uno_command
result = get_uno_command()
# THEN: The command 'soffice' should be called with the appropriate parameters
self.assertEquals(result, 'soffice --nologo --norestore --minimized --nodefault --nofirststartwizard'
' "--accept=pipe,name=openlp_pipe;urp;"')
def get_uno_command_when_no_command_exists_test(self):
"""
Test the ``get_uno_command`` function raises an FileNotFoundError when neither the libreoffice or soffice
commands are available.
:return:
"""
# GIVEN: A patched 'which' method which returns None
with patch('openlp.core.utils.which', **{'return_value': None}):
# WHEN: Calling get_uno_command
# THEN: a FileNotFoundError exception should be raised
self.assertRaises(FileNotFoundError, get_uno_command)
def get_uno_command_connection_type_test(self):
"""
Test the ``get_uno_command`` function when the connection type is anything other than pipe.
:return:
"""
# GIVEN: A patched 'which' method which returns 'libreoffice'
with patch('openlp.core.utils.which', **{'return_value': 'libreoffice'}):
# WHEN: Calling get_uno_command with a connection type other than pipe
result = get_uno_command('socket')
# THEN: The connection parameters should be set for socket
self.assertEqual(result, 'libreoffice --nologo --norestore --minimized --nodefault --nofirststartwizard'
' "--accept=socket,host=localhost,port=2002;urp;"')

View File

@ -94,11 +94,9 @@ class TestProjectorManager(TestCase, TestMixin):
self.projector_manager.bootstrap_initialise()
self.projector_manager.bootstrap_post_set_up()
# THEN: verify calls to retrieve saved projectors
# THEN: verify calls to retrieve saved projectors and edit page initialized
self.assertEqual(1, self.projector_manager._load_projectors.call_count,
'Initialization should have called load_projectors()')
# THEN: Verify edit page is initialized
self.assertEqual(type(self.projector_manager.projector_form), ProjectorEditForm,
'Initialization should have created a Projector Edit Form')
self.assertIs(self.projector_manager.projectordb,

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod: `tests.interfaces.openlp_core_ui.test_projectorsourceform` module
Tests for the Projector Source Select form.
"""
import logging
log = logging.getLogger(__name__)
log.debug('test_projectorsourceform loaded')
from unittest import TestCase
from tests.helpers.testmixin import TestMixin
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
from openlp.core.ui.projector.sourceselectform import source_group
def build_source_dict():
"""
Builds a source dictionary to verify source_group returns a valid dictionary of dictionary items
:returns: dictionary of valid PJLink source codes grouped by PJLink source group
"""
test_group = {}
for group in PJLINK_DEFAULT_SOURCES.keys():
test_group[group] = {}
for key in PJLINK_DEFAULT_CODES:
test_group[key[0]][key] = PJLINK_DEFAULT_CODES[key]
return test_group
class ProjectorSourceFormTest(TestCase, TestMixin):
"""
Test class for the Projector Source Select form module
"""
def source_dict_test(self):
"""
Test that source list dict returned from sourceselectform module is a valid dict with proper entries
"""
# GIVEN: A list of inputs
codes = []
for item in PJLINK_DEFAULT_CODES.keys():
codes.append(item)
codes.sort()
# WHEN: projector.sourceselectform.source_select() is called
check = source_group(codes, PJLINK_DEFAULT_CODES)
# THEN: return dictionary should match test dictionary
self.assertEquals(check, build_source_dict(),
"Source group dictionary should match test dictionary")