# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.forms.authorsform import AuthorsForm
from openlp.plugins.songs.forms.topicsform import TopicsForm
from openlp.plugins.songs.forms.songbookform import SongBookForm
from openlp.plugins.songs.lib.db import Author, Book, Topic, Song
from .songmaintenancedialog import Ui_SongMaintenanceDialog
log = logging.getLogger(__name__)
[docs]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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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
[docs] 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.'))
[docs] 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.'))
[docs] 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.'))
[docs] 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.'))
[docs] 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.'))
[docs] 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()
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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'
'.'))
[docs] 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.'))
[docs] 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.'))
[docs] 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)
[docs] 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)
[docs] 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)