Example #1
0
 def __init__(self, manager, parent=None):
     """
     Constructor
     """
     super(SongMaintenanceForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
                                               QtCore.Qt.WindowCloseButtonHint)
     self.setupUi(self)
     self.manager = manager
     self.author_form = AuthorsForm(self)
     self.topic_form = TopicsForm(self)
     self.song_book_form = SongBookForm(self)
     # Disable all edit and delete buttons, as there is no row selected.
     self.delete_author_button.setEnabled(False)
     self.edit_author_button.setEnabled(False)
     self.delete_topic_button.setEnabled(False)
     self.edit_topic_button.setEnabled(False)
     self.delete_book_button.setEnabled(False)
     self.edit_book_button.setEnabled(False)
     # Signals
     self.add_author_button.clicked.connect(self.on_add_author_button_clicked)
     self.add_topic_button.clicked.connect(self.on_add_topic_button_clicked)
     self.add_book_button.clicked.connect(self.on_add_book_button_clicked)
     self.edit_author_button.clicked.connect(self.on_edit_author_button_clicked)
     self.edit_topic_button.clicked.connect(self.on_edit_topic_button_clicked)
     self.edit_book_button.clicked.connect(self.on_edit_book_button_clicked)
     self.delete_author_button.clicked.connect(self.on_delete_author_button_clicked)
     self.delete_topic_button.clicked.connect(self.on_delete_topic_button_clicked)
     self.delete_book_button.clicked.connect(self.on_delete_book_button_clicked)
     self.authors_list_widget.currentRowChanged.connect(self.on_authors_list_row_changed)
     self.topics_list_widget.currentRowChanged.connect(self.on_topics_list_row_changed)
     self.song_books_list_widget.currentRowChanged.connect(self.on_song_books_list_row_changed)
Example #2
0
 def setUp(self):
     """
     Create the UI
     """
     Registry.create()
     self.setup_application()
     self.main_window = QtWidgets.QMainWindow()
     Registry().register('main_window', self.main_window)
     self.form = AuthorsForm()
 def __init__(self, manager, parent=None):
     """
     Constructor
     """
     super(SongMaintenanceForm, self).__init__(parent)
     self.setupUi(self)
     self.manager = manager
     self.author_form = AuthorsForm(self)
     self.topic_form = TopicsForm(self)
     self.song_book_form = SongBookForm(self)
     # Disable all edit and delete buttons, as there is no row selected.
     self.delete_author_button.setEnabled(False)
     self.edit_author_button.setEnabled(False)
     self.delete_topic_button.setEnabled(False)
     self.edit_topic_button.setEnabled(False)
     self.delete_book_button.setEnabled(False)
     self.edit_book_button.setEnabled(False)
     # Signals
     self.add_author_button.clicked.connect(self.on_add_author_button_clicked)
     self.add_topic_button.clicked.connect(self.on_add_topic_button_clicked)
     self.add_book_button.clicked.connect(self.on_add_book_button_clicked)
     self.edit_author_button.clicked.connect(self.on_edit_author_button_clicked)
     self.edit_topic_button.clicked.connect(self.on_edit_topic_button_clicked)
     self.edit_book_button.clicked.connect(self.on_edit_book_button_clicked)
     self.delete_author_button.clicked.connect(self.on_delete_author_button_clicked)
     self.delete_topic_button.clicked.connect(self.on_delete_topic_button_clicked)
     self.delete_book_button.clicked.connect(self.on_delete_book_button_clicked)
     self.authors_list_widget.currentRowChanged.connect(self.on_authors_list_row_changed)
     self.topics_list_widget.currentRowChanged.connect(self.on_topics_list_row_changed)
     self.song_books_list_widget.currentRowChanged.connect(self.on_song_books_list_row_changed)
Example #4
0
 def setUp(self):
     """
     Create the UI
     """
     Registry.create()
     self.setup_application()
     self.main_window = QtWidgets.QMainWindow()
     Registry().register('main_window', self.main_window)
     self.form = AuthorsForm()
