15.1.712. tablet_qt/tasks/gmcpq.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/>.
*/

#include "gmcpq.h"
#include "core/camcopsapp.h"
#include "common/textconst.h"
#include "maths/mathfunc.h"
#include "lib/stringfunc.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/namevaluepair.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qumcq.h"
#include "questionnairelib/qumcqgrid.h"
#include "questionnairelib/qumultipleresponse.h"
#include "questionnairelib/qutext.h"
#include "tasklib/taskfactory.h"
#include "tasklib/taskregistrar.h"
using mathfunc::noneNull;
using stringfunc::strnum;

const QString GmcPq::GMCPQ_TABLENAME("gmcpq");
const int N_ETHNICITY_OPTIONS = 16;

const QString DOCTOR("doctor");
const QString Q1("q1");
const QString Q2A("q2a");
const QString Q2B("q2b");
const QString Q2C("q2c");
const QString Q2D("q2d");
const QString Q2E("q2e");
const QString Q2F("q2f");
const QString Q2F_DETAILS("q2f_details");
const QString Q3("q3");
const QString Q4A("q4a");
const QString Q4B("q4b");
const QString Q4C("q4c");
const QString Q4D("q4d");
const QString Q4E("q4e");
const QString Q4F("q4f");
const QString Q4G("q4g");
const QString Q5A("q5a");
const QString Q5B("q5b");
const QString Q6("q6");
const QString Q7("q7");
const QString Q8("q8");
const QString Q9("q9");  // other comments
const QString Q10("q10");  // sex
const QString Q11("q11");
const QString Q12("q12");  // ethnicity
const QString Q12_DETAILS("q12_details");


void initializeGmcPq(TaskFactory& factory)
{
    static TaskRegistrar<GmcPq> registered(factory);
}


GmcPq::GmcPq(CamcopsApp& app, DatabaseManager& db, const int load_pk) :
    Task(app, db, GMCPQ_TABLENAME, true, false, false)  // ... anon, clin, resp
{
    addField(DOCTOR, QMetaType::fromType<QString>());
    addField(Q1, QMetaType::fromType<int>());
    addField(Q2A, QMetaType::fromType<bool>());
    addField(Q2B, QMetaType::fromType<bool>());
    addField(Q2C, QMetaType::fromType<bool>());
    addField(Q2D, QMetaType::fromType<bool>());
    addField(Q2E, QMetaType::fromType<bool>());
    addField(Q2F, QMetaType::fromType<bool>());
    addField(Q2F_DETAILS, QMetaType::fromType<QString>());
    addField(Q3, QMetaType::fromType<int>());
    addField(Q4A, QMetaType::fromType<int>());
    addField(Q4B, QMetaType::fromType<int>());
    addField(Q4C, QMetaType::fromType<int>());
    addField(Q4D, QMetaType::fromType<int>());
    addField(Q4E, QMetaType::fromType<int>());
    addField(Q4F, QMetaType::fromType<int>());
    addField(Q4G, QMetaType::fromType<int>());
    addField(Q5A, QMetaType::fromType<int>());
    addField(Q5B, QMetaType::fromType<int>());
    addField(Q6, QMetaType::fromType<bool>());
    addField(Q7, QMetaType::fromType<bool>());
    addField(Q8, QMetaType::fromType<bool>());
    addField(Q9, QMetaType::fromType<QString>());
    addField(Q10, QMetaType::fromType<QString>());
    addField(Q11, QMetaType::fromType<int>());
    addField(Q12, QMetaType::fromType<int>());  // ethnicity
    addField(Q12_DETAILS, QMetaType::fromType<QString>());

    load(load_pk);  // MUST ALWAYS CALL from derived Task constructor.
}


// ============================================================================
// Class info
// ============================================================================

QString GmcPq::shortname() const
{
    return "GMC-PQ";
}


QString GmcPq::longname() const
{
    return tr("UK General Medical Council (GMC) Patient Questionnaire");
}


QString GmcPq::description() const
{
    return tr("Questionnaire for patients to provide anonymous feedback to "
              "their doctors.");
}


// ============================================================================
// Instance info
// ============================================================================

bool GmcPq::isComplete() const
{
    return noneNull(values(QStringList{
                               DOCTOR,
                               Q1,
                               // q2...?
                               Q3,
                               Q4A, Q4B, Q4C, Q4D, Q4E, Q4F, Q4G,
                               Q5A, Q5B,
                               Q6,
                               Q7,
                               Q8
    }));
}


QStringList GmcPq::summary() const
{
    return QStringList{fieldSummary(DOCTOR, xstring("q_doctor"), " ")};
}


