From d7fe052afc67efd2211cdc0e69f3341be1d78fbd Mon Sep 17 00:00:00 2001 From: robbie jackson Date: Thu, 15 Apr 2021 17:33:10 +0000 Subject: [PATCH] Confirmation form --- openlp/core/ui/confirmationdialog.py | 86 ++++++++++++++++++ openlp/core/ui/confirmationform.py | 52 +++++++++++ openlp/plugins/songs/lib/mediaitem.py | 14 +-- tests/openlp_core/ui/test_confirmationform.py | 91 +++++++++++++++++++ 4 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 openlp/core/ui/confirmationdialog.py create mode 100644 openlp/core/ui/confirmationform.py create mode 100644 tests/openlp_core/ui/test_confirmationform.py diff --git a/openlp/core/ui/confirmationdialog.py b/openlp/core/ui/confirmationdialog.py new file mode 100644 index 000000000..14c7c920c --- /dev/null +++ b/openlp/core/ui/confirmationdialog.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2021 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, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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, see . # +########################################################################## + +from PyQt5 import QtGui, QtWidgets, Qt + +from openlp.core.ui.icons import UiIcons + + +class Ui_ConfirmationDialog(): + """ + The UI for the Confirmation dialog. + """ + + def setup_ui(self, confirmation_dialog, title, items, message, width=400, height=600): + """ + Set up the UI for the confirmation dialog. + + :param confirmation_dialog: The QDialog object to set up. + :param items: A list (or other iterable) containing the items + :param message: The confirmation message to display + :param width: Width of the dialog window + :param height: Height of the dialog window + + :return: None + """ + # overall aspects for confirmation dialog + confirmation_dialog.setObjectName('confirmation_dialog') + confirmation_dialog.setWindowIcon(UiIcons().main_icon) + confirmation_dialog.resize(width, height) + confirmation_dialog.setWindowTitle(title) + + self.confirmation_layout = QtWidgets.QVBoxLayout(confirmation_dialog) + self.confirmation_layout.setObjectName('confirmation_layout') + + # listview to display the items + self.listview = QtWidgets.QListView(self) + self.listview.setObjectName("confirmation listview") + # make the entries read-only + self.listview.setEditTriggers(Qt.QAbstractItemView.NoEditTriggers) + self.confirmation_layout.addWidget(self.listview) + + # add the items to the listview model + model = QtGui.QStandardItemModel() + self.listview.setModel(model) + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + # confirmation message and Yes/No buttons + self.message_and_buttons = QtWidgets.QWidget(confirmation_dialog) + self.message_and_buttons.setObjectName('message and buttons') + self.message_and_buttons_layout = QtWidgets.QHBoxLayout(self.message_and_buttons) + self.message_and_buttons_layout.setObjectName('message and buttons layout') + + self.message_label = QtWidgets.QLabel(message) + self.message_label.setObjectName('message') + self.message_label.setWordWrap(True) + self.message_and_buttons_layout.addWidget(self.message_label) + + button_types = QtWidgets.QDialogButtonBox.Yes | QtWidgets.QDialogButtonBox.No + self.buttonBox = QtWidgets.QDialogButtonBox(button_types) + self.buttonBox.setObjectName('buttons') + self.message_and_buttons_layout.addWidget(self.buttonBox) + + self.confirmation_layout.addWidget(self.message_and_buttons) + + # slot connections + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) diff --git a/openlp/core/ui/confirmationform.py b/openlp/core/ui/confirmationform.py new file mode 100644 index 000000000..883a3921e --- /dev/null +++ b/openlp/core/ui/confirmationform.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2021 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, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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, see . # +########################################################################## +""" +A general-purpose confirmation form. This can be used to obtain confirmation +that the user wants to delete a list of items, for example. +It is implemented as a QDialog containing: +- a QListView to display the list of items (eg the items to be deleted) +- a message which asks the user for confirmation +- Yes and No buttons, with Yes being the default +After instantiating a ConfirmationForm object the calling code must call exec() on the instance. +This runs the Qt dialog, which returns the usual 0 (for No) or 1 (for Yes) +""" +from PyQt5 import QtCore, QtWidgets + +from openlp.core.ui.confirmationdialog import Ui_ConfirmationDialog + + +class ConfirmationForm(QtWidgets.QDialog, Ui_ConfirmationDialog): + """ + The Confirmation form + """ + + def __init__(self, parent, title, items, message, width=400, height=600): + """ + :param parent: The parent QWidget + :param title: The title to be applied to the QDialog window + :param items: A list (or other iterable) of Strings for the items + :param message: The confirmation message to display + :param width: Width of the dialog window + :param height: Height of the dialog window + """ + super(ConfirmationForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | + QtCore.Qt.WindowCloseButtonHint) + self.setup_ui(self, title, items, message, width, height) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index dcfcb3aa7..798a2e433 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -38,6 +38,7 @@ from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.ui import create_widget_action, critical_error_message_box from openlp.core.ui.icons import UiIcons +from openlp.core.ui.confirmationform import ConfirmationForm from openlp.plugins.songs.forms.editsongform import EditSongForm from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songimportform import SongImportForm @@ -490,16 +491,15 @@ class SongMediaItem(MediaManagerItem): def on_delete_click(self): """ - Remove a song from the list and database + Remove a song or songs from the list and database """ if check_item_selected(self.list_view, UiStrings().SelectDelete): items = self.list_view.selectedItems() - if QtWidgets.QMessageBox.question( - self, UiStrings().ConfirmDelete, - translate('SongsPlugin.MediaItem', - 'Are you sure you want to delete the following songs?') + - '\n\n- {songs}'.format(songs='\n- '.join([item.text() for item in items])), - defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No: + item_strings = map(lambda i: i.text(), items) + delete_confirmed = ConfirmationForm(self, UiStrings().ConfirmDelete, item_strings, + translate('SongsPlugin.MediaItem', + 'Are you sure you want to delete these songs?')).exec() + if not delete_confirmed: return self.application.set_busy_cursor() self.main_window.display_progress_bar(len(items)) diff --git a/tests/openlp_core/ui/test_confirmationform.py b/tests/openlp_core/ui/test_confirmationform.py new file mode 100644 index 000000000..bd10f81d7 --- /dev/null +++ b/tests/openlp_core/ui/test_confirmationform.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2021 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, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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, see . # +########################################################################## +""" +Test the Confirmation Form +""" +from unittest.mock import patch +from PyQt5 import QtWidgets, QtTest, QtCore + +from openlp.core.ui.confirmationform import ConfirmationForm + + +def test_confirmation_form_ui_setup(settings): + """ + Test checking the UI elements are set up correctly + """ + # GIVEN: A ConfirmationForm class + + # WHEN: A ConfirmationForm object is created + form = ConfirmationForm(None, "title", ["item1", "item2", "item3"], "confirm?") + + # THEN: The UI elements should reflect the parameters passed + assert form.windowTitle() == "title" + assert form.findChild(QtWidgets.QListView, "confirmation listview").model().rowCount() == 3 + assert form.findChild(QtWidgets.QListView, "confirmation listview").model().item(0).text() == "item1" + assert form.findChild(QtWidgets.QListView, "confirmation listview").model().item(1).text() == "item2" + assert form.findChild(QtWidgets.QListView, "confirmation listview").model().item(2).text() == "item3" + assert form.findChild(QtWidgets.QLabel, "message").text() == "confirm?" + + +@patch('openlp.core.ui.confirmationform.ConfirmationForm.accept') +@patch('openlp.core.ui.confirmationform.ConfirmationForm.reject') +def test_confirmation_form_yes_button(mocked_reject, mocked_accept, settings): + """ + Test when the Yes button is clicked + """ + # GIVEN: A ConfirmationForm + form = ConfirmationForm(None, "title", ["item1", "item2", "item3"], "confirm?") + form.accept = mocked_accept + form.reject = mocked_reject + + # WHEN: The Yes button is clicked + buttons = form.findChildren(QtWidgets.QPushButton) + for button in buttons: + if 'Yes' in button.text(): + QtTest.QTest.mouseClick(button, QtCore.Qt.LeftButton) + break + + # THEN: accept is called + assert form.accept.call_count == 1 + assert form.reject.call_count == 0 + + +@patch('openlp.core.ui.confirmationform.ConfirmationForm.accept') +@patch('openlp.core.ui.confirmationform.ConfirmationForm.reject') +def test_confirmation_form_no_button(mocked_reject, mocked_accept, settings): + """ + Test when the No button is clicked + """ + # GIVEN: A ConfirmationForm + form = ConfirmationForm(None, "title", ["item1", "item2", "item3"], "confirm?") + form.accept = mocked_accept + form.reject = mocked_reject + + # WHEN: The No button is clicked + buttons = form.findChildren(QtWidgets.QPushButton) + for button in buttons: + if 'No' in button.text(): + QtTest.QTest.mouseClick(button, QtCore.Qt.LeftButton) + break + + # THEN: reject is called + assert form.accept.call_count == 0 + assert form.reject.call_count == 1