Example #5
0
class TestAuthorsForm(TestCase, TestMixin):
    """
    Test the AuthorsForm class
    """

    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtWidgets.QMainWindow()
        Registry().register('main_window', self.main_window)
        self.form = AuthorsForm()

    def tearDown(self):
        """
        Delete all the C++ objects at the end so that we don't have a segfault
        """
        del self.form
        del self.main_window

    def test_ui_defaults(self):
        """
        Test the AuthorForm defaults are correct
        """
        self.assertEqual(self.form.first_name_edit.text(), '', 'The first name edit should be empty')
        self.assertEqual(self.form.last_name_edit.text(), '', 'The last name edit should be empty')
        self.assertEqual(self.form.display_edit.text(), '', 'The display name edit should be empty')

    def test_get_first_name_property(self):
        """
        Test that getting the first name property on the AuthorForm works correctly
        """
        # GIVEN: A first name to set
        first_name = 'John'

        # WHEN: The first_name_edit's text is set
        self.form.first_name_edit.setText(first_name)

        # THEN: The first_name property should have the correct value
        self.assertEqual(self.form.first_name, first_name, 'The first name property should be correct')

    def test_set_first_name_property(self):
        """
        Test that setting the first name property on the AuthorForm works correctly
        """
        # GIVEN: A first name to set
        first_name = 'James'

        # WHEN: The first_name property is set
        self.form.first_name = first_name

        # THEN: The first_name_edit should have the correct value
        self.assertEqual(self.form.first_name_edit.text(), first_name, 'The first name should be set correctly')

    def test_get_last_name_property(self):
        """
        Test that getting the last name property on the AuthorForm works correctly
        """
        # GIVEN: A last name to set
        last_name = 'Smith'

        # WHEN: The last_name_edit's text is set
        self.form.last_name_edit.setText(last_name)

        # THEN: The last_name property should have the correct value
        self.assertEqual(self.form.last_name, last_name, 'The last name property should be correct')

    def test_set_last_name_property(self):
        """
        Test that setting the last name property on the AuthorForm works correctly
        """
        # GIVEN: A last name to set
        last_name = 'Potter'

        # WHEN: The last_name property is set
        self.form.last_name = last_name

        # THEN: The last_name_edit should have the correct value
        self.assertEqual(self.form.last_name_edit.text(), last_name, 'The last name should be set correctly')

    def test_get_display_name_property(self):
        """
        Test that getting the display name property on the AuthorForm works correctly
        """
        # GIVEN: A display name to set
        display_name = 'John'

        # WHEN: The display_name_edit's text is set
        self.form.display_edit.setText(display_name)

        # THEN: The display_name property should have the correct value
        self.assertEqual(self.form.display_name, display_name, 'The display name property should be correct')

    def test_set_display_name_property(self):
        """
        Test that setting the display name property on the AuthorForm works correctly
        """
        # GIVEN: A display name to set
        display_name = 'John'

        # WHEN: The display_name property is set
        self.form.display_name = display_name

        # THEN: The display_name_edit should have the correct value
        self.assertEqual(self.form.display_edit.text(), display_name, 'The display name should be set correctly')

    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.exec')
    def test_exec(self, mocked_exec):
        """
        Test the exec() method
        """
        # GIVEN: An authors for and various mocked objects
        with patch.object(self.form.first_name_edit, 'clear') as mocked_first_name_edit_clear, \
                patch.object(self.form.last_name_edit, 'clear') as mocked_last_name_edit_clear, \
                patch.object(self.form.display_edit, 'clear') as mocked_display_edit_clear, \
                patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus:
            # WHEN: The exec() method is called
            self.form.exec(clear=True)

            # THEN: The clear and exec() methods should have been called
        mocked_first_name_edit_clear.assert_called_once_with()
        mocked_last_name_edit_clear.assert_called_once_with()
        mocked_display_edit_clear.assert_called_once_with()
        mocked_first_name_edit_setFocus.assert_called_once_with()
        mocked_exec.assert_called_once_with(self.form)

    def test_first_name_edited(self):
        """
        Test the on_first_name_edited() method
        """
        # GIVEN: An author form
        self.form.auto_display_name = True

        with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_last_name_edit_text.return_value = 'Newton'

            # WHEN: on_first_name_edited() is called
            self.form.on_first_name_edited('John')

        # THEN: The display name should be updated
        assert mocked_last_name_edit_text.call_count == 2
        mocked_display_edit_setText.assert_called_once_with('John Newton')

    def test_first_name_edited_no_auto(self):
        """
        Test the on_first_name_edited() method without auto_display_name
        """
        # GIVEN: An author form
        self.form.auto_display_name = False

        with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:

            # WHEN: on_first_name_edited() is called
            self.form.on_first_name_edited('John')

        # THEN: The display name should not be updated
        assert mocked_last_name_edit_text.call_count == 0
        assert mocked_display_edit_setText.call_count == 0

    def test_last_name_edited(self):
        """
        Test the on_last_name_edited() method
        """
        # GIVEN: An author form
        self.form.auto_display_name = True

        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_first_name_edit_text.return_value = 'John'

            # WHEN: on_last_name_edited() is called
            self.form.on_last_name_edited('Newton')

        # THEN: The display name should be updated
        assert mocked_first_name_edit_text.call_count == 2
        mocked_display_edit_setText.assert_called_once_with('John Newton')

    def test_last_name_edited_no_auto(self):
        """
        Test the on_last_name_edited() method without auto_display_name
        """
        # GIVEN: An author form
        self.form.auto_display_name = False

        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:

            # WHEN: on_last_name_edited() is called
            self.form.on_last_name_edited('Newton')

        # THEN: The display name should not be updated
        assert mocked_first_name_edit_text.call_count == 0
        assert mocked_display_edit_setText.call_count == 0

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_first_name(self, mocked_critical_error):
        """
        Test the accept() method with no first name
        """
        # GIVEN: A form and no text in thefirst name edit
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus:
            mocked_first_name_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(message='You need to type in the first name of the author.')
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_first_name_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_last_name(self, mocked_critical_error):
        """
        Test the accept() method with no last name
        """
        # GIVEN: A form and no text in the last name edit
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.last_name_edit, 'setFocus') as mocked_last_name_edit_setFocus:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(message='You need to type in the last name of the author.')
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_display_name_no_combine(self, mocked_critical_error):
        """
        Test the accept() method with no display name and no combining
        """
        # GIVEN: A form and no text in the display name edit
        mocked_critical_error.return_value = QtWidgets.QMessageBox.No
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \
                patch.object(self.form.display_edit, 'setFocus') as mocked_display_edit_setFocus:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(
            message='You have not set a display name for the author, combine the first and last names?',
            parent=self.form, question=True)
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_display_edit_text.assert_called_once_with()
        mocked_display_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept')
    def test_accept_no_display_name(self, mocked_accept, mocked_critical_error):
        """
        Test the accept() method with no display name and auto-combine
        """
        # GIVEN: A form and no text in the display name edit
        mocked_accept.return_value = True
        mocked_critical_error.return_value = QtWidgets.QMessageBox.Yes
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is True
        mocked_critical_error.assert_called_once_with(
            message='You have not set a display name for the author, combine the first and last names?',
            parent=self.form, question=True)
        assert mocked_first_name_edit_text.call_count == 2
        assert mocked_last_name_edit_text.call_count == 2
        mocked_display_edit_text.assert_called_once_with()
        mocked_display_edit_setText.assert_called_once_with('John Newton')
        mocked_accept.assert_called_once_with(self.form)

    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept')
    def test_accept(self, mocked_accept):
        """
        Test the accept() method
        """
        # GIVEN: A form and text in the right places
        mocked_accept.return_value = True
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = 'John Newton'

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is True
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_display_edit_text.assert_called_once_with()
        mocked_accept.assert_called_once_with(self.form)
