15.1.693. tablet_qt/tasks/empsa.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 "empsa.h"

#include "common/uiconst.h"
#include "lib/convert.h"
#include "lib/stringfunc.h"
#include "maths/mathfunc.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/qugridcontainer.h"
#include "questionnairelib/quheading.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qulineeditinteger.h"
#include "questionnairelib/qupage.h"
#include "questionnairelib/quspacer.h"
#include "questionnairelib/qutext.h"
#include "tasklib/taskfactory.h"
#include "tasklib/taskregistrar.h"
using mathfunc::anyNull;
using mathfunc::meanOrNull;
using stringfunc::strseq;

const int FIRST_Q = 1;
const int LAST_Q = 12;
const int MIN_SCORE = 0;  // see empsa.rst; confirmed with creator
const int MAX_SCORE = 10;
const int DP = 2;

const QString Q_PREFIX("q");
const QString ABILITY_SUFFIX("_ability");
const QString MOTIVATION_SUFFIX("_motivation");
const QString COMMENTS_SUFFIX("_comments");
const QString NOTE_SUFFIX("_note");

const QString Empsa::EMPSA_TABLENAME("empsa");

void initializeEmpsa(TaskFactory& factory)
{
    static TaskRegistrar<Empsa> registered(factory);
}

QStringList Empsa::abilityFieldNames() const
{
    return strseq(Q_PREFIX, FIRST_Q, LAST_Q, ABILITY_SUFFIX);
}

QStringList Empsa::motivationFieldNames() const
{
    return strseq(Q_PREFIX, FIRST_Q, LAST_Q, MOTIVATION_SUFFIX);
}

QStringList Empsa::commentsFieldNames() const
{
    return strseq(Q_PREFIX, FIRST_Q, LAST_Q, COMMENTS_SUFFIX);
}


Empsa::Empsa(CamcopsApp& app, DatabaseManager& db, const int load_pk) :
    Task(app, db, EMPSA_TABLENAME, false, false, false)
