15.1.271. tablet_qt/menu/choosepatientmenu.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

// #define DEBUG_VERBOSE

#include "choosepatientmenu.h"
#include <QDebug>
#include <QPushButton>
#include "dbobjects/patient.h"
#include "dbobjects/patientsorter.h"
#include "dialogs/nvpchoicedialog.h"
#include "dialogs/scrollmessagebox.h"
#include "lib/stringfunc.h"
#include "lib/uifunc.h"
#include "menulib/menuheader.h"


ChoosePatientMenu::ChoosePatientMenu(CamcopsApp& app) :
    MenuWindow(app, uifunc::iconFilename(uiconst::ICON_CHOOSE_PATIENT))
{
}


void ChoosePatientMenu::extraLayoutCreation()
{
    connect(&m_app, &CamcopsApp::selectedPatientDetailsChanged,
            this, &ChoosePatientMenu::refreshPatientList,
            Qt::UniqueConnection);
    connect(&m_app, &CamcopsApp::refreshPatientList,
            this, &ChoosePatientMenu::refreshPatientList,
            Qt::UniqueConnection);

    // Set other header buttons
    m_p_header->offerAdd(true);

    connect(m_p_header, &MenuHeader::addClicked,
            this, &ChoosePatientMenu::addPatient,
            Qt::UniqueConnection);
}


QString ChoosePatientMenu::title() const
{
    return tr("Choose patient");
}


void ChoosePatientMenu::makeItems()
{
    // Patient
    PatientPtrList patients = m_app.getAllPatients();
    m_items = {
        MenuItem(tr("Special functions")).setLabelOnly(),
        MenuItem(
            txtMergeTitle(),
            std::bind(&ChoosePatientMenu::mergePatients, this),
            "",  // icon
            tr("Choose one patient, then select this option to merge with another")  // subtitle
        ).setNotIfLocked(),
        MenuItem(tr("Patients")).setLabelOnly(),
    };
    // qDebug() << Q_FUNC_INFO << "-" << patients.size() << "patient(s)";
    for (const PatientPtr& patient : patients) {
        m_items.append(MenuItem(patient));
    }
}


void ChoosePatientMenu::viewItem()
{
    editPatient(true);
}


void ChoosePatientMenu::editItem()
{
    editPatient(false);
}


void ChoosePatientMenu::deleteItem()
{
    deletePatient();
}


void ChoosePatientMenu::addPatient()
{
#ifdef DEBUG_VERBOSE
    qDebug() << Q_FUNC_INFO;
#endif
    // The patient we create here needs to stay in scope for the duration of
    // editing! The simplest way is to use a member object to hold the pointer.

    PatientPtr patient = PatientPtr(new Patient(m_app, m_app.db()));
    patient->save();
    // v2.2.0 fix:
    // MUST call m_app.setSelectedPatient before
    // CamcopsApp::open(..., patient), because when the editor closes,
    // CamcopsApp::close() will be called, and that will call
    // selectedPatientDetailsChanged() on the patient in question; this gives
    // the *impression* of the selected patient changing (e.g. name display
    // changes) but the underlying selection won't have, which is Bad; a
    // previously selected patient's tasks continue to show up but with the
    // newly-created patient's name.
    m_app.setSelectedPatient(patient->id());
    OpenableWidget* widget = patient->editor(false);
    m_app.openSubWindow(widget, TaskPtr(nullptr), false, patient);
}


void ChoosePatientMenu::editPatient(const bool read_only)
{
#ifdef DEBUG_VERBOSE
    qDebug() << Q_FUNC_INFO;
#endif
    PatientPtr patient = currentPatient();
    if (!patient) {
        uifunc::alert("Bug: null patient pointer in ChoosePatientMenu::editPatient");
        return;
    }
    OpenableWidget* widget = patient->editor(read_only);
    m_app.openSubWindow(widget, TaskPtr(nullptr), false, patient);
}