class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog,
                          RegistryProperties):
    """
    Class documentation goes here.
    """
    def __init__(self, manager, parent=None):
        """
        Constructor
        """
        super(SongMaintenanceForm, self).__init__(
            parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
        self.setupUi(self)
        self.manager = manager
        self.author_form = AuthorsForm(self)
        self.topic_form = TopicsForm(self)
        self.song_book_form = SongBookForm(self)
        # Disable all edit and delete buttons, as there is no row selected.
        self.delete_author_button.setEnabled(False)
        self.edit_author_button.setEnabled(False)
        self.delete_topic_button.setEnabled(False)
        self.edit_topic_button.setEnabled(False)
        self.delete_book_button.setEnabled(False)
        self.edit_book_button.setEnabled(False)
        # Signals
        self.add_author_button.clicked.connect(
            self.on_add_author_button_clicked)
        self.add_topic_button.clicked.connect(self.on_add_topic_button_clicked)
        self.add_book_button.clicked.connect(self.on_add_book_button_clicked)
        self.edit_author_button.clicked.connect(
            self.on_edit_author_button_clicked)
        self.edit_topic_button.clicked.connect(
            self.on_edit_topic_button_clicked)
        self.edit_book_button.clicked.connect(self.on_edit_book_button_clicked)
        self.delete_author_button.clicked.connect(
            self.on_delete_author_button_clicked)
        self.delete_topic_button.clicked.connect(
            self.on_delete_topic_button_clicked)
        self.delete_book_button.clicked.connect(
            self.on_delete_book_button_clicked)
        self.authors_list_widget.currentRowChanged.connect(
            self.on_authors_list_row_changed)
        self.topics_list_widget.currentRowChanged.connect(
            self.on_topics_list_row_changed)
        self.song_books_list_widget.currentRowChanged.connect(
            self.on_song_books_list_row_changed)

    def exec(self, from_song_edit=False):
        """
        Show the dialog.


        :param from_song_edit: Indicates if the maintenance dialog has been opened from song edit
            or from the media manager. Defaults to **False**.
        """
        self.from_song_edit = from_song_edit
        self.type_list_widget.setCurrentRow(0)
        self.reset_authors()
        self.reset_topics()
        self.reset_song_books()
        self.type_list_widget.setFocus()
        return QtWidgets.QDialog.exec(self)

    def _get_current_item_id(self, list_widget):
        """
        Get the ID of the currently selected item.

        :param list_widget: The list widget to examine.
        """
        item = list_widget.currentItem()
        if item:
            item_id = (item.data(QtCore.Qt.UserRole))
            return item_id
        else:
            return -1

    def _delete_item(self, item_class, list_widget, reset_func, dlg_title,
                     del_text, err_text):
        """
        Delete an item.
        """
        item_id = self._get_current_item_id(list_widget)
        if item_id != -1:
            item = self.manager.get_object(item_class, item_id)
            if item and not item.songs:
                if critical_error_message_box(
                        dlg_title, del_text, self,
                        True) == QtWidgets.QMessageBox.Yes:
                    self.manager.delete_object(item_class, item.id)
                    reset_func()
            else:
                critical_error_message_box(dlg_title, err_text)
        else:
            critical_error_message_box(dlg_title, UiStrings().NISs)

    def reset_authors(self):
        """
        Reloads the Authors list.
        """
        def get_author_key(author):
            """Get the key to sort by"""
            return get_natural_key(author.display_name)

        self.authors_list_widget.clear()
        authors = self.manager.get_all_objects(Author)
        authors.sort(key=get_author_key)
        for author in authors:
            if author.display_name:
                author_name = QtWidgets.QListWidgetItem(author.display_name)
            else:
                author_name = QtWidgets.QListWidgetItem(' '.join(
                    [author.first_name, author.last_name]))
            author_name.setData(QtCore.Qt.UserRole, author.id)
            self.authors_list_widget.addItem(author_name)

    def reset_topics(self):
        """
        Reloads the Topics list.
        """
        def get_topic_key(topic):
            """Get the key to sort by"""
            return get_natural_key(topic.name)

        self.topics_list_widget.clear()
        topics = self.manager.get_all_objects(Topic)
        topics.sort(key=get_topic_key)
        for topic in topics:
            topic_name = QtWidgets.QListWidgetItem(topic.name)
            topic_name.setData(QtCore.Qt.UserRole, topic.id)
            self.topics_list_widget.addItem(topic_name)

    def reset_song_books(self):
        """
        Reloads the Books list.
        """
        def get_book_key(book):
            """Get the key to sort by"""
            return get_natural_key(book.name)

        self.song_books_list_widget.clear()
        books = self.manager.get_all_objects(Book)
        books.sort(key=get_book_key)
        for book in books:
            book_name = QtWidgets.QListWidgetItem(
                '{name} ({publisher})'.format(name=book.name,
                                              publisher=book.publisher))
            book_name.setData(QtCore.Qt.UserRole, book.id)
            self.song_books_list_widget.addItem(book_name)

    def check_author_exists(self, new_author, edit=False):
        """
        Returns *False* if the given Author already exists, otherwise *True*.

        :param new_author: The new Author.
        :param edit: Are we editing the song?
        """
        authors = self.manager.get_all_objects(
            Author,
            and_(Author.first_name == new_author.first_name,
                 Author.last_name == new_author.last_name,
                 Author.display_name == new_author.display_name))
        return self.__check_object_exists(authors, new_author, edit)

    def check_topic_exists(self, new_topic, edit=False):
        """
        Returns *False* if the given Topic already exists, otherwise *True*.

        :param new_topic: The new Topic.
        :param edit: Are we editing the song?
        """
        topics = self.manager.get_all_objects(Topic,
                                              Topic.name == new_topic.name)
        return self.__check_object_exists(topics, new_topic, edit)

    def check_song_book_exists(self, new_book, edit=False):
        """
        Returns *False* if the given Topic already exists, otherwise *True*.

        :param new_book: The new Book.
        :param edit: Are we editing the song?
        """
        books = self.manager.get_all_objects(
            Book,
            and_(Book.name == new_book.name,
                 Book.publisher == new_book.publisher))
        return self.__check_object_exists(books, new_book, edit)

    def __check_object_exists(self, existing_objects, new_object, edit):
        """
        Utility method to check for an existing object.

        :param existing_objects: The objects reference
        :param new_object: An individual object
        :param edit: If we edit an item, this should be *True*.
        """
        if existing_objects:
            # If we edit an existing object, we need to make sure that we do
            # not return False when nothing has changed.
            if edit:
                for existing_object in existing_objects:
                    if existing_object.id != new_object.id:
                        return False
                return True
            else:
                return False
        else:
            return True

    def on_add_author_button_clicked(self):
        """
        Add an author to the list.
        """
        self.author_form.auto_display_name = True
        if self.author_form.exec():
            author = Author.populate(
                first_name=self.author_form.first_name,
                last_name=self.author_form.last_name,
                display_name=self.author_form.display_name)
            if self.check_author_exists(author):
                if self.manager.save_object(author):
                    self.reset_authors()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not add your author.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm',
                                      'This author already exists.'))

    def on_add_topic_button_clicked(self):
        """
        Add a topic to the list.
        """
        if self.topic_form.exec():
            topic = Topic.populate(name=self.topic_form.name)
            if self.check_topic_exists(topic):
                if self.manager.save_object(topic):
                    self.reset_topics()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not add your topic.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm',
                                      'This topic already exists.'))

    def on_add_book_button_clicked(self):
        """
        Add a book to the list.
        """
        if self.song_book_form.exec():
            book = Book.populate(
                name=self.song_book_form.name_edit.text(),
                publisher=self.song_book_form.publisher_edit.text())
            if self.check_song_book_exists(book):
                if self.manager.save_object(book):
                    self.reset_song_books()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not add your book.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm',
                                      'This book already exists.'))

    def on_edit_author_button_clicked(self):
        """
        Edit an author.
        """
        author_id = self._get_current_item_id(self.authors_list_widget)
        if author_id == -1:
            return
        author = self.manager.get_object(Author, author_id)
        self.author_form.auto_display_name = False
        self.author_form.first_name_edit.setText(author.first_name)
        self.author_form.last_name_edit.setText(author.last_name)
        self.author_form.display_edit.setText(author.display_name)
        # Save the author's first and last name as well as the display name
        # for the case that they have to be restored.
        temp_first_name = author.first_name
        temp_last_name = author.last_name
        temp_display_name = author.display_name
        if self.author_form.exec(False):
            author.first_name = self.author_form.first_name_edit.text()
            author.last_name = self.author_form.last_name_edit.text()
            author.display_name = self.author_form.display_edit.text()
            if self.check_author_exists(author, True):
                if self.manager.save_object(author):
                    self.reset_authors()
                    if not self.from_song_edit:
                        Registry().execute('songs_load_list')
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not save your changes.'))
            elif critical_error_message_box(message=translate(
                    'SongsPlugin.SongMaintenanceForm',
                    'The author {original} already exists. Would you like to make songs with author {new} use the '
                    'existing author {original}?').format(
                        original=author.display_name, new=temp_display_name),
                                            parent=self,
                                            question=True
                                            ) == QtWidgets.QMessageBox.Yes:
                self._merge_objects(author, self.merge_authors,
                                    self.reset_authors)
            else:
                # We restore the author's old first and last name as well as
                # his display name.
                author.first_name = temp_first_name
                author.last_name = temp_last_name
                author.display_name = temp_display_name
                critical_error_message_box(message=translate(
                    'SongsPlugin.SongMaintenanceForm',
                    'Could not save your modified author, because the author already exists.'
                ))

    def on_edit_topic_button_clicked(self):
        """
        Edit a topic.
        """
        topic_id = self._get_current_item_id(self.topics_list_widget)
        if topic_id == -1:
            return
        topic = self.manager.get_object(Topic, topic_id)
        self.topic_form.name = topic.name
        # Save the topic's name for the case that he has to be restored.
        temp_name = topic.name
        if self.topic_form.exec(False):
            topic.name = self.topic_form.name_edit.text()
            if self.check_topic_exists(topic, True):
                if self.manager.save_object(topic):
                    self.reset_topics()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not save your changes.'))
            elif critical_error_message_box(message=translate(
                    'SongsPlugin.SongMaintenanceForm',
                    'The topic {original} already exists. Would you like to make songs with '
                    'topic {new} use the existing topic {original}?').format(
                        original=topic.name, new=temp_name),
                                            parent=self,
                                            question=True
                                            ) == QtWidgets.QMessageBox.Yes:
                self._merge_objects(topic, self.merge_topics,
                                    self.reset_topics)
            else:
                # We restore the topics's old name.
                topic.name = temp_name
                critical_error_message_box(message=translate(
                    'SongsPlugin.SongMaintenanceForm',
                    'Could not save your modified topic, because it already exists.'
                ))

    def on_edit_book_button_clicked(self):
        """
        Edit a book.
        """
        book_id = self._get_current_item_id(self.song_books_list_widget)
        if book_id == -1:
            return
        book = self.manager.get_object(Book, book_id)
        if book.publisher is None:
            book.publisher = ''
        self.song_book_form.name_edit.setText(book.name)
        self.song_book_form.publisher_edit.setText(book.publisher)
        # Save the book's name and publisher for the case that they have to
        # be restored.
        temp_name = book.name
        temp_publisher = book.publisher
        if self.song_book_form.exec(False):
            book.name = self.song_book_form.name_edit.text()
            book.publisher = self.song_book_form.publisher_edit.text()
            if self.check_song_book_exists(book, True):
                if self.manager.save_object(book):
                    self.reset_song_books()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm',
                                          'Could not save your changes.'))
            elif critical_error_message_box(message=translate(
                    'SongsPlugin.SongMaintenanceForm',
                    'The book {original} already exists. Would you like to make songs with '
                    'book {new} use the existing book {original}?').format(
                        original=book.name, new=temp_name),
                                            parent=self,
                                            question=True
                                            ) == QtWidgets.QMessageBox.Yes:
                self._merge_objects(book, self.merge_song_books,
                                    self.reset_song_books)
            else:
                # We restore the book's old name and publisher.
                book.name = temp_name
                book.publisher = temp_publisher

    def _merge_objects(self, db_object, merge, reset):
        """
        Utility method to merge two objects to leave one in the database.
        """
        self.application.set_busy_cursor()
        merge(db_object)
        reset()
        if not self.from_song_edit:
            Registry().execute('songs_load_list')
        self.application.set_normal_cursor()

    def merge_authors(self, old_author):
        """
        Merges two authors into one author.

        :param old_author: The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_author = self.manager.get_object_filtered(
            Author,
            and_(Author.first_name == old_author.first_name,
                 Author.last_name == old_author.last_name,
                 Author.display_name == old_author.display_name,
                 Author.id != old_author.id))
        # Find the songs, which have the old_author as author.
        songs = self.manager.get_all_objects(Song,
                                             Song.authors.contains(old_author))
        for song in songs:
            for author_song in song.authors_songs:
                song.add_author(existing_author, author_song.author_type)
                song.remove_author(old_author, author_song.author_type)
            self.manager.save_object(song)
        self.manager.delete_object(Author, old_author.id)

    def merge_topics(self, old_topic):
        """
        Merges two topics into one topic.

        :param old_topic: The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_topic = self.manager.get_object_filtered(
            Topic, and_(Topic.name == old_topic.name,
                        Topic.id != old_topic.id))
        # Find the songs, which have the old_topic as topic.
        songs = self.manager.get_all_objects(Song,
                                             Song.topics.contains(old_topic))
        for song in songs:
            # We check if the song has already existing_topic as topic. If that
            # is not the case we add it.
            if existing_topic not in song.topics:
                song.topics.append(existing_topic)
            song.topics.remove(old_topic)
            self.manager.save_object(song)
        self.manager.delete_object(Topic, old_topic.id)

    def merge_song_books(self, old_song_book):
        """
        Merges two books into one book.

        ``old_song_book``
            The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_book = self.manager.get_object_filtered(
            Book,
            and_(Book.name == old_song_book.name,
                 Book.publisher == old_song_book.publisher,
                 Book.id != old_song_book.id))
        # Find the songs, which have the old_song_book as book.
        songs = self.manager.get_all_objects(
            Song, Song.song_book_id == old_song_book.id)
        for song in songs:
            song.song_book_id = existing_book.id
            self.manager.save_object(song)
        self.manager.delete_object(Book, old_song_book.id)

    def on_delete_author_button_clicked(self):
        """
        Delete the author if the author is not attached to any songs.
        """
        self._delete_item(
            Author, self.authors_list_widget, self.reset_authors,
            translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
            translate('SongsPlugin.SongMaintenanceForm',
                      'Are you sure you want to delete the selected author?'),
            translate(
                'SongsPlugin.SongMaintenanceForm',
                'This author cannot be deleted, they are currently assigned to at least one song'
                '.'))

    def on_delete_topic_button_clicked(self):
        """
        Delete the Book if the Book is not attached to any songs.
        """
        self._delete_item(
            Topic, self.topics_list_widget, self.reset_topics,
            translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
            translate('SongsPlugin.SongMaintenanceForm',
                      'Are you sure you want to delete the selected topic?'),
            translate(
                'SongsPlugin.SongMaintenanceForm',
                'This topic cannot be deleted, it is currently assigned to at least one song.'
            ))

    def on_delete_book_button_clicked(self):
        """
        Delete the Book if the Book is not attached to any songs.
        """
        self._delete_item(
            Book, self.song_books_list_widget, self.reset_song_books,
            translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
            translate('SongsPlugin.SongMaintenanceForm',
                      'Are you sure you want to delete the selected book?'),
            translate(
                'SongsPlugin.SongMaintenanceForm',
                'This book cannot be deleted, it is currently assigned to at least one song.'
            ))

    def on_authors_list_row_changed(self, row):
        """
        Called when the *authors_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_author_button,
                         self.delete_author_button)

    def on_topics_list_row_changed(self, row):
        """
        Called when the *topics_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_topic_button, self.delete_topic_button)

    def on_song_books_list_row_changed(self, row):
        """
        Called when the *song_books_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_book_button, self.delete_book_button)

    def _row_change(self, row, edit_button, delete_button):
        """
        Utility method to toggle if buttons are enabled.

        ``row``
            The current row. If there is no current row, the value is -1.
        """
        if row == -1:
            delete_button.setEnabled(False)
            edit_button.setEnabled(False)
        else:
            delete_button.setEnabled(True)
            edit_button.setEnabled(True)