QStringList GmcPq::detail() const
{
    const NameValueOptions q4options = optionsQ4();
    const NameValueOptions q5options = optionsQ5();
    QStringList lines = completenessInfo();
    const QString sp = " ";
    const QString co = ": ";
    lines.append(fieldSummary(DOCTOR, xstring("q_doctor"), sp));
    lines.append("");
    lines.append(fieldSummaryNameValueOptions(Q1, optionsQ1(), xstring("q1"), sp));
    lines.append(xstring("q2"));
    lines.append(fieldSummaryYesNoNull(Q2A, xstring("q2_a"), co));
    lines.append(fieldSummaryYesNoNull(Q2B, xstring("q2_b"), co));
    lines.append(fieldSummaryYesNoNull(Q2C, xstring("q2_c"), co));
    lines.append(fieldSummaryYesNoNull(Q2D, xstring("q2_d"), co));
    lines.append(fieldSummaryYesNoNull(Q2E, xstring("q2_e"), co));
    lines.append(fieldSummaryYesNoNull(Q2F, xstring("q2_f"), co));
    lines.append(fieldSummary(Q3, xstring("q3"), sp));
    lines.append(xstring("q4"));
    lines.append(fieldSummaryNameValueOptions(Q4A, q4options, xstring("q4_a"), co));
    lines.append(fieldSummaryNameValueOptions(Q4B, q4options, xstring("q4_b"), co));
    lines.append(fieldSummaryNameValueOptions(Q4C, q4options, xstring("q4_c"), co));
    lines.append(fieldSummaryNameValueOptions(Q4D, q4options, xstring("q4_d"), co));
    lines.append(fieldSummaryNameValueOptions(Q4E, q4options, xstring("q4_e"), co));
    lines.append(fieldSummaryNameValueOptions(Q4F, q4options, xstring("q4_f"), co));
    lines.append(fieldSummaryNameValueOptions(Q4G, q4options, xstring("q4_g"), co));
    lines.append(xstring("q5"));
    lines.append(fieldSummaryNameValueOptions(Q5A, q5options, xstring("q5_a"), co));
    lines.append(fieldSummaryNameValueOptions(Q5B, q5options, xstring("q5_b"), co));
    lines.append(fieldSummaryYesNoNull(Q6, xstring("q6"), co));
    lines.append(fieldSummaryYesNoNull(Q7, xstring("q7"), co));
    lines.append(fieldSummaryYesNoNull(Q8, xstring("q8"), sp));
    lines.append(fieldSummary(Q9, "9. " + xstring("q9_s"), co));
    lines.append(fieldSummary(Q10, "10. " + TextConst::sex(), co));
    lines.append(fieldSummaryNameValueOptions(Q11, optionsQ11(), xstring("q11"), sp));
    lines.append(fieldSummaryNameValueOptions(Q12, ethnicityOptions(m_app), xstring("q12"), sp));
    lines.append(fieldSummary(Q12_DETAILS, xstring("ethnicity_other_s"), co));
    return lines;
}


OpenableWidget* GmcPq::editor(const bool read_only)
{
    QVector<QuPagePtr> pages;
    const NameValueOptions yn_options = CommonOptions::yesNoBoolean();

    auto text = [this](const QString& xstringname) -> QuElement* {
        return new QuText(xstring(xstringname));
    };
    auto boldtext = [this](const QString& xstringname) -> QuElement* {
        return (new QuText(xstring(xstringname)))->setBold();
    };
    auto mcq = [this](const QString& fieldname,
                      const NameValueOptions& options) -> QuElement* {
        return new QuMcq(fieldRef(fieldname), options);
    };
    auto maketitle = [this](int page) -> QString {
        return xstring("titleprefix") + QString::number(page);
    };
    auto qf = [this](const QString& fieldname,
                     const QString& xstringname) -> QuestionWithOneField {
        return QuestionWithOneField(fieldRef(fieldname),
                                    xstring(xstringname));
    };
    auto yn = [this, &yn_options](const QString& fieldname) -> QuElement* {
        return new QuMcq(fieldRef(fieldname), yn_options);
    };

    pages.append(QuPagePtr((new QuPage{
        text("info1"),
        boldtext("please_enter_doctor"),
        new QuLineEdit(fieldRef(DOCTOR)),
        boldtext("info2"),
        text("q1"),
        mcq(Q1, optionsQ1()),
        boldtext("info3"),
    })->setTitle(maketitle(1))));

    pages.append(QuPagePtr((new QuPage{
        text("q2"),
        (new QuMultipleResponse{
            qf(Q2A, "q2_a"),
            qf(Q2B, "q2_b"),
            qf(Q2C, "q2_c"),
            qf(Q2D, "q2_d"),
            qf(Q2E, "q2_e"),
            qf(Q2F, "q2_f"),
        })->setMinimumAnswers(1),
        text("q2f_s"),
        new QuLineEdit(fieldRef(Q2F_DETAILS)),
    })->setTitle(maketitle(2))));

    pages.append(QuPagePtr((new QuPage{
        text("q3"),
        mcq(Q3, optionsQ3()),
    })->setTitle(maketitle(3))));

    pages.append(QuPagePtr((new QuPage{
        text("q4"),
        new QuMcqGrid(QVector<QuestionWithOneField>{qf(Q4A, "q4_a"),
                                                    qf(Q4B, "q4_b"),
                                                    qf(Q4C, "q4_c"),
                                                    qf(Q4D, "q4_d"),
                                                    qf(Q4E, "q4_e"),
                                                    qf(Q4F, "q4_f"),
                                                    qf(Q4G, "q4_g")},
                      optionsQ4()),
    })->setTitle(maketitle(4))));

    pages.append(QuPagePtr((new QuPage{
        text("q5"),
        new QuMcqGrid(QVector<QuestionWithOneField>{qf(Q5A, "q5_a"),
                                                    qf(Q5B, "q5_b")},
                      optionsQ5()),
    })->setTitle(maketitle(5))));

    pages.append(QuPagePtr((new QuPage{
        text("q6"),
        yn(Q6),
    })->setTitle(maketitle(6))));

    pages.append(QuPagePtr((new QuPage{
        text("q7"),
        yn(Q7),
    })->setTitle(maketitle(7))));

    pages.append(QuPagePtr((new QuPage{
        text("q8"),
        yn(Q8),
    })->setTitle(maketitle(8))));

    pages.append(QuPagePtr((new QuPage{
        text("q9"),
        text("q9_s"),
        new QuLineEdit(fieldRef(Q9, false)),
    })->setTitle(maketitle(9))));

    pages.append(QuPagePtr((new QuPage{
        text("q10"),
        mcq(Q10, CommonOptions::sexes()),
    })->setTitle(maketitle(10))));

    pages.append(QuPagePtr((new QuPage{
        text("q11"),
        mcq(Q11, optionsQ11()),
    })->setTitle(maketitle(11))));

    pages.append(QuPagePtr((new QuPage{
        text("q12"),
        mcq(Q12, ethnicityOptions(m_app)),
        text("ethnicity_other_s"),
        new QuLineEdit(fieldRef(Q12_DETAILS)),
    })->setTitle(maketitle(12))));

    pages.append(QuPagePtr((new QuPage{
        new QuText(TextConst::thankYou()),
    })->setTitle(TextConst::finished())));


    connect(fieldRef(Q2F).data(), &FieldRef::valueChanged,
            this, &GmcPq::updateMandatory);
    connect(fieldRef(Q12).data(), &FieldRef::valueChanged,
            this, &GmcPq::updateMandatory);
    updateMandatory();

    auto questionnaire = new Questionnaire(m_app, pages);
    questionnaire->setType(QuPage::PageType::Patient);
    questionnaire->setReadOnly(read_only);
    return questionnaire;
}