void ChoosePatientMenu::deletePatient()
{
    qDebug() << Q_FUNC_INFO;
    PatientPtr patient = currentPatient();
    if (!patient) {
        uifunc::alert("Bug: null patient pointer in ChoosePatientMenu::editPatient");
        return;
    }
    QString patient_details = patient->twoLineDetailString();

    // First check
    {
        ScrollMessageBox msgbox(
                    QMessageBox::Warning,
                    tr("Delete patient"),
                    tr("Delete this patient?") + "\n\n" +  patient_details,
                    this);
        QAbstractButton* delete_button = msgbox.addButton(
                    tr("Yes, delete"), QMessageBox::YesRole);
        msgbox.addButton(tr("No, cancel"), QMessageBox::NoRole);
        msgbox.exec();
        if (msgbox.clickedButton() != delete_button) {
            return;
        }
    }

    // Second check
    int n_tasks = patient->numTasks();
    if (n_tasks > 0) {
        ScrollMessageBox msgbox(
                    QMessageBox::Warning,
                    tr("Delete patient WITH TASKS"),
                    tr("Delete this patient?") + "\n\n" + patient_details +
                        "\n\n" + tr("THERE ARE %1 ASSOCIATED TASKS!").arg(n_tasks),
                    this);
        // NB can't use HTML "<b></b>" in the text there.
        QAbstractButton* delete_button = msgbox.addButton(
                    tr("Yes, delete despite tasks"), QMessageBox::YesRole);
        msgbox.addButton(tr("No, cancel"), QMessageBox::NoRole);
        msgbox.exec();
        if (msgbox.clickedButton() != delete_button) {
            return;
        }
    }

    // Delete
    qInfo() << "Deleting patient:" << patient_details;
    patient->deleteFromDatabase();
    qInfo() << "... patient deleted";
    m_app.setDefaultPatient();
    refreshPatientList();
}


void ChoosePatientMenu::refreshPatientList()
{
    rebuild(false);  // no need to rebuild header
}


void ChoosePatientMenu::mergePatients()
{
    auto reportFail = [this](const QString& text) -> void {
        ScrollMessageBox::warning(this, txtMergeTitle(), text);
    };

    // Is one selected?
    if (!m_app.isPatientSelected()) {
        reportFail(tr("Select a patient first, then choose this option to "
                      "merge with another."));
        return;
    }

    // Get all others
    const PatientPtrList all_patients = m_app.getAllPatients();
    PatientPtrList other_patients;
    Patient* selected_patient = m_app.selectedPatient();
    for (const PatientPtr& other : all_patients) {
        if (other->id() != selected_patient->id() &&
                other->matchesForMerge(selected_patient)) {
            other_patients.append(other);
        }
    }
    if (other_patients.isEmpty()) {
        reportFail(tr(
                "No other patients available that match the selected "
                "patient. (Information can be present in one patient and "
                "missing from the other, but where information is present, "
                "it must match.)"));
        return;
    }

    // Offer the user a choice of the others
    std::sort(other_patients.begin(), other_patients.end(), PatientSorter());
    NameValueOptions options;
    for (const PatientPtr& other : other_patients) {
        options.append(NameValuePair(other->descriptionForMerge(),
                                     other->pkvalue()));
    }
    NvpChoiceDialog dlg(this, options, tr("Choose other patient"));
    QVariant chosen_other_pk;
    if (dlg.choose(&chosen_other_pk) != QDialog::Accepted) {
        return;  // user pressed cancel, or some such
    }
    PatientPtr chosen_other = nullptr;
    for (PatientPtr& other : other_patients) {
        if (other->pkvalue() == chosen_other_pk) {
            chosen_other = other;
            break;
        }
    }
    Q_ASSERT(chosen_other);

    // Confirm
    QStringList confirm_lines{
        stringfunc::bold(tr("Please confirm:")),
        stringfunc::bold(tr("MERGE:")),
        selected_patient->descriptionForMerge(),
        stringfunc::bold(tr("WITH:")),
        chosen_other->descriptionForMerge(),
        stringfunc::bold("?"),
    };
    QString confirm_text = confirm_lines.join("<br><br>");
    const QString yes = tr("Yes, merge");
    const QString no = tr("No, cancel");
    if (!uifunc::confirm(confirm_text, txtMergeTitle(), yes, no, this)) {
        return;
    }
    confirm_lines.prepend(tr("ARE YOU SURE?"));
    confirm_text = confirm_lines.join("<br><br>");
    if (!uifunc::confirm(confirm_text, txtMergeTitle(), yes, no, this)) {
        return;
    }

    // Perform the merge
    qInfo() << Q_FUNC_INFO << "Copying patient information and moving tasks...";
    selected_patient->mergeInDetailsAndTakeTasksFrom(chosen_other.data());
    qInfo() << Q_FUNC_INFO << "Deleting other patient...";
    chosen_other->deleteFromDatabase();
    qInfo() << Q_FUNC_INFO << "Merge complete.";

    // Refresh list, etc.
    m_app.setDefaultPatient();
    refreshPatientList();
}


QString ChoosePatientMenu::txtMergeTitle()
{
    return tr("Merge patients");
}