class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog, RegistryProperties):
    """
    Class documentation goes here.
    """
    def __init__(self, manager, parent=None):
        """
        Constructor
        """
        super(SongMaintenanceForm, self).__init__(parent)
        self.setupUi(self)
        self.manager = manager
        self.author_form = AuthorsForm(self)
        self.topic_form = TopicsForm(self)
        self.song_book_form = SongBookForm(self)
        # Disable all edit and delete buttons, as there is no row selected.
        self.delete_author_button.setEnabled(False)
        self.edit_author_button.setEnabled(False)
        self.delete_topic_button.setEnabled(False)
        self.edit_topic_button.setEnabled(False)
        self.delete_book_button.setEnabled(False)
        self.edit_book_button.setEnabled(False)
        # Signals
        self.add_author_button.clicked.connect(self.on_add_author_button_clicked)
        self.add_topic_button.clicked.connect(self.on_add_topic_button_clicked)
        self.add_book_button.clicked.connect(self.on_add_book_button_clicked)
        self.edit_author_button.clicked.connect(self.on_edit_author_button_clicked)
        self.edit_topic_button.clicked.connect(self.on_edit_topic_button_clicked)
        self.edit_book_button.clicked.connect(self.on_edit_book_button_clicked)
        self.delete_author_button.clicked.connect(self.on_delete_author_button_clicked)
        self.delete_topic_button.clicked.connect(self.on_delete_topic_button_clicked)
        self.delete_book_button.clicked.connect(self.on_delete_book_button_clicked)
        self.authors_list_widget.currentRowChanged.connect(self.on_authors_list_row_changed)
        self.topics_list_widget.currentRowChanged.connect(self.on_topics_list_row_changed)
        self.song_books_list_widget.currentRowChanged.connect(self.on_song_books_list_row_changed)

    def exec_(self, from_song_edit=False):
        """
        Show the dialog.


        :param from_song_edit: Indicates if the maintenance dialog has been opened from song edit
            or from the media manager. Defaults to **False**.
        """
        self.from_song_edit = from_song_edit
        self.type_list_widget.setCurrentRow(0)
        self.reset_authors()
        self.reset_topics()
        self.reset_song_books()
        self.type_list_widget.setFocus()
        return QtGui.QDialog.exec_(self)

    def _get_current_item_id(self, list_widget):
        """
        Get the ID of the currently selected item.

        :param list_widget: The list widget to examine.
        """
        item = list_widget.currentItem()
        if item:
            item_id = (item.data(QtCore.Qt.UserRole))
            return item_id
        else:
            return -1

    def _delete_item(self, item_class, list_widget, reset_func, dlg_title, del_text, err_text):
        """
        Delete an item.
        """
        item_id = self._get_current_item_id(list_widget)
        if item_id != -1:
            item = self.manager.get_object(item_class, item_id)
            if item and not item.songs:
                if critical_error_message_box(dlg_title, del_text, self, True) == QtGui.QMessageBox.Yes:
                    self.manager.delete_object(item_class, item.id)
                    reset_func()
            else:
                critical_error_message_box(dlg_title, err_text)
        else:
            critical_error_message_box(dlg_title, UiStrings().NISs)

    def reset_authors(self):
        """
        Reloads the Authors list.
        """
        self.authors_list_widget.clear()
        authors = self.manager.get_all_objects(Author, order_by_ref=Author.display_name)
        for author in authors:
            if author.display_name:
                author_name = QtGui.QListWidgetItem(author.display_name)
            else:
                author_name = QtGui.QListWidgetItem(' '.join([author.first_name, author.last_name]))
            author_name.setData(QtCore.Qt.UserRole, author.id)
            self.authors_list_widget.addItem(author_name)

    def reset_topics(self):
        """
        Reloads the Topics list.
        """
        self.topics_list_widget.clear()
        topics = self.manager.get_all_objects(Topic, order_by_ref=Topic.name)
        for topic in topics:
            topic_name = QtGui.QListWidgetItem(topic.name)
            topic_name.setData(QtCore.Qt.UserRole, topic.id)
            self.topics_list_widget.addItem(topic_name)

    def reset_song_books(self):
        """
        Reloads the Books list.
        """
        self.song_books_list_widget.clear()
        books = self.manager.get_all_objects(Book, order_by_ref=Book.name)
        for book in books:
            book_name = QtGui.QListWidgetItem('%s (%s)' % (book.name, book.publisher))
            book_name.setData(QtCore.Qt.UserRole, book.id)
            self.song_books_list_widget.addItem(book_name)

    def check_author_exists(self, new_author, edit=False):
        """
        Returns *False* if the given Author already exists, otherwise *True*.

        :param new_author: The new Author.
        :param edit: Are we editing the song?
        """
        authors = self.manager.get_all_objects(
            Author,
            and_(
                Author.first_name == new_author.first_name,
                Author.last_name == new_author.last_name,
                Author.display_name == new_author.display_name
            )
        )
        return self.__check_object_exists(authors, new_author, edit)

    def check_topic_exists(self, new_topic, edit=False):
        """
        Returns *False* if the given Topic already exists, otherwise *True*.

        :param new_topic: The new Topic.
        :param edit: Are we editing the song?
        """
        topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name)
        return self.__check_object_exists(topics, new_topic, edit)

    def check_song_book_exists(self, new_book, edit=False):
        """
        Returns *False* if the given Topic already exists, otherwise *True*.

        :param new_book: The new Book.
        :param edit: Are we editing the song?
        """
        books = self.manager.get_all_objects(
            Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher))
        return self.__check_object_exists(books, new_book, edit)

    def __check_object_exists(self, existing_objects, new_object, edit):
        """
        Utility method to check for an existing object.

        :param existing_objects: The objects reference
        :param new_object: An individual object
        :param edit: If we edit an item, this should be *True*.
        """
        if existing_objects:
            # If we edit an existing object, we need to make sure that we do
            # not return False when nothing has changed.
            if edit:
                for existing_object in existing_objects:
                    if existing_object.id != new_object.id:
                        return False
                return True
            else:
                return False
        else:
            return True

    def on_add_author_button_clicked(self):
        """
        Add an author to the list.
        """
        self.author_form.auto_display_name = True
        if self.author_form.exec_():
            author = Author.populate(
                first_name=self.author_form.first_name,
                last_name=self.author_form.last_name,
                display_name=self.author_form.display_name
            )
            if self.check_author_exists(author):
                if self.manager.save_object(author):
                    self.reset_authors()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not add your author.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm', 'This author already exists.'))

    def on_add_topic_button_clicked(self):
        """
        Add a topic to the list.
        """
        if self.topic_form.exec_():
            topic = Topic.populate(name=self.topic_form.name)
            if self.check_topic_exists(topic):
                if self.manager.save_object(topic):
                    self.reset_topics()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not add your topic.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm', 'This topic already exists.'))

    def on_add_book_button_clicked(self):
        """
        Add a book to the list.
        """
        if self.song_book_form.exec_():
            book = Book.populate(name=self.song_book_form.name_edit.text(),
                                 publisher=self.song_book_form.publisher_edit.text())
            if self.check_song_book_exists(book):
                if self.manager.save_object(book):
                    self.reset_song_books()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not add your book.'))
            else:
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm', 'This book already exists.'))

    def on_edit_author_button_clicked(self):
        """
        Edit an author.
        """
        author_id = self._get_current_item_id(self.authors_list_widget)
        if author_id == -1:
            return
        author = self.manager.get_object(Author, author_id)
        self.author_form.auto_display_name = False
        self.author_form.first_name_edit.setText(author.first_name)
        self.author_form.last_name_edit.setText(author.last_name)
        self.author_form.display_edit.setText(author.display_name)
        # Save the author's first and last name as well as the display name
        # for the case that they have to be restored.
        temp_first_name = author.first_name
        temp_last_name = author.last_name
        temp_display_name = author.display_name
        if self.author_form.exec_(False):
            author.first_name = self.author_form.first_name_edit.text()
            author.last_name = self.author_form.last_name_edit.text()
            author.display_name = self.author_form.display_edit.text()
            if self.check_author_exists(author, True):
                if self.manager.save_object(author):
                    self.reset_authors()
                    if not self.from_song_edit:
                        Registry().execute('songs_load_list')
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
            elif critical_error_message_box(message=translate(
                'SongsPlugin.SongMaintenanceForm', 'The author %s already exists. Would you like to make songs with '
                'author %s use the existing author %s?') %
                    (author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \
                    QtGui.QMessageBox.Yes:
                self._merge_objects(author, self.merge_authors, self.reset_authors)
            else:
                # We restore the author's old first and last name as well as
                # his display name.
                author.first_name = temp_first_name
                author.last_name = temp_last_name
                author.display_name = temp_display_name
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm',
                                      'Could not save your modified author, because the author already exists.'))

    def on_edit_topic_button_clicked(self):
        """
        Edit a topic.
        """
        topic_id = self._get_current_item_id(self.topics_list_widget)
        if topic_id == -1:
            return
        topic = self.manager.get_object(Topic, topic_id)
        self.topic_form.name = topic.name
        # Save the topic's name for the case that he has to be restored.
        temp_name = topic.name
        if self.topic_form.exec_(False):
            topic.name = self.topic_form.name_edit.text()
            if self.check_topic_exists(topic, True):
                if self.manager.save_object(topic):
                    self.reset_topics()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
            elif critical_error_message_box(
                message=translate('SongsPlugin.SongMaintenanceForm',
                                  'The topic %s already exists. Would you like to make songs with topic %s use the '
                                  'existing topic %s?') % (topic.name, temp_name, topic.name),
                    parent=self, question=True) == QtGui.QMessageBox.Yes:
                self._merge_objects(topic, self.merge_topics, self.reset_topics)
            else:
                # We restore the topics's old name.
                topic.name = temp_name
                critical_error_message_box(
                    message=translate('SongsPlugin.SongMaintenanceForm',
                                      'Could not save your modified topic, because it already exists.'))

    def on_edit_book_button_clicked(self):
        """
        Edit a book.
        """
        book_id = self._get_current_item_id(self.song_books_list_widget)
        if book_id == -1:
            return
        book = self.manager.get_object(Book, book_id)
        if book.publisher is None:
            book.publisher = ''
        self.song_book_form.name_edit.setText(book.name)
        self.song_book_form.publisher_edit.setText(book.publisher)
        # Save the book's name and publisher for the case that they have to
        # be restored.
        temp_name = book.name
        temp_publisher = book.publisher
        if self.song_book_form.exec_(False):
            book.name = self.song_book_form.name_edit.text()
            book.publisher = self.song_book_form.publisher_edit.text()
            if self.check_song_book_exists(book, True):
                if self.manager.save_object(book):
                    self.reset_song_books()
                else:
                    critical_error_message_box(
                        message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
            elif critical_error_message_box(
                message=translate('SongsPlugin.SongMaintenanceForm',
                                  'The book %s already exists. Would you like to make '
                                  'songs with book %s use the existing book %s?') % (book.name, temp_name, book.name),
                    parent=self, question=True) == QtGui.QMessageBox.Yes:
                self._merge_objects(book, self.merge_song_books, self.reset_song_books)
            else:
                # We restore the book's old name and publisher.
                book.name = temp_name
                book.publisher = temp_publisher

    def _merge_objects(self, db_object, merge, reset):
        """
        Utility method to merge two objects to leave one in the database.
        """
        self.application.set_busy_cursor()
        merge(db_object)
        reset()
        if not self.from_song_edit:
            Registry().execute('songs_load_list')
        self.application.set_normal_cursor()

    def merge_authors(self, old_author):
        """
        Merges two authors into one author.

        :param old_author: The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_author = self.manager.get_object_filtered(
            Author,
            and_(
                Author.first_name == old_author.first_name,
                Author.last_name == old_author.last_name,
                Author.display_name == old_author.display_name,
                Author.id != old_author.id
            )
        )
        # Find the songs, which have the old_author as author.
        songs = self.manager.get_all_objects(Song, Song.authors.contains(old_author))
        for song in songs:
            for author_song in song.authors_songs:
                song.add_author(existing_author, author_song.author_type)
                song.remove_author(old_author, author_song.author_type)
            self.manager.save_object(song)
        self.manager.delete_object(Author, old_author.id)

    def merge_topics(self, old_topic):
        """
        Merges two topics into one topic.

        :param old_topic: The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_topic = self.manager.get_object_filtered(
            Topic, and_(Topic.name == old_topic.name, Topic.id != old_topic.id)
        )
        # Find the songs, which have the old_topic as topic.
        songs = self.manager.get_all_objects(Song, Song.topics.contains(old_topic))
        for song in songs:
            # We check if the song has already existing_topic as topic. If that
            # is not the case we add it.
            if existing_topic not in song.topics:
                song.topics.append(existing_topic)
            song.topics.remove(old_topic)
            self.manager.save_object(song)
        self.manager.delete_object(Topic, old_topic.id)

    def merge_song_books(self, old_song_book):
        """
        Merges two books into one book.

        ``old_song_book``
            The object, which was edited, that will be deleted
        """
        # Find the duplicate.
        existing_book = self.manager.get_object_filtered(
            Book,
            and_(
                Book.name == old_song_book.name,
                Book.publisher == old_song_book.publisher,
                Book.id != old_song_book.id
            )
        )
        # Find the songs, which have the old_song_book as book.
        songs = self.manager.get_all_objects(Song, Song.song_book_id == old_song_book.id)
        for song in songs:
            song.song_book_id = existing_book.id
            self.manager.save_object(song)
        self.manager.delete_object(Book, old_song_book.id)

    def on_delete_author_button_clicked(self):
        """
        Delete the author if the author is not attached to any songs.
        """
        self._delete_item(Author, self.authors_list_widget, self.reset_authors,
                          translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'Are you sure you want to delete the selected author?'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'This author cannot be deleted, they are currently assigned to at least one song'
                                    '.'))

    def on_delete_topic_button_clicked(self):
        """
        Delete the Book if the Book is not attached to any songs.
        """
        self._delete_item(Topic, self.topics_list_widget, self.reset_topics,
                          translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'Are you sure you want to delete the selected topic?'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'This topic cannot be deleted, it is currently assigned to at least one song.'))

    def on_delete_book_button_clicked(self):
        """
        Delete the Book if the Book is not attached to any songs.
        """
        self._delete_item(Book, self.song_books_list_widget, self.reset_song_books,
                          translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'Are you sure you want to delete the selected book?'),
                          translate('SongsPlugin.SongMaintenanceForm',
                                    'This book cannot be deleted, it is currently assigned to at least one song.'))

    def on_authors_list_row_changed(self, row):
        """
        Called when the *authors_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_author_button, self.delete_author_button)

    def on_topics_list_row_changed(self, row):
        """
        Called when the *topics_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_topic_button, self.delete_topic_button)

    def on_song_books_list_row_changed(self, row):
        """
        Called when the *song_books_list_widget*'s current row has changed.
        """
        self._row_change(row, self.edit_book_button, self.delete_book_button)

    def _row_change(self, row, edit_button, delete_button):
        """
        Utility method to toggle if buttons are enabled.

        ``row``
            The current row. If there is no current row, the value is -1.
        """
        if row == -1:
            delete_button.setEnabled(False)
            edit_button.setEnabled(False)
        else:
            delete_button.setEnabled(True)
            edit_button.setEnabled(True)
