def on_save_report_button_clicked(self): """ Saving exception log and system information to a file. """ report_text = translate( 'OpenLP.ExceptionForm', '**OpenLP Bug Report**\n' 'Version: %s\n\n' '--- Details of the Exception. ---\n\n%s\n\n ' '--- Exception Traceback ---\n%s\n' '--- System information ---\n%s\n' '--- Library Versions ---\n%s\n') filename = QtGui.QFileDialog.getSaveFileName( self, translate('OpenLP.ExceptionForm', 'Save Crash Report'), Settings().value(self.settings_section + '/last directory'), translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)')) if filename: filename = str(filename).replace('/', os.path.sep) Settings().setValue(self.settings_section + '/last directory', os.path.dirname(filename)) report_text = report_text % self._create_report() try: report_file = open(filename, 'w') try: report_file.write(report_text) except UnicodeError: report_file.close() report_file = open(filename, 'wb') report_file.write(report_text.encode('utf-8')) finally: report_file.close() except IOError: log.exception('Failed to write crash report') finally: report_file.close()
def test_build_song_footer_base_ccli(self): """ Test build songs footer with basic song and a CCLI number """ # GIVEN: A Song and a Service Item and a configured CCLI license mock_song = MagicMock() mock_song.title = 'My Song' mock_song.copyright = 'My copyright' service_item = ServiceItem(None) Settings().setValue('core/ccli number', '1234') # WHEN: I generate the Footer with default settings self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], 'The array should be returned correctly with a song, an author, copyright and ccli') # WHEN: I amend the CCLI value Settings().setValue('core/ccli number', '4321') self.media_item.generate_footer(service_item, mock_song) # THEN: I would get an amended footer string self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], 'The array should be returned correctly with a song, an author, copyright and amended ccli')
def test_add_action_different_parent(self): """ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and both have the QtCore.Qt.WindowShortcut shortcut context set. """ # GIVEN: Two actions with the same shortcuts. parent = QtCore.QObject() action2 = QtWidgets.QAction(parent) action2.setObjectName('action2') second_parent = QtCore.QObject() action_with_same_shortcuts2 = QtWidgets.QAction(second_parent) action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2') # Add default shortcuts to Settings class. default_shortcuts = { 'shortcuts/action2': [QtGui.QKeySequence(QtCore.Qt.Key_C), QtGui.QKeySequence(QtCore.Qt.Key_D)], 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(QtCore.Qt.Key_D), QtGui.QKeySequence(QtCore.Qt.Key_C)] } Settings.extend_default_settings(default_shortcuts) # WHEN: Add the two actions to the action list. self.action_list.add_action(action2, 'example_category') self.action_list.add_action(action_with_same_shortcuts2, 'example_category') # Remove the actions again. self.action_list.remove_action(action2, 'example_category') self.action_list.remove_action(action_with_same_shortcuts2, 'example_category') # THEN: As both actions have the same shortcuts, they should be removed from one action. assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.' assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
def build_settings(self): """ Build the settings Object and initialise it """ Settings.setDefaultFormat(Settings.IniFormat) self.fd, self.ini_file = mkstemp('.ini') Settings().set_filename(self.ini_file)
def initialise(self): """ We need to set up the screen """ self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date')) self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date')) self.file_line_edit.setText(Settings().value(self.plugin.settings_section + '/last directory export'))
def extend_default_settings_test(self): """ Test that the extend_default_settings method extends the default settings """ # GIVEN: A patched __default_settings__ dictionary with patch.dict(Settings.__default_settings__, { 'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3 }, True): # WHEN: Calling extend_default_settings Settings.extend_default_settings({ 'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2 }) # THEN: The _default_settings__ dictionary_ should have the new keys self.assertEqual( Settings.__default_settings__, { 'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2 })
def accept(self): """ Ok was triggered so lets save the data and run the report """ log.debug('accept') path = self.file_line_edit.text() if not path: self.main_window.error_message( translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'), translate( 'SongUsagePlugin.SongUsageDetailForm', 'You have not set a valid output location for your' ' song usage report. \nPlease select an existing path on your computer.' )) return check_directory_exists(path) file_name = translate('SongUsagePlugin.SongUsageDetailForm', 'usage_detail_%s_%s.txt') % \ (self.from_date_calendar.selectedDate().toString('ddMMyyyy'), self.to_date_calendar.selectedDate().toString('ddMMyyyy')) Settings().setValue(self.plugin.settings_section + '/from date', self.from_date_calendar.selectedDate()) Settings().setValue(self.plugin.settings_section + '/to date', self.to_date_calendar.selectedDate()) usage = self.plugin.manager.get_all_objects( SongUsageItem, and_( SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(), SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()), [SongUsageItem.usagedate, SongUsageItem.usagetime]) report_file_name = os.path.join(path, file_name) file_handle = None try: file_handle = open(report_file_name, 'wb') for instance in usage: record = '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \ '\"%s\",\"%s\"\n' % \ (instance.usagedate, instance.usagetime, instance.title, instance.copyright, instance.ccl_number, instance.authors, instance.plugin_name, instance.source) file_handle.write(record.encode('utf-8')) self.main_window.information_message( translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'), translate('SongUsagePlugin.SongUsageDetailForm', 'Report \n%s \nhas been successfully created. ') % report_file_name) except OSError as ose: log.exception('Failed to write out song usage records') critical_error_message_box( translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'), translate('SongUsagePlugin.SongUsageDetailForm', 'An error occurred while creating the report: %s') % ose.strerror) finally: if file_handle: file_handle.close() self.close()
def controller_text(self, var): """ Perform an action on the slide controller. """ log.debug("controller_text var = %s" % var) current_item = self.live_controller.service_item data = [] if current_item: for index, frame in enumerate(current_item.get_frames()): item = {} # Handle text (songs, custom, bibles) if current_item.is_text(): if frame['verseTag']: item['tag'] = str(frame['verseTag']) else: item['tag'] = str(index + 1) item['text'] = str(frame['text']) item['html'] = str(frame['html']) # Handle images, unless a custom thumbnail is given or if thumbnails is disabled elif current_item.is_image() and not frame.get( 'image', '') and Settings().value('remotes/thumbnails'): item['tag'] = str(index + 1) thumbnail_path = os.path.join('images', 'thumbnails', frame['title']) full_thumbnail_path = os.path.join( AppLocation.get_data_path(), thumbnail_path) # Create thumbnail if it doesn't exists if not os.path.exists(full_thumbnail_path): create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False) item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path) item['text'] = str(frame['title']) item['html'] = str(frame['title']) else: # Handle presentation etc. item['tag'] = str(index + 1) if current_item.is_capable( ItemCapabilities.HasDisplayTitle): item['title'] = str(frame['display_title']) if current_item.is_capable(ItemCapabilities.HasNotes): item['slide_notes'] = str(frame['notes']) if current_item.is_capable(ItemCapabilities.HasThumbnails) and \ Settings().value('remotes/thumbnails'): # If the file is under our app directory tree send the portion after the match data_path = AppLocation.get_data_path() if frame['image'][0:len(data_path)] == data_path: item['img'] = urllib.request.pathname2url( frame['image'][len(data_path):]) item['text'] = str(frame['title']) item['html'] = str(frame['title']) item['selected'] = (self.live_controller.selected_row == index) data.append(item) json_data = {'results': {'slides': data}} if current_item: json_data['results'][ 'item'] = self.live_controller.service_item.unique_identifier self.do_json_header() return json.dumps(json_data).encode()
def setup(self): """ Set up and build the output screen """ self.log_debug('Start MainDisplay setup (live = %s)' % self.is_live) self.screen = self.screens.current self.setVisible(False) Display.setup(self) if self.is_live: # Build the initial frame. background_color = QtGui.QColor() background_color.setNamedColor(Settings().value('advanced/default color')) if not background_color.isValid(): background_color = QtCore.Qt.white image_file = Settings().value('advanced/default image') splash_image = QtGui.QImage(image_file) self.initial_fame = QtGui.QImage( self.screen['size'].width(), self.screen['size'].height(), QtGui.QImage.Format_ARGB32_Premultiplied) painter_image = QtGui.QPainter() painter_image.begin(self.initial_fame) painter_image.fillRect(self.initial_fame.rect(), background_color) painter_image.drawImage( (self.screen['size'].width() - splash_image.width()) // 2, (self.screen['size'].height() - splash_image.height()) // 2, splash_image) service_item = ServiceItem() service_item.bg_image_bytes = image_to_byte(self.initial_fame) self.web_view.setHtml(build_html(service_item, self.screen, self.is_live, None, plugins=self.plugin_manager.plugins)) self._hide_mouse()
def settings_override_test(self): """ Test the Settings creation and its override usage """ # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', } Settings().extend_default_settings(screen_settings) # WHEN reading a setting for the first time extend = Settings().value('test/extend') # THEN the default value is returned self.assertEqual( 'very wide', extend, 'The default value of "very wide" should be returned') # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def on_save_report_button_clicked(self): """ Saving exception log and system information to a file. """ filename = QtWidgets.QFileDialog.getSaveFileName( self, translate('OpenLP.ExceptionForm', 'Save Crash Report'), Settings().value(self.settings_section + '/last directory'), translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))[0] if filename: filename = str(filename).replace('/', os.path.sep) Settings().setValue(self.settings_section + '/last directory', os.path.dirname(filename)) opts = self._create_report() report_text = self.report_text.format( version=opts['version'], description=opts['description'], traceback=opts['traceback'], libs=opts['libs'], system=opts['system']) try: report_file = open(filename, 'w') try: report_file.write(report_text) except UnicodeError: report_file.close() report_file = open(filename, 'wb') report_file.write(report_text.encode('utf-8')) finally: report_file.close() except IOError: log.exception('Failed to write crash report') finally: report_file.close()
def test_add_action_different_context(self): """ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and both have the QtCore.Qt.WidgetShortcut shortcut context set. """ # GIVEN: Two actions with the same shortcuts. parent = QtCore.QObject() action3 = QtGui.QAction(parent) action3.setObjectName('action3') action3.setShortcutContext(QtCore.Qt.WidgetShortcut) second_parent = QtCore.QObject() action_with_same_shortcuts3 = QtGui.QAction(second_parent) action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3') action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut) # Add default shortcuts to Settings class. default_shortcuts = { 'shortcuts/action3': [QtGui.QKeySequence('e'), QtGui.QKeySequence('f')], 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence('e'), QtGui.QKeySequence('f')] } Settings.extend_default_settings(default_shortcuts) # WHEN: Add the two actions to the action list. self.action_list.add_action(action3, 'example_category2') self.action_list.add_action(action_with_same_shortcuts3, 'example_category2') # Remove the actions again. self.action_list.remove_action(action3, 'example_category2') self.action_list.remove_action(action_with_same_shortcuts3, 'example_category2') # THEN: Both action should keep their shortcuts. assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.' assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.'
def click_save_credentials_button_test(self): """ Test that we try and save credentials, but they are rejected because they are bad. I do not want to store valid credentials in here because this source is openly available. """ # GIVEN: An instance of the PlanningCenterAuthForm with patch('PyQt5.QtWidgets.QDialog.exec'): # WHEN: The form is shown self.form.exec() # WHEN: The "delete credentials" button is clicked with new credentials set application_id = 'def' secret = '456' self.form.application_id_line_edit.setText(application_id) self.form.secret_line_edit.setText(secret) with patch('PyQt5.QtWidgets.QMessageBox'): QtTest.QTest.mouseClick(self.form.save_credentials_button, QtCore.Qt.LeftButton) # THEN: We should test credentials and validate that they did not get saved self.assertNotEqual( Settings().value('planningcenter/application_id'), application_id, 'The application_id should not have been saved') self.assertNotEqual(Settings().value('planningcenter/secret'), secret, 'The secret code should not have been saved')
def test_add_action_different_context(self): """ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and both have the QtCore.Qt.WidgetShortcut shortcut context set. """ # GIVEN: Two actions with the same shortcuts. parent = QtCore.QObject() action3 = QtWidgets.QAction(parent) action3.setObjectName('action3') action3.setShortcutContext(QtCore.Qt.WidgetShortcut) second_parent = QtCore.QObject() action_with_same_shortcuts3 = QtWidgets.QAction(second_parent) action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3') action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut) # Add default shortcuts to Settings class. default_shortcuts = { 'shortcuts/action3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)], 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)] } Settings.extend_default_settings(default_shortcuts) # WHEN: Add the two actions to the action list. self.action_list.add_action(action3, 'example_category2') self.action_list.add_action(action_with_same_shortcuts3, 'example_category2') # Remove the actions again. self.action_list.remove_action(action3, 'example_category2') self.action_list.remove_action(action_with_same_shortcuts3, 'example_category2') # THEN: Both action should keep their shortcuts. assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.' assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.'
def test_backup_on_upgrade_first_install(self): """ Test that we don't try to backup on a new install """ # GIVEN: Mocked data version and OpenLP version which are the same old_install = False MOCKED_VERSION = { 'full': '2.2.0-bzr000', 'version': '2.2.0', 'build': 'bzr000' } Settings().setValue('core/application version', '2.2.0') with patch('openlp.core.get_application_version') as mocked_get_application_version,\ patch('openlp.core.QtWidgets.QMessageBox.question') as mocked_question: mocked_get_application_version.return_value = MOCKED_VERSION mocked_question.return_value = QtWidgets.QMessageBox.No # WHEN: We check if a backup should be created self.openlp.backup_on_upgrade(old_install) # THEN: It should not ask if we want to create a backup self.assertEqual(Settings().value('core/application version'), '2.2.0', 'Version should be the same!') self.assertEqual(mocked_question.call_count, 0, 'No question should have been asked!')
def test_add_action_different_parent(self): """ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and both have the QtCore.Qt.WindowShortcut shortcut context set. """ # GIVEN: Two actions with the same shortcuts. parent = QtCore.QObject() action2 = QtGui.QAction(parent) action2.setObjectName('action2') second_parent = QtCore.QObject() action_with_same_shortcuts2 = QtGui.QAction(second_parent) action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2') # Add default shortcuts to Settings class. default_shortcuts = { 'shortcuts/action2': [QtGui.QKeySequence('c'), QtGui.QKeySequence('d')], 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence('d'), QtGui.QKeySequence('c')] } Settings.extend_default_settings(default_shortcuts) # WHEN: Add the two actions to the action list. self.action_list.add_action(action2, 'example_category') self.action_list.add_action(action_with_same_shortcuts2, 'example_category') # Remove the actions again. self.action_list.remove_action(action2, 'example_category') self.action_list.remove_action(action_with_same_shortcuts2, 'example_category') # THEN: As both actions have the same shortcuts, they should be removed from one action. assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.' assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
def poll(self): """ Poll OpenLP to determine the current slide number and item name. """ result = { 'service': self.service_manager.service_id, 'slide': self.live_controller.selected_row or 0, 'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '', 'twelve': Settings().value('remotes/twelve hour'), 'blank': self.live_controller.blank_screen.isChecked(), 'theme': self.live_controller.theme_screen.isChecked(), 'display': self.live_controller.desktop_screen.isChecked(), 'version': 2, 'isSecure': Settings().value(self.settings_section + '/authentication enabled'), 'isAuthorised': self.authorised } self.do_json_header() return json.dumps({'results': result}).encode()
def on_delete_credentials_button_clicked(self): """ Deletes the credentials """ Settings().setValue("planningcenter/application_id", '') Settings().setValue("planningcenter/secret", '') self.done(1)
def _perform_wizard(self): """ Run the tasks in the wizard. """ # Set plugin states self._increment_progress_bar(translate('OpenLP.FirstTimeWizard', 'Enabling selected plugins...')) self._set_plugin_status(self.songs_check_box, 'songs/status') self._set_plugin_status(self.bible_check_box, 'bibles/status') self._set_plugin_status(self.presentation_check_box, 'presentations/status') self._set_plugin_status(self.image_check_box, 'images/status') self._set_plugin_status(self.media_check_box, 'media/status') self._set_plugin_status(self.remote_check_box, 'remotes/status') self._set_plugin_status(self.custom_check_box, 'custom/status') self._set_plugin_status(self.song_usage_check_box, 'songusage/status') self._set_plugin_status(self.alert_check_box, 'alerts/status') if self.web_access: if not self._download_selected(): critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), translate('OpenLP.FirstTimeWizard', 'There was a connection problem while ' 'downloading, so further downloads will be skipped. Try to re-run ' 'the First Time Wizard later.')) # Set Default Display if self.display_combo_box.currentIndex() != -1: Settings().setValue('core/monitor', self.display_combo_box.currentIndex()) self.screens.set_current_display(self.display_combo_box.currentIndex()) # Set Global Theme if self.theme_combo_box.currentIndex() != -1: Settings().setValue('themes/global theme', self.theme_combo_box.currentText())
def on_export_theme(self, field=None): """ Export the theme in a zip file :param field: """ item = self.theme_list_widget.currentItem() if item is None: critical_error_message_box(message=translate( 'OpenLP.ThemeManager', 'You have not selected a theme.')) return theme = item.data(QtCore.Qt.UserRole) path = QtWidgets.QFileDialog.getExistingDirectory( self, translate('OpenLP.ThemeManager', 'Save Theme - (%s)') % theme, Settings().value(self.settings_section + '/last directory export')) self.application.set_busy_cursor() if path: Settings().setValue( self.settings_section + '/last directory export', path) if self._export_theme(path, theme): QtWidgets.QMessageBox.information( self, translate('OpenLP.ThemeManager', 'Theme Exported'), translate('OpenLP.ThemeManager', 'Your theme has been successfully exported.')) self.application.set_normal_cursor()
def __init__(self): """ Constructor """ super(PrintServiceForm, self).__init__(Registry().get('main_window')) self.printer = QtGui.QPrinter() self.print_dialog = QtGui.QPrintDialog(self.printer, self) self.document = QtGui.QTextDocument() self.zoom = 0 self.setupUi(self) # Load the settings for the dialog. settings = Settings() settings.beginGroup('advanced') self.slide_text_check_box.setChecked(settings.value('print slide text')) self.page_break_after_text.setChecked(settings.value('add page break')) if not self.slide_text_check_box.isChecked(): self.page_break_after_text.setDisabled(True) self.meta_data_check_box.setChecked(settings.value('print file meta data')) self.notes_check_box.setChecked(settings.value('print notes')) self.zoom_combo_box.setCurrentIndex(settings.value('display size')) settings.endGroup() # Signals self.print_button.triggered.connect(self.print_service_order) self.zoom_out_button.clicked.connect(self.zoom_out) self.zoom_in_button.clicked.connect(self.zoom_in) self.zoom_original_button.clicked.connect(self.zoom_original) self.preview_widget.paintRequested.connect(self.paint_requested) self.zoom_combo_box.currentIndexChanged.connect(self.display_size_changed) self.plain_copy.triggered.connect(self.copy_text) self.html_copy.triggered.connect(self.copy_html_text) self.slide_text_check_box.stateChanged.connect(self.on_slide_text_check_box_changed) self.update_preview_text()
def setUp(self): """ Prepare the tests """ self.action_list = ActionList.get_instance() self.build_settings() self.settings = Settings() self.settings.beginGroup('shortcuts')
def generate_footer(self, item, song): """ Generates the song footer based on a song and adds details to a service item. :param item: The service item to be amended :param song: The song to be used to generate the footer :return: List of all authors (only required for initial song generation) """ authors_words = [] authors_music = [] authors_words_music = [] authors_translation = [] authors_none = [] for author_song in song.authors_songs: if author_song.author_type == AuthorType.Words: authors_words.append(author_song.author.display_name) elif author_song.author_type == AuthorType.Music: authors_music.append(author_song.author.display_name) elif author_song.author_type == AuthorType.WordsAndMusic: authors_words_music.append(author_song.author.display_name) elif author_song.author_type == AuthorType.Translation: authors_translation.append(author_song.author.display_name) else: authors_none.append(author_song.author.display_name) authors_all = authors_words_music + authors_words + authors_music + authors_translation + authors_none item.audit = [ song.title, authors_all, song.copyright, str(song.ccli_number) ] item.raw_footer = [] item.raw_footer.append(song.title) if authors_none: item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), authors=create_separated_list(authors_none))) if authors_words_music: item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic], authors=create_separated_list(authors_words_music))) if authors_words: item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Words], authors=create_separated_list(authors_words))) if authors_music: item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Music], authors=create_separated_list(authors_music))) if authors_translation: item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation], authors=create_separated_list(authors_translation))) if song.copyright: if self.display_copyright_symbol: item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, song=song.copyright)) else: item.raw_footer.append(song.copyright) if self.display_songbook and song.songbook_entries: songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries] item.raw_footer.append(", ".join(songbooks)) if Settings().value('core/ccli number'): item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + Settings().value('core/ccli number')) return authors_all
def config_update(self): """ Config has been updated so reload values """ log.debug('Config loaded') self.add_custom_from_service = Settings().value( self.settings_section + '/add custom from service') self.is_search_as_you_type_enabled = Settings().value( 'advanced/search as type')
def on_double_clicked(self): """ Allows the list click action to be determined dynamically """ if Settings().value('advanced/double click live'): self.on_live_click() elif not Settings().value('advanced/single click preview'): # NOTE: The above check is necessary to prevent bug #1419300 self.on_preview_click()
def build_settings(self): """ Build the settings Object and initialise it """ self.fd, self.ini_file = mkstemp('.ini') Settings.set_filename(self.ini_file) Settings().setDefaultFormat(Settings.IniFormat) # Needed on windows to make sure a Settings object is available during the tests self.setting = Settings() Settings().setValue('themes/global theme', 'my_theme')
def define_output_location(self): """ Triggered when the Directory selection button is clicked """ path = QtWidgets.QFileDialog.getExistingDirectory( self, translate('SongUsagePlugin.SongUsageDetailForm', 'Output File Location'), Settings().value(self.plugin.settings_section + '/last directory export')) if path: Settings().setValue(self.plugin.settings_section + '/last directory export', path) self.file_line_edit.setText(path)
def config_update(self): """ Is triggered when the songs config is updated """ log.debug('config_updated') self.is_search_as_you_type_enabled = Settings().value('advanced/search as type') self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') self.display_songbook = Settings().value(self.settings_section + '/display songbook') self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol')
def __init__(self, parent=None, plugin=None): QtWidgets.QDialog.__init__( self, parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) self.plugin = plugin # create an Planning Center API Object application_id = Settings().value("planningcenter/application_id") secret = Settings().value("planningcenter/secret") self.planning_center_api = PlanningCenterAPI(application_id, secret) self.setup_ui(self)
def test_settings_override_with_group(self): """ Test the Settings creation and its override usage - with groups """ # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', } Settings.extend_default_settings(screen_settings) # WHEN reading a setting for the first time settings = Settings() settings.beginGroup('test') extend = settings.value('extend') # THEN the default value is returned self.assertEqual('very wide', extend, 'The default value defined should be returned') # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def check_installed(self): """ Check the viewer is installed. :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') self.mudrawbin = '' self.mutoolbin = '' self.gsbin = '' self.also_supports = [] # Use the user defined program if given if Settings().value('presentations/enable_pdf_program'): pdf_program = Settings().value('presentations/pdf_program') program_type = self.check_binary(pdf_program) if program_type == 'gs': self.gsbin = pdf_program elif program_type == 'mudraw': self.mudrawbin = pdf_program elif program_type == 'mutool': self.mutoolbin = pdf_program else: # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) if is_win(): # for windows we only accept mudraw.exe or mutool.exe in the base folder application_path = AppLocation.get_directory(AppLocation.AppDir) if os.path.isfile(os.path.join(application_path, 'mudraw.exe')): self.mudrawbin = os.path.join(application_path, 'mudraw.exe') elif os.path.isfile(os.path.join(application_path, 'mutool.exe')): self.mutoolbin = os.path.join(application_path, 'mutool.exe') else: DEVNULL = open(os.devnull, 'wb') # First try to find mudraw self.mudrawbin = which('mudraw') # if mudraw isn't installed, try mutool if not self.mudrawbin: self.mutoolbin = which('mutool') # Check we got a working mutool if not self.mutoolbin or self.check_binary(self.mutoolbin) != 'mutool': self.gsbin = which('gs') # Last option: check if mudraw or mutool is placed in OpenLP base folder if not self.mudrawbin and not self.mutoolbin and not self.gsbin: application_path = AppLocation.get_directory(AppLocation.AppDir) if os.path.isfile(os.path.join(application_path, 'mudraw')): self.mudrawbin = os.path.join(application_path, 'mudraw') elif os.path.isfile(os.path.join(application_path, 'mutool')): self.mutoolbin = os.path.join(application_path, 'mutool') if self.mudrawbin or self.mutoolbin: self.also_supports = ['xps', 'oxps'] return True elif self.gsbin: return True else: return False
def __init__(self, name, default_settings, media_item_class=None, settings_tab_class=None, version=None): """ This is the constructor for the plugin object. This provides an easy way for descendant plugins to populate common data. This method *must* be overridden, like so:: class MyPlugin(Plugin): def __init__(self): super(MyPlugin, self).__init__('MyPlugin', version='0.1') :param name: Defaults to *None*. The name of the plugin. :param default_settings: A dict containing the plugin's settings. The value to each key is the default value to be used. :param media_item_class: The class name of the plugin's media item. :param settings_tab_class: The class name of the plugin's settings tab. :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number. """ log.debug('Plugin %s initialised' % name) super(Plugin, self).__init__() self.name = name self.text_strings = {} self.set_plugin_text_strings() self.name_strings = self.text_strings[StringContent.Name] if version: self.version = version else: self.version = get_application_version()['version'] self.settings_section = self.name self.icon = None self.media_item_class = media_item_class self.settings_tab_class = settings_tab_class self.settings_tab = None self.media_item = None self.weight = 0 self.status = PluginStatus.Inactive # Add the default status to the default settings. default_settings[name + '/status'] = PluginStatus.Inactive default_settings[name + '/last directory'] = '' # Append a setting for files in the mediamanager (note not all plugins # which have a mediamanager need this). if media_item_class is not None: default_settings['%s/%s files' % (name, name)] = [] # Add settings to the dict of all settings. Settings.extend_default_settings(default_settings) Registry().register_function('%s_add_service_item' % self.name, self.process_add_service_event) Registry().register_function('%s_config_updated' % self.name, self.config_update)
def get_data_path(): """ Return the path OpenLP stores all its data under. """ # Check if we have a different data location. if Settings().contains('advanced/data path'): path = Settings().value('advanced/data path') else: path = AppLocation.get_directory(AppLocation.DataDir) check_directory_exists(path) return os.path.normpath(path)
def setUp(self): """ Some pre-test setup required. """ Settings.setDefaultFormat(Settings.IniFormat) self.build_settings() self.temp_dir = mkdtemp('openlp') Settings().setValue('advanced/data path', self.temp_dir) Registry.create() Registry().register('service_list', MagicMock()) self.setup_application() self.main_window = QtGui.QMainWindow() Registry().register('main_window', self.main_window)
def update_reference_separators(): """ Updates separators and matches for parsing and formating scripture references. """ default_separators = [ '|'.join([ translate('BiblesPlugin', ':', 'Verse identifier e.g. Genesis 1 : 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'v', 'Verse identifier e.g. Genesis 1 v 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'V', 'Verse identifier e.g. Genesis 1 V 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'verse', 'Verse identifier e.g. Genesis 1 verse 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'verses', 'Verse identifier e.g. Genesis 1 verses 1 - 2 = Genesis Chapter 1 Verses 1 to 2')]), '|'.join([ translate('BiblesPlugin', '-', 'range identifier e.g. Genesis 1 verse 1 - 2 = Genesis Chapter 1 Verses 1 To 2'), translate('BiblesPlugin', 'to', 'range identifier e.g. Genesis 1 verse 1 - 2 = Genesis Chapter 1 Verses 1 To 2')]), '|'.join([ translate('BiblesPlugin', ',', 'connecting identifier e.g. Genesis 1 verse 1 - 2, 4 - 5 = ' 'Genesis Chapter 1 Verses 1 To 2 And Verses 4 To 5'), translate('BiblesPlugin', 'and', 'connecting identifier e.g. Genesis 1 verse 1 - 2 and 4 - 5 = ' 'Genesis Chapter 1 Verses 1 To 2 And Verses 4 To 5')]), '|'.join([translate('BiblesPlugin', 'end', 'ending identifier e.g. Genesis 1 verse 1 - end = ' 'Genesis Chapter 1 Verses 1 To The Last Verse')])] settings = Settings() settings.beginGroup('bibles') custom_separators = [ settings.value('verse separator'), settings.value('range separator'), settings.value('list separator'), settings.value('end separator')] settings.endGroup() for index, role in enumerate(['v', 'r', 'l', 'e']): if custom_separators[index].strip('|') == '': source_string = default_separators[index].strip('|') else: source_string = custom_separators[index].strip('|') while '||' in source_string: source_string = source_string.replace('||', '|') if role != 'e': REFERENCE_SEPARATORS['sep_%s_display' % role] = source_string.split('|')[0] # escape reserved characters for character in '\\.^$*+?{}[]()': source_string = source_string.replace(character, '\\' + character) # add various unicode alternatives source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])') source_string = source_string.replace(',', '(?:[,\u201A])') REFERENCE_SEPARATORS['sep_%s' % role] = '\s*(?:%s)\s*' % source_string REFERENCE_SEPARATORS['sep_%s_default' % role] = default_separators[index] # verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)? range_regex = '(?:(?P<from_chapter>[0-9]+)%(sep_v)s)?' \ '(?P<from_verse>[0-9]+)(?P<range_to>%(sep_r)s(?:(?:(?P<to_chapter>' \ '[0-9]+)%(sep_v)s)?(?P<to_verse>[0-9]+)|%(sep_e)s)?)?' % REFERENCE_SEPARATORS REFERENCE_MATCHES['range'] = re.compile('^\s*%s\s*$' % range_regex, re.UNICODE) REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) # full reference match: <book>(<range>(,(?!$)|(?=$)))+ REFERENCE_MATCHES['full'] = \ re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*' '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$' % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)
def load(self): """ Load the projector settings on startup """ settings = Settings() settings.beginGroup(self.settings_section) self.connect_on_startup.setChecked(settings.value('connect on start')) self.socket_timeout_spin_box.setValue(settings.value('socket timeout')) self.socket_poll_spin_box.setValue(settings.value('poll time')) self.dialog_type_combo_box.setCurrentIndex(settings.value('source dialog type')) settings.endGroup()
def extend_default_settings_test(self): """ Test that the extend_default_settings method extends the default settings """ # GIVEN: A patched __default_settings__ dictionary with patch.dict(Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True): # WHEN: Calling extend_default_settings Settings.extend_default_settings({'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2}) # THEN: The _default_settings__ dictionary_ should have the new keys self.assertEqual( Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, 'test/extended 1': 1, 'test/extended 2': 2})
def save(self): """ Save the Dialog settings """ settings = Settings() settings.beginGroup(self.settings_section) settings.setValue('display footer', self.display_footer) settings.setValue('add custom from service', self.update_load) settings.endGroup() if self.tab_visited: self.settings_form.register_post_process('custom_config_updated') self.tab_visited = False
def get_settings(self): """ Retrieve the saved settings """ settings = Settings() settings.beginGroup(self.settings_section) self.autostart = settings.value('connect on start') self.poll_time = settings.value('poll time') self.socket_timeout = settings.value('socket timeout') self.source_select_dialog_type = settings.value('source dialog type') settings.endGroup() del settings
def get_list(self, type=MediaType.Audio): """ Get the list of media, optional select media type. :param type: Type to get, defaults to audio. :return: The media list """ media = Settings().value(self.settings_section + '/media files') media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1])) if type == MediaType.Audio: extension = self.media_controller.audio_extensions_list else: extension = self.media_controller.video_extensions_list extension = [x[1:] for x in extension] media = [x for x in media if os.path.splitext(x)[1] in extension] return media
def get_media_players(): """ This method extracts the configured media players and overridden player from the settings. """ log.debug('get_media_players') saved_players = Settings().value('media/players') reg_ex = QtCore.QRegExp(".*\[(.*)\].*") if Settings().value('media/override player') == QtCore.Qt.Checked: if reg_ex.exactMatch(saved_players): overridden_player = '{text}'.format(text=reg_ex.cap(1)) else: overridden_player = 'auto' else: overridden_player = '' saved_players_list = saved_players.replace('[', '').replace(']', '').split(',') if saved_players else [] return saved_players_list, overridden_player
def set_defaults(self): """ Set default values for the wizard pages. """ settings = Settings() settings.beginGroup(self.plugin.settings_section) self.restart() self.finish_button.setVisible(False) self.cancel_button.setVisible(True) self.setField("source_format", 0) self.setField("osis_location", "") self.setField("csv_booksfile", "") self.setField("csv_versefile", "") self.setField("opensong_file", "") self.setField("zefania_file", "") self.setField("web_location", WebDownload.Crosswalk) self.setField("web_biblename", self.web_translation_combo_box.currentIndex()) self.setField("proxy_server", settings.value("proxy address")) self.setField("proxy_username", settings.value("proxy username")) self.setField("proxy_password", settings.value("proxy password")) self.setField("license_version", self.version_name_edit.text()) self.setField("license_copyright", self.copyright_edit.text()) self.setField("license_permissions", self.permissions_edit.text()) self.on_web_source_combo_box_index_changed(WebDownload.Crosswalk) settings.endGroup()
def save(self): settings = Settings() settings.beginGroup(self.settings_section) settings.setValue('background color', self.background_color) settings.endGroup() if self.initial_color != self.background_color: self.settings_form.register_post_process('images_config_updated')
def add_action(self, action, category=None, weight=None): """ Add an action to the list of actions. **Note**: The action's objectName must be set when you want to add it! :param action: The action to add (QAction). **Note**, the action must not have an empty ``objectName``. :param category: The category this action belongs to. The category has to be a python string. . **Note**, if the category is ``None``, the category and its actions are being hidden in the shortcut dialog. However, if they are added, it is possible to avoid assigning shortcuts twice, which is important. :param weight: The weight specifies how important a category is. However, this only has an impact on the order the categories are displayed. """ if category not in self.categories: self.categories.append(category) settings = Settings() settings.beginGroup('shortcuts') # Get the default shortcut from the config. action.default_shortcuts = settings.get_default_value(action.objectName()) if weight is None: self.categories[category].actions.append(action) else: self.categories[category].actions.add(action, weight) # Load the shortcut from the config. shortcuts = settings.value(action.objectName()) settings.endGroup() if not shortcuts: action.setShortcuts([]) return # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O, # which is only done when we convert the strings in this way (QKeySequencet -> uncode). shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts)))) # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut # after removing the (initial) primary shortcut due to conflicts. if len(shortcuts) == 2: existing_actions = ActionList.shortcut_map.get(shortcuts[1], []) # Check for conflicts with other actions considering the shortcut context. if self._is_shortcut_available(existing_actions, action): actions = ActionList.shortcut_map.get(shortcuts[1], []) actions.append(action) ActionList.shortcut_map[shortcuts[1]] = actions else: log.warning('Shortcut "{shortcut}" is removed from "{action}" because another ' 'action already uses this shortcut.'.format(shortcut=shortcuts[1], action=action.objectName())) shortcuts.remove(shortcuts[1]) # Check the primary shortcut. existing_actions = ActionList.shortcut_map.get(shortcuts[0], []) # Check for conflicts with other actions considering the shortcut context. if self._is_shortcut_available(existing_actions, action): actions = ActionList.shortcut_map.get(shortcuts[0], []) actions.append(action) ActionList.shortcut_map[shortcuts[0]] = actions else: log.warning('Shortcut "{shortcut}" is removed from "{action}" ' 'because another action already uses this shortcut.'.format(shortcut=shortcuts[0], action=action.objectName())) shortcuts.remove(shortcuts[0]) action.setShortcuts([QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
def init_url(plugin_name, db_file_name=None): """ Return the database URL. :param plugin_name: The name of the plugin for the database creation. :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used. """ settings = Settings() settings.beginGroup(plugin_name) db_type = settings.value('db type') if db_type == 'sqlite': db_url = get_db_path(plugin_name, db_file_name) else: db_url = '{type}://{user}:{password}@{host}/{db}'.format(type=db_type, user=urlquote(settings.value('db username')), password=urlquote(settings.value('db password')), host=urlquote(settings.value('db hostname')), db=urlquote(settings.value('db database'))) settings.endGroup() return db_url
def check_latest_version(current_version): """ Check the latest version of OpenLP against the version file on the OpenLP site. **Rules around versions and version files:** * If a version number has a build (i.e. -bzr1234), then it is a nightly. * If a version number's minor version is an odd number, it is a development release. * If a version number's minor version is an even number, it is a stable release. :param current_version: The current version of OpenLP. """ version_string = current_version["full"] # set to prod in the distribution config file. settings = Settings() settings.beginGroup("core") last_test = settings.value("last version test") this_test = str(datetime.now().date()) settings.setValue("last version test", this_test) settings.endGroup() if last_test != this_test: if current_version["build"]: req = urllib.request.Request("http://www.openlp.org/files/nightly_version.txt") else: version_parts = current_version["version"].split(".") if int(version_parts[1]) % 2 != 0: req = urllib.request.Request("http://www.openlp.org/files/dev_version.txt") else: req = urllib.request.Request("http://www.openlp.org/files/version.txt") req.add_header( "User-Agent", "OpenLP/%s %s/%s; " % (current_version["full"], platform.system(), platform.release()) ) remote_version = None retries = 0 while True: try: remote_version = str( urllib.request.urlopen(req, None, timeout=CONNECTION_TIMEOUT).read().decode() ).strip() except (urllib.error.URLError, ConnectionError): if retries > CONNECTION_RETRIES: log.exception("Failed to download the latest OpenLP version file") else: retries += 1 time.sleep(0.1) continue break if remote_version: version_string = remote_version return version_string
def set_defaults(self): """ Set default values for the wizard pages. """ log.debug('BibleUpgrade set_defaults') settings = Settings() settings.beginGroup(self.plugin.settings_section) self.stop_import_flag = False self.success.clear() self.new_bibles.clear() self.clearScrollArea() self.files = self.manager.old_bible_databases self.addScrollArea() self.retranslateUi() for number, filename in enumerate(self.files): self.checkBox[number].setCheckState(QtCore.Qt.Checked) self.progress_bar.show() self.restart() self.finish_button.setVisible(False) self.cancel_button.setVisible(True) settings.endGroup()
def load(self): settings = Settings() settings.beginGroup(self.settings_section) self.background_color = settings.value('background color') self.initial_color = self.background_color settings.endGroup() self.background_color_button.color = self.background_color
def load(self): """ Load the settings into the dialog """ settings = Settings() settings.beginGroup(self.settings_section) self.display_footer = settings.value('display footer') self.update_load = settings.value('add custom from service') self.display_footer_check_box.setChecked(self.display_footer) self.add_from_service_checkbox.setChecked(self.update_load) settings.endGroup()
def init_url(plugin_name, db_file_name=None): """ Return the database URL. :param plugin_name: The name of the plugin for the database creation. :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used. """ settings = Settings() settings.beginGroup(plugin_name) db_type = settings.value('db type') if db_type == 'sqlite': db_url = get_db_path(plugin_name, db_file_name) else: db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')), urlquote(settings.value('db password')), urlquote(settings.value('db hostname')), urlquote(settings.value('db database'))) settings.endGroup() return db_url
def load(self): """ Load the theme settings into the tab """ settings = Settings() settings.beginGroup(self.settings_section) self.theme_level = settings.value('theme level') self.global_theme = settings.value('global theme') self.wrap_footer_check_box.setChecked(settings.value('wrap footer')) settings.endGroup() if self.theme_level == ThemeLevel.Global: self.global_level_radio_button.setChecked(True) elif self.theme_level == ThemeLevel.Service: self.service_level_radio_button.setChecked(True) else: self.song_level_radio_button.setChecked(True)
def save(self): """ Save the settings """ settings = Settings() settings.beginGroup(self.settings_section) settings.setValue('background color', self.background_color) settings.endGroup() old_players, override_player = get_media_players() if self.used_players != old_players: # clean old Media stuff set_media_players(self.used_players, override_player) self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_media_rebuild') self.settings_form.register_post_process('config_screen_changed')
def save(self): """ Save the shortcuts. **Note**, that we do not have to load the shortcuts, as they are loaded in :class:`~openlp.core.utils.ActionList`. """ settings = Settings() settings.beginGroup('shortcuts') for category in self.action_list.categories: # Check if the category is for internal use only. if category.name is None: continue for action in category.actions: if action in self.changed_actions: old_shortcuts = list(map(self.get_shortcut_string, action.shortcuts())) action.setShortcuts(self.changed_actions[action]) self.action_list.update_shortcut_map(action, old_shortcuts) settings.setValue(action.objectName(), action.shortcuts()) settings.endGroup()
def load(self): """ Load the settings """ if self.saved_used_players: self.used_players = self.saved_used_players self.used_players = get_media_players()[0] self.saved_used_players = self.used_players settings = Settings() settings.beginGroup(self.settings_section) self.update_player_list() self.background_color = settings.value('background color') self.initial_color = self.background_color settings.endGroup() self.background_color_button.color = self.background_color
def display_size_changed(self, display): """ The Zoom Combo box has changed so set up the size. """ if display == ZoomSize.Page: self.preview_widget.fitInView() elif display == ZoomSize.Width: self.preview_widget.fitToWidth() elif display == ZoomSize.OneHundred: self.preview_widget.fitToWidth() self.preview_widget.zoomIn(1) elif display == ZoomSize.SeventyFive: self.preview_widget.fitToWidth() self.preview_widget.zoomIn(0.75) elif display == ZoomSize.Fifty: self.preview_widget.fitToWidth() self.preview_widget.zoomIn(0.5) elif display == ZoomSize.TwentyFive: self.preview_widget.fitToWidth() self.preview_widget.zoomIn(0.25) settings = Settings() settings.beginGroup('advanced') settings.setValue('display size', display) settings.endGroup()
def settings_override_with_group_test(self): """ Test the Settings creation and its override usage - with groups """ # GIVEN: an override for the settings screen_settings = { 'test/extend': 'very wide', } Settings.extend_default_settings(screen_settings) # WHEN reading a setting for the first time settings = Settings() settings.beginGroup('test') extend = settings.value('extend') # THEN the default value is returned self.assertEqual('very wide', extend, 'The default value defined should be returned') # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')