diff --git a/.bzrignore b/.bzrignore index 6377150e0..a0c3f0b4f 100644 --- a/.bzrignore +++ b/.bzrignore @@ -32,3 +32,4 @@ tests.kdev4 *.orig __pycache__ *.dll +.directory diff --git a/README.txt b/README.txt index b937e1d5f..04294b1b8 100644 --- a/README.txt +++ b/README.txt @@ -1,16 +1,15 @@ -OpenLP 2.0 -========== +OpenLP +====== You're probably reading this because you've just downloaded the source code for -OpenLP 2.0. If you are looking for the installer file, please go to the download +OpenLP. If you are looking for the installer file, please go to the download page on the web site:: - http://openlp.org/en/download.html + http://openlp.org/download If you're looking for how to contribute to OpenLP, then please look at the OpenLP wiki:: http://wiki.openlp.org/ -Thanks for downloading OpenLP 2.0! - +Thanks for downloading OpenLP! diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index 276a073bb..251e0657c 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -44,7 +44,7 @@ class Ui_AboutDialog(object): Set up the UI for the dialog. """ about_dialog.setObjectName('about_dialog') - about_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) + about_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) self.about_dialog_layout = QtGui.QVBoxLayout(about_dialog) self.about_dialog_layout.setObjectName('about_dialog_layout') self.logo_label = QtGui.QLabel(about_dialog) diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index b8b3941cd..329ed7797 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -32,7 +32,7 @@ The GUI widgets of the exception dialog. from PyQt4 import QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, build_icon from openlp.core.lib.ui import create_button, create_button_box @@ -45,6 +45,7 @@ class Ui_ExceptionDialog(object): Set up the UI. """ exception_dialog.setObjectName('exception_dialog') + exception_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.exception_layout = QtGui.QVBoxLayout(exception_dialog) self.exception_layout.setObjectName('exception_layout') self.message_layout = QtGui.QHBoxLayout() diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py index 0900b82c3..4a316aece 100644 --- a/openlp/core/ui/filerenamedialog.py +++ b/openlp/core/ui/filerenamedialog.py @@ -31,7 +31,7 @@ The UI widgets for the rename dialog """ from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +44,7 @@ class Ui_FileRenameDialog(object): Set up the UI """ file_rename_dialog.setObjectName('file_rename_dialog') + file_rename_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) file_rename_dialog.resize(300, 10) self.dialog_layout = QtGui.QGridLayout(file_rename_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py index 10b341c2c..b4263e498 100644 --- a/openlp/core/ui/firsttimelanguagedialog.py +++ b/openlp/core/ui/firsttimelanguagedialog.py @@ -32,6 +32,7 @@ The UI widgets of the language selection dialog. from PyQt4 import QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +45,7 @@ class Ui_FirstTimeLanguageDialog(object): Set up the UI. """ language_dialog.setObjectName('language_dialog') + language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) language_dialog.resize(300, 50) self.dialog_layout = QtGui.QVBoxLayout(language_dialog) self.dialog_layout.setContentsMargins(8, 8, 8, 8) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 1a270f931..35623ded2 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -34,6 +34,7 @@ from PyQt4 import QtCore, QtGui import sys from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import add_welcome_page @@ -60,6 +61,7 @@ class Ui_FirstTimeWizard(object): Set up the UI. """ first_time_wizard.setObjectName('first_time_wizard') + first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) first_time_wizard.resize(550, 386) first_time_wizard.setModal(True) first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle) diff --git a/openlp/core/ui/formattingtagdialog.py b/openlp/core/ui/formattingtagdialog.py index 387bca0a7..569405a05 100644 --- a/openlp/core/ui/formattingtagdialog.py +++ b/openlp/core/ui/formattingtagdialog.py @@ -45,6 +45,7 @@ class Ui_FormattingTagDialog(object): Set up the UI """ formatting_tag_dialog.setObjectName('formatting_tag_dialog') + formatting_tag_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) formatting_tag_dialog.resize(725, 548) self.list_data_grid_layout = QtGui.QVBoxLayout(formatting_tag_dialog) self.list_data_grid_layout.setMargin(8) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 9c193b079..5b5aa0781 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -89,7 +89,7 @@ class Ui_MainWindow(object): Set up the user interface """ main_window.setObjectName('MainWindow') - main_window.setWindowIcon(build_icon(':/icon/openlp-logo-64x64.png')) + main_window.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) main_window.setDockNestingEnabled(True) # Set up the main container, which contains all the other form widgets. self.main_content = QtGui.QWidget(main_window) diff --git a/openlp/core/ui/plugindialog.py b/openlp/core/ui/plugindialog.py index f4d2d991b..40311677e 100644 --- a/openlp/core/ui/plugindialog.py +++ b/openlp/core/ui/plugindialog.py @@ -32,6 +32,7 @@ The UI widgets of the plugin view dialog from PyQt4 import QtCore, QtGui from openlp.core.common import UiStrings, translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +45,7 @@ class Ui_PluginViewDialog(object): Set up the UI """ pluginViewDialog.setObjectName('pluginViewDialog') + pluginViewDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) pluginViewDialog.setWindowModality(QtCore.Qt.ApplicationModal) self.plugin_layout = QtGui.QVBoxLayout(pluginViewDialog) self.plugin_layout.setObjectName('plugin_layout') diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py index f59dcb044..0873a0b4a 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.py @@ -56,6 +56,7 @@ class Ui_PrintServiceDialog(object): Set up the UI """ print_service_dialog.setObjectName('print_service_dialog') + print_service_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) print_service_dialog.resize(664, 594) self.main_layout = QtGui.QVBoxLayout(print_service_dialog) self.main_layout.setSpacing(0) diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py index 76139c875..1fa19eb31 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.py @@ -32,6 +32,7 @@ The UI widgets for the service item edit dialog from PyQt4 import QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box, create_button @@ -44,6 +45,7 @@ class Ui_ServiceItemEditDialog(object): Set up the UI """ serviceItemEditDialog.setObjectName('serviceItemEditDialog') + serviceItemEditDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.dialog_layout = QtGui.QGridLayout(serviceItemEditDialog) self.dialog_layout.setContentsMargins(8, 8, 8, 8) self.dialog_layout.setSpacing(8) diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index f625680d6..fbac6a155 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -45,8 +45,8 @@ class Ui_SettingsDialog(object): Set up the UI """ settings_dialog.setObjectName('settings_dialog') + settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) settings_dialog.resize(800, 500) - settings_dialog.setWindowIcon(build_icon(':/system/system_settings.png')) self.dialog_layout = QtGui.QGridLayout(settings_dialog) self.dialog_layout.setObjectName('dialog_layout') self.dialog_layout.setMargin(8) diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py index 54433da24..ef2dc4056 100644 --- a/openlp/core/ui/shortcutlistdialog.py +++ b/openlp/core/ui/shortcutlistdialog.py @@ -66,6 +66,7 @@ class Ui_ShortcutListDialog(object): Set up the UI """ shortcutListDialog.setObjectName('shortcutListDialog') + shortcutListDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) shortcutListDialog.resize(500, 438) self.shortcut_list_layout = QtGui.QVBoxLayout(shortcutListDialog) self.shortcut_list_layout.setObjectName('shortcut_list_layout') diff --git a/openlp/core/ui/starttimedialog.py b/openlp/core/ui/starttimedialog.py index 54e0629a4..cfd507bb2 100644 --- a/openlp/core/ui/starttimedialog.py +++ b/openlp/core/ui/starttimedialog.py @@ -32,6 +32,7 @@ The UI widgets for the time dialog from PyQt4 import QtCore, QtGui from openlp.core.common import UiStrings, translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +45,7 @@ class Ui_StartTimeDialog(object): Set up the UI """ StartTimeDialog.setObjectName('StartTimeDialog') + StartTimeDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) StartTimeDialog.resize(350, 10) self.dialog_layout = QtGui.QGridLayout(StartTimeDialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py index 023d6259c..d3fbeed2d 100644 --- a/openlp/core/ui/themelayoutdialog.py +++ b/openlp/core/ui/themelayoutdialog.py @@ -32,6 +32,7 @@ The layout of the theme from PyQt4 import QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +45,7 @@ class Ui_ThemeLayoutDialog(object): Set up the UI """ themeLayoutDialog.setObjectName('themeLayoutDialogDialog') + themeLayoutDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.preview_layout = QtGui.QVBoxLayout(themeLayoutDialog) self.preview_layout.setObjectName('preview_layout') self.preview_area = QtGui.QWidget(themeLayoutDialog) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 77ccb0663..bda52c807 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -46,6 +46,7 @@ class Ui_ThemeWizard(object): Set up the UI """ themeWizard.setObjectName('OpenLP.ThemeWizard') + themeWizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) themeWizard.setModal(True) themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) themeWizard.setOptions(QtGui.QWizard.IndependentPages | diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 5996296b5..908981dc6 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -118,6 +118,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties): """ Set up the wizard UI. """ + self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.setModal(True) self.setWizardStyle(QtGui.QWizard.ModernStyle) self.setOptions(QtGui.QWizard.IndependentPages | diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index e4fd29a39..47e61365a 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -46,7 +46,7 @@ class Ui_AlertDialog(object): """ alert_dialog.setObjectName('alert_dialog') alert_dialog.resize(400, 300) - alert_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) + alert_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) self.alert_dialog_layout = QtGui.QGridLayout(alert_dialog) self.alert_dialog_layout.setObjectName('alert_dialog_layout') self.alert_text_layout = QtGui.QFormLayout() diff --git a/openlp/plugins/bibles/forms/booknamedialog.py b/openlp/plugins/bibles/forms/booknamedialog.py index 5903391c3..120caeff0 100644 --- a/openlp/plugins/bibles/forms/booknamedialog.py +++ b/openlp/plugins/bibles/forms/booknamedialog.py @@ -30,12 +30,14 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box class Ui_BookNameDialog(object): def setupUi(self, book_name_dialog): book_name_dialog.setObjectName('book_name_dialog') + book_name_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) book_name_dialog.resize(400, 271) self.book_name_layout = QtGui.QVBoxLayout(book_name_dialog) self.book_name_layout.setSpacing(8) diff --git a/openlp/plugins/bibles/forms/editbibledialog.py b/openlp/plugins/bibles/forms/editbibledialog.py index 1fbaa2f1e..e9e39a053 100644 --- a/openlp/plugins/bibles/forms/editbibledialog.py +++ b/openlp/plugins/bibles/forms/editbibledialog.py @@ -39,8 +39,8 @@ from openlp.plugins.bibles.lib.db import BiblesResourcesDB class Ui_EditBibleDialog(object): def setupUi(self, edit_bible_dialog): edit_bible_dialog.setObjectName('edit_bible_dialog') + edit_bible_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) edit_bible_dialog.resize(520, 400) - edit_bible_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) edit_bible_dialog.setModal(True) self.dialog_layout = QtGui.QVBoxLayout(edit_bible_dialog) self.dialog_layout.setSpacing(8) diff --git a/openlp/plugins/bibles/forms/languagedialog.py b/openlp/plugins/bibles/forms/languagedialog.py index 10382ea13..ab40503d2 100644 --- a/openlp/plugins/bibles/forms/languagedialog.py +++ b/openlp/plugins/bibles/forms/languagedialog.py @@ -30,12 +30,14 @@ from PyQt4 import QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box class Ui_LanguageDialog(object): def setupUi(self, language_dialog): language_dialog.setObjectName('language_dialog') + language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) language_dialog.resize(400, 165) self.language_layout = QtGui.QVBoxLayout(language_dialog) self.language_layout.setSpacing(8) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index dd14c6409..76f91071a 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -60,7 +60,6 @@ class BibleMediaItem(MediaManagerItem): log.info('Bible Media Item loaded') def __init__(self, parent, plugin): - self.icon_path = 'songs/song' self.lock_icon = build_icon(':/bibles/bibles_search_lock.png') self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png') MediaManagerItem.__init__(self, parent, plugin) @@ -172,6 +171,7 @@ class BibleMediaItem(MediaManagerItem): self.page_layout.addWidget(tab) tab.setVisible(False) lock_button.toggled.connect(self.on_lock_button_toggled) + second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed) setattr(self, prefix + 'VersionLabel', version_label) setattr(self, prefix + 'VersionComboBox', version_combo_box) setattr(self, prefix + 'SecondLabel', second_label) @@ -263,11 +263,15 @@ class BibleMediaItem(MediaManagerItem): def config_update(self): log.debug('config_update') if Settings().value(self.settings_section + '/second bibles'): + self.quickSecondLabel.setVisible(True) + self.quickSecondComboBox.setVisible(True) self.advancedSecondLabel.setVisible(True) self.advancedSecondComboBox.setVisible(True) self.quickSecondLabel.setVisible(True) self.quickSecondComboBox.setVisible(True) else: + self.quickSecondLabel.setVisible(False) + self.quickSecondComboBox.setVisible(False) self.advancedSecondLabel.setVisible(False) self.advancedSecondComboBox.setVisible(False) self.quickSecondLabel.setVisible(False) @@ -459,6 +463,17 @@ class BibleMediaItem(MediaManagerItem): books.sort(key=get_locale_key) set_case_insensitive_completer(books, self.quick_search_edit) + def on_second_bible_combobox_index_changed(self, selection): + """ + Activate the style combobox only when no second bible is selected + """ + if selection == 0: + self.quickStyleComboBox.setEnabled(True) + self.advancedStyleComboBox.setEnabled(True) + else: + self.quickStyleComboBox.setEnabled(False) + self.advancedStyleComboBox.setEnabled(False) + def on_import_click(self): if not hasattr(self, 'import_wizard'): self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin) diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index 22a33d921..5ed92f95e 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.py @@ -41,8 +41,8 @@ class Ui_CustomEditDialog(object): :param custom_edit_dialog: The Dialog """ custom_edit_dialog.setObjectName('custom_edit_dialog') + custom_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) custom_edit_dialog.resize(450, 350) - custom_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) self.dialog_layout = QtGui.QVBoxLayout(custom_edit_dialog) self.dialog_layout.setObjectName('dialog_layout') self.title_layout = QtGui.QHBoxLayout() diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index 3b74566fc..c59cc4cd1 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.py @@ -30,13 +30,14 @@ from PyQt4 import QtGui from openlp.core.common import UiStrings, translate -from openlp.core.lib import SpellTextEdit +from openlp.core.lib import SpellTextEdit, build_icon from openlp.core.lib.ui import create_button, create_button_box class Ui_CustomSlideEditDialog(object): def setupUi(self, custom_slide_edit_dialog): custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog') + custom_slide_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) custom_slide_edit_dialog.resize(350, 300) self.dialog_layout = QtGui.QVBoxLayout(custom_slide_edit_dialog) self.slide_text_edit = SpellTextEdit(self) diff --git a/openlp/plugins/songs/forms/authorsdialog.py b/openlp/plugins/songs/forms/authorsdialog.py index 65fb169b0..7bca76ca6 100644 --- a/openlp/plugins/songs/forms/authorsdialog.py +++ b/openlp/plugins/songs/forms/authorsdialog.py @@ -43,8 +43,8 @@ class Ui_AuthorsDialog(object): Set up the UI for the dialog. """ authors_dialog.setObjectName('authors_dialog') + authors_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) authors_dialog.resize(300, 10) - authors_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) authors_dialog.setModal(True) self.dialog_layout = QtGui.QVBoxLayout(authors_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index d0fb51a2d..a9ca71946 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -43,8 +43,8 @@ class Ui_EditSongDialog(object): """ def setupUi(self, edit_song_dialog): edit_song_dialog.setObjectName('edit_song_dialog') + edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) edit_song_dialog.resize(650, 400) - edit_song_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) edit_song_dialog.setModal(True) self.dialog_layout = QtGui.QVBoxLayout(edit_song_dialog) self.dialog_layout.setSpacing(8) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 1814655ea..ae47a110f 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -107,6 +107,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.audio_list_widget.setAlternatingRowColors(True) self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE) self.whitespace = re.compile(r'\W+', re.UNICODE) + self.find_tags = re.compile(u'\{/?\w+\}', re.UNICODE) def _load_objects(self, cls, combo, cache): """ @@ -234,8 +235,57 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.manager.save_object(book) else: return False + # Validate tags (lp#1199639) + misplaced_tags = [] + verse_tags = [] + for i in range(self.verse_list_widget.rowCount()): + item = self.verse_list_widget.item(i, 0) + tags = self.find_tags.findall(item.text()) + field = item.data(QtCore.Qt.UserRole) + verse_tags.append(field) + if not self._validate_tags(tags): + misplaced_tags.append('%s %s' % (VerseType.translated_name(field[0]), field[1:])) + if misplaced_tags: + critical_error_message_box( + message=translate('SongsPlugin.EditSongForm', + 'There are misplaced formatting tags in the following verses:\n\n%s\n\n' + 'Please correct these tags before continuing.' % ', '.join(misplaced_tags))) + return False + for tag in verse_tags: + if verse_tags.count(tag) > 26: + # lp#1310523: OpenLyrics allows only a-z variants of one verse: + # http://openlyrics.info/dataformat.html#verse-name + critical_error_message_box(message=translate( + 'SongsPlugin.EditSongForm', 'You have %(count)s verses named %(name)s %(number)s. ' + 'You can have at most 26 verses with the same name' % + {'count': verse_tags.count(tag), + 'name': VerseType.translated_name(tag[0]), + 'number': tag[1:]})) + return False return True + def _validate_tags(self, tags): + """ + Validates a list of tags + Deletes the first affiliated tag pair which is located side by side in the list + and call itself recursively with the shortened tag list. + If there is any misplaced tag in the list, either the length of the tag list is not even, + or the function won't find any tag pairs side by side. + If there is no misplaced tag, the length of the list will be zero on any recursive run. + + :param tags: A list of tags + :return: True if the function can't find any mismatched tags. Else False. + """ + if len(tags) == 0: + return True + if len(tags) % 2 != 0: + return False + for i in range(len(tags)-1): + if tags[i+1] == "{/" + tags[i][1:]: + del tags[i:i+2] + return self._validate_tags(tags) + return False + def _process_lyrics(self): """ Process the lyric data entered by the user into the OpenLP XML format. diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index f1901b203..1dc3d9182 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -37,6 +37,7 @@ from openlp.plugins.songs.lib import VerseType class Ui_EditVerseDialog(object): def setupUi(self, edit_verse_dialog): edit_verse_dialog.setObjectName('edit_verse_dialog') + edit_verse_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) edit_verse_dialog.resize(400, 400) edit_verse_dialog.setModal(True) self.dialog_layout = QtGui.QVBoxLayout(edit_verse_dialog) diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py index 3a1db39ed..495e08883 100644 --- a/openlp/plugins/songs/forms/mediafilesdialog.py +++ b/openlp/plugins/songs/forms/mediafilesdialog.py @@ -42,10 +42,10 @@ class Ui_MediaFilesDialog(object): Set up the user interface. """ media_files_dialog.setObjectName('media_files_dialog') + media_files_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) media_files_dialog.setWindowModality(QtCore.Qt.ApplicationModal) media_files_dialog.resize(400, 300) media_files_dialog.setModal(True) - media_files_dialog.setWindowIcon(build_icon(':/icon/openlp-logo-16x16.png')) self.files_vertical_layout = QtGui.QVBoxLayout(media_files_dialog) self.files_vertical_layout.setSpacing(8) self.files_vertical_layout.setMargin(8) diff --git a/openlp/plugins/songs/forms/songbookdialog.py b/openlp/plugins/songs/forms/songbookdialog.py index 8cacef1a2..1ed79a4eb 100644 --- a/openlp/plugins/songs/forms/songbookdialog.py +++ b/openlp/plugins/songs/forms/songbookdialog.py @@ -29,7 +29,7 @@ from PyQt4 import QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, build_icon from openlp.core.lib.ui import create_button_box @@ -42,6 +42,7 @@ class Ui_SongBookDialog(object): Set up the user interface. """ song_book_dialog.setObjectName('song_book_dialog') + song_book_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) song_book_dialog.resize(300, 10) self.dialog_layout = QtGui.QVBoxLayout(song_book_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py index 84e3535d3..893ae9c1c 100644 --- a/openlp/plugins/songs/forms/songmaintenancedialog.py +++ b/openlp/plugins/songs/forms/songmaintenancedialog.py @@ -44,6 +44,7 @@ class Ui_SongMaintenanceDialog(object): Set up the user interface for the song maintenance dialog """ song_maintenance_dialog.setObjectName('song_maintenance_dialog') + song_maintenance_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal) song_maintenance_dialog.resize(10, 350) self.dialog_layout = QtGui.QGridLayout(song_maintenance_dialog) diff --git a/openlp/plugins/songs/forms/topicsdialog.py b/openlp/plugins/songs/forms/topicsdialog.py index eb6229bf6..ffa7da333 100644 --- a/openlp/plugins/songs/forms/topicsdialog.py +++ b/openlp/plugins/songs/forms/topicsdialog.py @@ -29,7 +29,7 @@ from PyQt4 import QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, build_icon from openlp.core.lib.ui import create_button_box @@ -42,6 +42,7 @@ class Ui_TopicsDialog(object): Set up the user interface for the topics dialog. """ topics_dialog.setObjectName('topics_dialog') + topics_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) topics_dialog.resize(300, 10) self.dialog_layout = QtGui.QVBoxLayout(topics_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 95868ffa7..ec14175ac 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -206,14 +206,14 @@ class VerseType(object): Return the VerseType for a given tag :param verse_tag: The string to return a VerseType for - :param default: Default return value if no matching tag is found + :param default: Default return value if no matching tag is found (a valid VerseType or None) :return: A VerseType of the tag """ verse_tag = verse_tag[0].lower() for num, tag in enumerate(VerseType.tags): if verse_tag == tag: return num - if len(VerseType.names) > default: + if default in range(0, len(VerseType.names)) or default is None: return default else: return VerseType.Other @@ -231,7 +231,7 @@ class VerseType(object): for num, tag in enumerate(VerseType.translated_tags): if verse_tag == tag: return num - if len(VerseType.names) > default: + if default in range(0, len(VerseType.names)) or default is None: return default else: return VerseType.Other diff --git a/openlp/plugins/songs/lib/sundayplusimport.py b/openlp/plugins/songs/lib/sundayplusimport.py index f22f8b058..5c5f73047 100644 --- a/openlp/plugins/songs/lib/sundayplusimport.py +++ b/openlp/plugins/songs/lib/sundayplusimport.py @@ -68,7 +68,7 @@ class SundayPlusImport(SongImport): for filename in self.import_source: if self.stop_import_flag: return - song_file = open(filename) + song_file = open(filename, 'rb') self.do_import_file(song_file) song_file.close() @@ -103,7 +103,7 @@ class SundayPlusImport(SongImport): # Now we are looking for the name. if data[i:i + 1] == '#': name_end = data.find(':', i + 1) - name = data[i + 1:name_end] + name = data[i + 1:name_end].upper() i = name_end + 1 while data[i:i + 1] == ' ': i += 1 @@ -129,13 +129,13 @@ class SundayPlusImport(SongImport): value = data[i:end] # If we are in the main group. if not cell: - if name == 'title': + if name == 'TITLE': self.title = self.decode(self.unescape(value)) - elif name == 'Author': + elif name == 'AUTHOR': author = self.decode(self.unescape(value)) if len(author): self.add_author(author) - elif name == 'Copyright': + elif name == 'COPYRIGHT': self.copyright = self.decode(self.unescape(value)) elif name[0:4] == 'CELL': self.parse(value, cell=name[4:]) @@ -147,12 +147,12 @@ class SundayPlusImport(SongImport): verse_type = VerseType.tags[VerseType.from_loose_input(value[0])] if len(value) >= 2 and value[-1] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: verse_type = "%s%s" % (verse_type, value[-1]) - elif name == 'Hotkey': - # Hotkey always appears after MARKER_NAME, so it + elif name == 'HOTKEY': + # HOTKEY always appears after MARKER_NAME, so it # effectively overrides MARKER_NAME, if present. if len(value) and value in list(HOTKEY_TO_VERSE_TYPE.keys()): verse_type = HOTKEY_TO_VERSE_TYPE[value] - if name == 'rtf': + if name == 'RTF': value = self.unescape(value) result = strip_rtf(value, self.encoding) if result is None: diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index b856cb53f..87e5da21e 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -310,9 +310,9 @@ class OpenLyrics(object): verse_tag = verse[0]['type'][0].lower() verse_number = verse[0]['label'] verse_def = verse_tag + verse_number - verse_tags.append(verse_def) # Create the letter from the number of duplicates - verse[0]['suffix'] = chr(96 + verse_tags.count(verse_def)) + verse[0][u'suffix'] = chr(97 + (verse_tags.count(verse_def) % 26)) + verse_tags.append(verse_def) # If the verse tag is a duplicate use the suffix letter for verse in verse_list: verse_tag = verse[0]['type'][0].lower() diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py index 01597a790..190292678 100644 --- a/openlp/plugins/songusage/forms/songusagedeletedialog.py +++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py @@ -30,6 +30,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import translate +from openlp.core.lib import build_icon from openlp.core.lib.ui import create_button_box @@ -44,6 +45,7 @@ class Ui_SongUsageDeleteDialog(object): :param song_usage_delete_dialog: """ song_usage_delete_dialog.setObjectName('song_usage_delete_dialog') + song_usage_delete_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) song_usage_delete_dialog.resize(291, 243) self.vertical_layout = QtGui.QVBoxLayout(song_usage_delete_dialog) self.vertical_layout.setSpacing(8) diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index ede5075a0..9431c7894 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -45,6 +45,7 @@ class Ui_SongUsageDetailDialog(object): :param song_usage_detail_dialog: """ song_usage_detail_dialog.setObjectName('song_usage_detail_dialog') + song_usage_detail_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) song_usage_detail_dialog.resize(609, 413) self.vertical_layout = QtGui.QVBoxLayout(song_usage_detail_dialog) self.vertical_layout.setSpacing(8) diff --git a/tests/functional/openlp_core_lib/test_ui.py b/tests/functional/openlp_core_lib/test_ui.py index 025b1a638..f1b8ee17a 100644 --- a/tests/functional/openlp_core_lib/test_ui.py +++ b/tests/functional/openlp_core_lib/test_ui.py @@ -82,6 +82,21 @@ class TestUi(TestCase): self.assertEqual(1, len(btnbox.buttons())) self.assertEqual(QtGui.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0])) + def test_create_horizontal_adjusting_combo_box(self): + """ + Test creating a horizontal adjusting combo box + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create the combobox + combo = create_horizontal_adjusting_combo_box(dialog, 'combo1') + + # THEN: We should get a ComboBox + self.assertIsInstance(combo, QtGui.QComboBox) + self.assertEqual('combo1', combo.objectName()) + self.assertEqual(QtGui.QComboBox.AdjustToMinimumContentsLength, combo.sizeAdjustPolicy()) + def test_create_button(self): """ Test creating a button @@ -114,38 +129,6 @@ class TestUi(TestCase): self.assertEqual('my_btn', btn.objectName()) self.assertTrue(btn.isEnabled()) - def test_create_valign_selection_widgets(self): - """ - Test creating a combo box for valign selection - """ - # GIVEN: A dialog - dialog = QtGui.QDialog() - - # WHEN: We create the widgets - label, combo = create_valign_selection_widgets(dialog) - - # THEN: We should get a label and a combobox. - self.assertEqual(translate('OpenLP.Ui', '&Vertical Align:'), label.text()) - self.assertIsInstance(combo, QtGui.QComboBox) - self.assertEqual(combo, label.buddy()) - for text in [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]: - self.assertTrue(combo.findText(text) >= 0) - - def test_create_horizontal_adjusting_combo_box(self): - """ - Test creating a horizontal adjusting combo box - """ - # GIVEN: A dialog - dialog = QtGui.QDialog() - - # WHEN: We create the combobox - combo = create_horizontal_adjusting_combo_box(dialog, 'combo1') - - # THEN: We should get a ComboBox - self.assertIsInstance(combo, QtGui.QComboBox) - self.assertEqual('combo1', combo.objectName()) - self.assertEqual(QtGui.QComboBox.AdjustToMinimumContentsLength, combo.sizeAdjustPolicy()) - def test_create_action(self): """ Test creating an action @@ -170,3 +153,47 @@ class TestUi(TestCase): self.assertIsInstance(action.icon(), QtGui.QIcon) self.assertEqual('my tooltip', action.toolTip()) self.assertEqual('my statustip', action.statusTip()) + + def test_create_valign_selection_widgets(self): + """ + Test creating a combo box for valign selection + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create the widgets + label, combo = create_valign_selection_widgets(dialog) + + # THEN: We should get a label and a combobox. + self.assertEqual(translate('OpenLP.Ui', '&Vertical Align:'), label.text()) + self.assertIsInstance(combo, QtGui.QComboBox) + self.assertEqual(combo, label.buddy()) + for text in [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]: + self.assertTrue(combo.findText(text) >= 0) + + def test_find_and_set_in_combo_box(self): + """ + Test finding a string in a combo box and setting it as the selected item if present + """ + # GIVEN: A ComboBox + combo = QtGui.QComboBox() + combo.addItems(['One', 'Two', 'Three']) + combo.setCurrentIndex(1) + + # WHEN: We call the method with a non-existing value and set_missing=False + find_and_set_in_combo_box(combo, 'Four', set_missing=False) + + # THEN: The index should not have changed + self.assertEqual(1, combo.currentIndex()) + + # WHEN: We call the method with a non-existing value + find_and_set_in_combo_box(combo, 'Four') + + # THEN: The index should have been reset + self.assertEqual(0, combo.currentIndex()) + + # WHEN: We call the method with the default behavior + find_and_set_in_combo_box(combo, 'Three') + + # THEN: The index should have changed + self.assertEqual(2, combo.currentIndex()) diff --git a/tests/functional/openlp_plugins/songs/test_lib.py b/tests/functional/openlp_plugins/songs/test_lib.py index b67c1a4be..140126f26 100644 --- a/tests/functional/openlp_plugins/songs/test_lib.py +++ b/tests/functional/openlp_plugins/songs/test_lib.py @@ -445,9 +445,9 @@ class TestVerseType(TestCase): # THEN: The result should be VerseType.Chorus self.assertEqual(result, VerseType.Chorus, 'The result should be VerseType.Chorus, but was "%s"' % result) - def from_tag_with_invalid_default_test(self): + def from_tag_with_invalid_intdefault_test(self): """ - Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid default. + Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid int default. """ # GIVEN: A mocked out translate() function that just returns what it was given with patch('openlp.plugins.songs.lib.translate') as mocked_translate: @@ -458,3 +458,31 @@ class TestVerseType(TestCase): # THEN: The result should be VerseType.Other self.assertEqual(result, VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result) + + def from_tag_with_invalid_default_test(self): + """ + Test that the from_tag() method returns a sane default when passed an invalid tag and an invalid default. + """ + # GIVEN: A mocked out translate() function that just returns what it was given + with patch('openlp.plugins.songs.lib.translate') as mocked_translate: + mocked_translate.side_effect = lambda x, y: y + + # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back + result = VerseType.from_tag('@', 'asdf') + + # THEN: The result should be VerseType.Other + self.assertEqual(result, VerseType.Other, 'The result should be VerseType.Other, but was "%s"' % result) + + def from_tag_with_none_default_test(self): + """ + Test that the from_tag() method returns a sane default when passed an invalid tag and None as default. + """ + # GIVEN: A mocked out translate() function that just returns what it was given + with patch('openlp.plugins.songs.lib.translate') as mocked_translate: + mocked_translate.side_effect = lambda x, y: y + + # WHEN: We run the from_tag() method with an invalid verse type, we get the specified default back + result = VerseType.from_tag('m', None) + + # THEN: The result should be None + self.assertIsNone(result, 'The result should be None, but was "%s"' % result) diff --git a/tests/utils/test_bzr_tags.py b/tests/utils/test_bzr_tags.py index 95017e94f..393f4ce25 100644 --- a/tests/utils/test_bzr_tags.py +++ b/tests/utils/test_bzr_tags.py @@ -53,6 +53,7 @@ TAGS = [ ['2.0.1', '?'], ['2.0.2', '?'], ['2.0.3', '?'], + ['2.0.4', '?'], ['2.1.0', '2119'] ]