Example #8
0
class TestAuthorsForm(TestCase, TestMixin):
    """
    Test the AuthorsForm class
    """
    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtWidgets.QMainWindow()
        Registry().register('main_window', self.main_window)
        self.form = AuthorsForm()

    def tearDown(self):
        """
        Delete all the C++ objects at the end so that we don't have a segfault
        """
        del self.form
        del self.main_window

    def test_ui_defaults(self):
        """
        Test the AuthorForm defaults are correct
        """
        assert self.form.first_name_edit.text(
        ) == '', 'The first name edit should be empty'
        assert self.form.last_name_edit.text(
        ) == '', 'The last name edit should be empty'
        assert self.form.display_edit.text(
        ) == '', 'The display name edit should be empty'

    def test_get_first_name_property(self):
        """
        Test that getting the first name property on the AuthorForm works correctly
        """
        # GIVEN: A first name to set
        first_name = 'John'

        # WHEN: The first_name_edit's text is set
        self.form.first_name_edit.setText(first_name)

        # THEN: The first_name property should have the correct value
        assert self.form.first_name == first_name, 'The first name property should be correct'

    def test_set_first_name_property(self):
        """
        Test that setting the first name property on the AuthorForm works correctly
        """
        # GIVEN: A first name to set
        first_name = 'James'

        # WHEN: The first_name property is set
        self.form.first_name = first_name

        # THEN: The first_name_edit should have the correct value
        assert self.form.first_name_edit.text(
        ) == first_name, 'The first name should be set correctly'

    def test_get_last_name_property(self):
        """
        Test that getting the last name property on the AuthorForm works correctly
        """
        # GIVEN: A last name to set
        last_name = 'Smith'

        # WHEN: The last_name_edit's text is set
        self.form.last_name_edit.setText(last_name)

        # THEN: The last_name property should have the correct value
        assert self.form.last_name == last_name, 'The last name property should be correct'

    def test_set_last_name_property(self):
        """
        Test that setting the last name property on the AuthorForm works correctly
        """
        # GIVEN: A last name to set
        last_name = 'Potter'

        # WHEN: The last_name property is set
        self.form.last_name = last_name

        # THEN: The last_name_edit should have the correct value
        assert self.form.last_name_edit.text(
        ) == last_name, 'The last name should be set correctly'

    def test_get_display_name_property(self):
        """
        Test that getting the display name property on the AuthorForm works correctly
        """
        # GIVEN: A display name to set
        display_name = 'John'

        # WHEN: The display_name_edit's text is set
        self.form.display_edit.setText(display_name)

        # THEN: The display_name property should have the correct value
        assert self.form.display_name == display_name, 'The display name property should be correct'

    def test_set_display_name_property(self):
        """
        Test that setting the display name property on the AuthorForm works correctly
        """
        # GIVEN: A display name to set
        display_name = 'John'

        # WHEN: The display_name property is set
        self.form.display_name = display_name

        # THEN: The display_name_edit should have the correct value
        assert self.form.display_edit.text(
        ) == display_name, 'The display name should be set correctly'

    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.exec')
    def test_exec(self, mocked_exec):
        """
        Test the exec() method
        """
        # GIVEN: An authors for and various mocked objects
        with patch.object(self.form.first_name_edit, 'clear') as mocked_first_name_edit_clear, \
                patch.object(self.form.last_name_edit, 'clear') as mocked_last_name_edit_clear, \
                patch.object(self.form.display_edit, 'clear') as mocked_display_edit_clear, \
                patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus:
            # WHEN: The exec() method is called
            self.form.exec(clear=True)

            # THEN: The clear and exec() methods should have been called
        mocked_first_name_edit_clear.assert_called_once_with()
        mocked_last_name_edit_clear.assert_called_once_with()
        mocked_display_edit_clear.assert_called_once_with()
        mocked_first_name_edit_setFocus.assert_called_once_with()
        mocked_exec.assert_called_once_with(self.form)

    def test_first_name_edited(self):
        """
        Test the on_first_name_edited() method
        """
        # GIVEN: An author form
        self.form.auto_display_name = True

        with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_last_name_edit_text.return_value = 'Newton'

            # WHEN: on_first_name_edited() is called
            self.form.on_first_name_edited('John')

        # THEN: The display name should be updated
        assert mocked_last_name_edit_text.call_count == 2
        mocked_display_edit_setText.assert_called_once_with('John Newton')

    def test_first_name_edited_no_auto(self):
        """
        Test the on_first_name_edited() method without auto_display_name
        """
        # GIVEN: An author form
        self.form.auto_display_name = False

        with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:

            # WHEN: on_first_name_edited() is called
            self.form.on_first_name_edited('John')

        # THEN: The display name should not be updated
        assert mocked_last_name_edit_text.call_count == 0
        assert mocked_display_edit_setText.call_count == 0

    def test_last_name_edited(self):
        """
        Test the on_last_name_edited() method
        """
        # GIVEN: An author form
        self.form.auto_display_name = True

        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_first_name_edit_text.return_value = 'John'

            # WHEN: on_last_name_edited() is called
            self.form.on_last_name_edited('Newton')

        # THEN: The display name should be updated
        assert mocked_first_name_edit_text.call_count == 2
        mocked_display_edit_setText.assert_called_once_with('John Newton')

    def test_last_name_edited_no_auto(self):
        """
        Test the on_last_name_edited() method without auto_display_name
        """
        # GIVEN: An author form
        self.form.auto_display_name = False

        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:

            # WHEN: on_last_name_edited() is called
            self.form.on_last_name_edited('Newton')

        # THEN: The display name should not be updated
        assert mocked_first_name_edit_text.call_count == 0
        assert mocked_display_edit_setText.call_count == 0

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_first_name(self, mocked_critical_error):
        """
        Test the accept() method with no first name
        """
        # GIVEN: A form and no text in thefirst name edit
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus:
            mocked_first_name_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(
            message='You need to type in the first name of the author.')
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_first_name_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_last_name(self, mocked_critical_error):
        """
        Test the accept() method with no last name
        """
        # GIVEN: A form and no text in the last name edit
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.last_name_edit, 'setFocus') as mocked_last_name_edit_setFocus:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(
            message='You need to type in the last name of the author.')
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    def test_accept_no_display_name_no_combine(self, mocked_critical_error):
        """
        Test the accept() method with no display name and no combining
        """
        # GIVEN: A form and no text in the display name edit
        mocked_critical_error.return_value = QtWidgets.QMessageBox.No
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \
                patch.object(self.form.display_edit, 'setFocus') as mocked_display_edit_setFocus:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is False
        mocked_critical_error.assert_called_once_with(
            message=
            'You have not set a display name for the author, combine the first and last names?',
            parent=self.form,
            question=True)
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_display_edit_text.assert_called_once_with()
        mocked_display_edit_setFocus.assert_called_once_with()

    @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box')
    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept')
    def test_accept_no_display_name(self, mocked_accept,
                                    mocked_critical_error):
        """
        Test the accept() method with no display name and auto-combine
        """
        # GIVEN: A form and no text in the display name edit
        mocked_accept.return_value = True
        mocked_critical_error.return_value = QtWidgets.QMessageBox.Yes
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \
                patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = ''

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is True
        mocked_critical_error.assert_called_once_with(
            message=
            'You have not set a display name for the author, combine the first and last names?',
            parent=self.form,
            question=True)
        assert mocked_first_name_edit_text.call_count == 2
        assert mocked_last_name_edit_text.call_count == 2
        mocked_display_edit_text.assert_called_once_with()
        mocked_display_edit_setText.assert_called_once_with('John Newton')
        mocked_accept.assert_called_once_with(self.form)

    @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept')
    def test_accept(self, mocked_accept):
        """
        Test the accept() method
        """
        # GIVEN: A form and text in the right places
        mocked_accept.return_value = True
        with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \
                patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \
                patch.object(self.form.display_edit, 'text') as mocked_display_edit_text:
            mocked_first_name_edit_text.return_value = 'John'
            mocked_last_name_edit_text.return_value = 'Newton'
            mocked_display_edit_text.return_value = 'John Newton'

            # WHEN: accept() is called
            result = self.form.accept()

        # THEN: The result should be false and a critical error displayed
        assert result is True
        mocked_first_name_edit_text.assert_called_once_with()
        mocked_last_name_edit_text.assert_called_once_with()
        mocked_display_edit_text.assert_called_once_with()
        mocked_accept.assert_called_once_with(self.form)