// ... anon, clin, resp
{
    addFields(abilityFieldNames(), QMetaType::fromType<int>());
    addFields(motivationFieldNames(), QMetaType::fromType<int>());
    addFields(commentsFieldNames(), QMetaType::fromType<QString>());

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

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

QString Empsa::shortname() const
{
    return "EMPSA";
}

QString Empsa::longname() const
{
    return tr("Eating & Meal Preparation Skills Assessment");
}

QString Empsa::description() const
{
    return tr(
        "A 12-item self-report assessment tool designed to measure eating "
        "disordered clients' perceived ability and motivation to perform 12 "
        "eating and meal preparation tasks."
    );
}

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


bool Empsa::isComplete() const
{
    if (anyNull(values(abilityFieldNames()))) {
        return false;
    }

    if (anyNull(values(motivationFieldNames()))) {
        return false;
    }

    return true;
}

QStringList Empsa::detail() const
{
    QStringList lines = completenessInfo();

    auto html = QString("<table>");
    html.append("<tr>");
    html.append("<th></th>");
    html.append("<th></th>");
    html.append(QString("<th>%1</th>").arg(xstring("ability")));
    html.append(QString("<th>%1</th>").arg(xstring("motivation")));
    html.append("</tr>");

    for (int q = FIRST_Q; q <= LAST_Q; ++q) {
        const QString q_str = QString("%1%2").arg(Q_PREFIX).arg(q);
        const QString ability_field_name = q_str + ABILITY_SUFFIX;
        const QString motivation_field_name = q_str + MOTIVATION_SUFFIX;

        html.append("<tr>");
        html.append(QString("<td>%1</td>").arg(q));
        html.append(QString("<td>%1</td>").arg(xstring(q_str)));
        html.append(QString("<td>%1</td>").arg(valueInt(ability_field_name)));
        html.append(QString("<td>%1</td>").arg(valueInt(motivation_field_name))
        );
        html.append("</tr>");
    }
    html.append("</table>");

    lines.append(html);
    lines.append("");
    lines += summary();

    return lines;
}

QStringList Empsa::summary() const
{
    auto rangeScore = [](const QString& description,
                         const QVariant score,
                         const int min,
                         const int max) {
        return QString("%1: <b>%2</b> [%3–%4].")
            .arg(
                description,
                convert::prettyValue(score, DP),
                QString::number(min),
                QString::number(max)
            );
    };

    return QStringList{
        rangeScore(
            xstring("ability"), abilitySubscale(), MIN_SCORE, MAX_SCORE
        ),
        rangeScore(
            xstring("motivation"), motivationSubscale(), MIN_SCORE, MAX_SCORE
        ),

    };
}

QVariant Empsa::abilitySubscale() const
{
    return meanOrNull(values(abilityFieldNames()));
}

QVariant Empsa::motivationSubscale() const
{
    return meanOrNull(values(motivationFieldNames()));
}

OpenableWidget* Empsa::editor(const bool read_only)
{
    auto subtitle = (new QuText(xstring("subtitle")))->setItalic();
    auto instructions_1 = new QuText(xstring("instructions_1"));
    auto instructions_2 = new QuText(xstring("instructions_2"));

    auto instructions_grid = new QuGridContainer();
    instructions_grid->setStyleSheet(
        "background-color: #fefec2; padding: 10px;"
    );

    int row = 0;
    instructions_grid->addCell(QuGridCell(
        (new QuText(xstring("instructions_3")))
            ->setBold()
            ->setTextAndWidgetAlignment(Qt::AlignHCenter),
        row,
        0,
        1,
        2,
        Qt::AlignHCenter
    ));

    row++;

    instructions_grid->addCell(QuGridCell(
        (new QuText(xstring("zero")))
            ->setBold()
            ->setTextAndWidgetAlignment(Qt::AlignLeft),
        row,
        0,
        1,
        1,
        Qt::AlignLeft
    ));
    instructions_grid->addCell(QuGridCell(
        (new QuText(xstring("ten")))
            ->setBold()
            ->setTextAndWidgetAlignment(Qt::AlignRight),
        row,
        1,
        1,
        1,
        Qt::AlignRight
    ));

    auto grid = new QuGridContainer();
    grid->setColumnStretch(0, 1);
    grid->setColumnStretch(1, 9);
    grid->setColumnStretch(2, 2);
    grid->setColumnStretch(3, 2);
    grid->setColumnStretch(4, 9);

    row = 0;

    grid->addCell(QuGridCell(new QuText(""), row, 0));
    grid->addCell(QuGridCell((new QuText(xstring("task")))->setBold(), row, 1)
    );
    grid->addCell(
        QuGridCell((new QuText(xstring("ability")))->setBold(), row, 2)
    );
    grid->addCell(
        QuGridCell((new QuText(xstring("motivation")))->setBold(), row, 3)
    );
    grid->addCell(
        QuGridCell((new QuText(xstring("comments")))->setBold(), row, 4)
    );

    row++;

    for (int q = FIRST_Q; q <= LAST_Q; ++q) {
        const QString q_str = QString("%1%2").arg(Q_PREFIX).arg(q);
        const QString ability_field_name = q_str + ABILITY_SUFFIX;
        const QString motivation_field_name = q_str + MOTIVATION_SUFFIX;
        const QString comments_field_name = q_str + COMMENTS_SUFFIX;
        const QString note_name = q_str + NOTE_SUFFIX;
        const QString label
            = QString("%1 %2").arg(xstring(q_str)).arg(xstring(note_name));

        grid->addCell(QuGridCell(new QuText(QString::number(q)), row, 0));
        grid->addCell(QuGridCell(new QuText(label), row, 1));

        auto ability_edit = new QuLineEditInteger(
            fieldRef(ability_field_name), MIN_SCORE, MAX_SCORE
        );
        //: Range for integer input
        ability_edit->setHint(
            QString(tr("%1 to %2")).arg(MIN_SCORE).arg(MAX_SCORE)
        );
        grid->addCell(QuGridCell(ability_edit, row, 2));

        auto motivation_edit = new QuLineEditInteger(
            fieldRef(motivation_field_name), MIN_SCORE, MAX_SCORE
        );
        motivation_edit->setHint(
            QString(tr("%1 to %2")).arg(MIN_SCORE).arg(MAX_SCORE)
        );
        grid->addCell(QuGridCell(motivation_edit, row, 3));

        grid->addCell(QuGridCell(
            new QuLineEdit(fieldRef(comments_field_name, false)), row, 4
        ));

        row++;
    }


    QVector<QuElement*> elements{
        subtitle,
        new QuSpacer(QSize(uiconst::MEDIUMSPACE, uiconst::MEDIUMSPACE)),
        instructions_1,
        new QuSpacer(QSize(uiconst::MEDIUMSPACE, uiconst::MEDIUMSPACE)),
        instructions_2,
        new QuSpacer(QSize(uiconst::MEDIUMSPACE, uiconst::MEDIUMSPACE)),
        instructions_grid,
        new QuSpacer(QSize(uiconst::MEDIUMSPACE, uiconst::MEDIUMSPACE)),
        grid,
    };

    QuPagePtr page((new QuPage(elements))->setTitle(xstring("title")));

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

    return questionnaire;
}