// ============================================================================
// Task-specific calculations
// ============================================================================

NameValueOptions GmcPq::ethnicityOptions(CamcopsApp& app)
{
    const QString& xstring_taskname = GMCPQ_TABLENAME;
    NameValueOptions options;
    for (int i = 1; i <= N_ETHNICITY_OPTIONS; ++i) {
        options.append(NameValuePair(
            app.xstring(xstring_taskname, strnum("ethnicity_option", i)),
            i));
    }
    return options;
}


bool GmcPq::ethnicityOther(int ethnicity_code)
{
    return ethnicity_code == 3 ||
            ethnicity_code == 7 ||
            ethnicity_code == 11 ||
            ethnicity_code == 14 ||
            ethnicity_code == 16;
}


NameValueOptions GmcPq::optionsQ1() const
{
    return NameValueOptions{
        {xstring("q1_option1"), 1},
        {xstring("q1_option2"), 2},
        {xstring("q1_option3"), 3},
        {xstring("q1_option4"), 4},
    };
}


NameValueOptions GmcPq::optionsQ3() const
{
    return NameValueOptions{
        {xstring("q3_option1"), 1},
        {xstring("q3_option2"), 2},
        {xstring("q3_option3"), 3},
        {xstring("q3_option4"), 4},
        {xstring("q3_option5"), 5},
    };
}


NameValueOptions GmcPq::optionsQ4() const
{
    return NameValueOptions{
        {xstring("q4_option1"), 1},
        {xstring("q4_option2"), 2},
        {xstring("q4_option3"), 3},
        {xstring("q4_option4"), 4},
        {xstring("q4_option5"), 5},
        {xstring("q4_option0"), 0},
    };
}


NameValueOptions GmcPq::optionsQ5() const
{
    return NameValueOptions{
        {xstring("q5_option1"), 1},
        {xstring("q5_option2"), 2},
        {xstring("q5_option3"), 3},
        {xstring("q5_option4"), 4},
        {xstring("q5_option5"), 5},
        {xstring("q5_option0"), 0},
    };
}


NameValueOptions GmcPq::optionsQ11() const
{
    return NameValueOptions{
        {xstring("q11_option1"), 1},
        {xstring("q11_option2"), 2},
        {xstring("q11_option3"), 3},
        {xstring("q11_option4"), 4},
        {xstring("q11_option5"), 5},
    };
}


// ============================================================================
// Signal handlers
// ============================================================================

void GmcPq::updateMandatory()
{
    // This could be more efficient with lots of signal handlers, but...
    const bool need_q2f_details = valueBool(Q2F);
    const bool need_ethnicity_other = ethnicityOther(valueInt(Q12));

    fieldRef(Q2F_DETAILS)->setMandatory(need_q2f_details);
    fieldRef(Q12_DETAILS)->setMandatory(need_ethnicity_other);
}