15.1.784. tablet_qt/tasks/miniace.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/>.
*/


/*

A note on task inheritance:

- MiniAce isn't a proper subclass of Ace3, because it has fewer fields.
- A common non-creatable parent is perfectly proper, though. That would likely
  be most sensible.

*/

#define NOSCROLL_IMAGE_PAGES  // Should be defined. Better UI with it.

#include "miniace.h"

#include <QDebug>

#include "common/textconst.h"
#include "common/uiconst.h"
#include "lib/datetime.h"
#include "lib/stringfunc.h"
#include "lib/uifunc.h"
#include "maths/mathfunc.h"
#include "questionnairelib/namevalueoptions.h"
#include "questionnairelib/quboolean.h"
#include "questionnairelib/qucountdown.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/questionnairefunc.h"
#include "questionnairelib/quflowcontainer.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qulineeditinteger.h"
#include "questionnairelib/qumcq.h"
#include "questionnairelib/quphoto.h"
#include "questionnairelib/qutext.h"
#include "questionnairelib/quverticalcontainer.h"
#include "tasklib/taskfactory.h"
#include "tasklib/taskregistrar.h"
using mathfunc::allNull;
using mathfunc::eq;
using mathfunc::noneNull;
using mathfunc::sumInt;
using mathfunc::totalScorePhrase;
using stringfunc::strnum;
using stringfunc::strseq;

const QString MiniAce::MINIACE_TABLENAME(QStringLiteral("miniace"));

// Field names, field prefixes, and field counts
const int N_ATTN_TIME_MINIACE = 4;

// Subtotals. No magic numbers...
const int TOTAL_ATTN_MINIACE = 4;
const int TOTAL_MEM_MINIACE = 14;
const int TOTAL_FLUENCY_MINIACE = 7;
const int TOTAL_VSP_MINIACE = 5;

// xstrings
const QString X_EDITION_MINIACE(QStringLiteral("edition_miniace"));

void initializeMiniAce(TaskFactory& factory)
{
    static TaskRegistrar<MiniAce> registered(factory);
}

MiniAce::MiniAce(
    CamcopsApp& app, DatabaseManager& db, const int load_pk, QObject* parent
) :
    AceFamily(app, db, MINIACE_TABLENAME, parent)
{
    addField(
        FN_TASK_EDITION,
        QMetaType::fromType<QString>(),
        false,
        false,
        false,
        xstring(X_EDITION_MINIACE)
    );
    addField(
        FN_TASK_ADDRESS_VERSION,
        QMetaType::fromType<QString>(),
        false,
        false,
        false,
        TASK_DEFAULT_VERSION
    );
    addField(
        FN_REMOTE_ADMINISTRATION,
        QMetaType::fromType<bool>(),
        false,
        false,
        false,
        false
    );

    addField(FN_AGE_FT_EDUCATION, QMetaType::fromType<int>());
    addField(FN_OCCUPATION, QMetaType::fromType<QString>());
    addField(FN_HANDEDNESS, QMetaType::fromType<QString>());

    addFields(
        strseq(FP_ATTN_TIME, 1, N_ATTN_TIME_MINIACE),
        QMetaType::fromType<int>()
    );

    addFields(
        strseq(FP_MEM_REPEAT_ADDR_TRIAL1, 1, N_MEM_REPEAT_RECALL_ADDR),
        QMetaType::fromType<int>()
    );
    addFields(
        strseq(FP_MEM_REPEAT_ADDR_TRIAL2, 1, N_MEM_REPEAT_RECALL_ADDR),
        QMetaType::fromType<int>()
    );
    addFields(
        strseq(FP_MEM_REPEAT_ADDR_TRIAL3, 1, N_MEM_REPEAT_RECALL_ADDR),
        QMetaType::fromType<int>()
    );

    addField(FN_FLUENCY_ANIMALS_SCORE, QMetaType::fromType<int>());

    addField(FN_VSP_DRAW_CLOCK, QMetaType::fromType<int>());

    addFields(
        strseq(FP_MEM_RECALL_ADDRESS, 1, N_MEM_REPEAT_RECALL_ADDR),
        QMetaType::fromType<int>()
    );

    addField(FN_PICTURE1_BLOBID, QMetaType::fromType<int>());
    // ... FK to BLOB table
    addField(FN_PICTURE2_BLOBID, QMetaType::fromType<int>());
    // ... FK to BLOB table
    addField(FN_COMMENTS, QMetaType::fromType<QString>());

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

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

QString MiniAce::shortname() const
{
    return QStringLiteral("Mini-ACE");
}

QString MiniAce::longname() const
{
    return tr("Mini-Addenbrooke’s Cognitive Examination");
}

QString MiniAce::description() const
{
    return tr(
        "30-point clinician-administered assessment of attention/"
        "orientation, memory, fluency, and visuospatial domains."
    );
}

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

bool MiniAce::isComplete() const
{
    return noneNull(values(strseq(FP_ATTN_TIME, 1, N_ATTN_TIME_MINIACE)))
        && noneNull(values(
            strseq(FP_MEM_REPEAT_ADDR_TRIAL3, 1, N_MEM_REPEAT_RECALL_ADDR)
        ))
        && !valueIsNull(FN_FLUENCY_ANIMALS_SCORE)
        && !valueIsNull(FN_VSP_DRAW_CLOCK)
        && noneNull(values(
            strseq(FP_MEM_RECALL_ADDRESS, 1, N_MEM_REPEAT_RECALL_ADDR)
        ));
}

QStringList MiniAce::summary() const
{
    QStringList lines;
    lines.append(
        xstring(X_MINI_ACE_SCORE)
        + scorePercent(miniAceScore(), TOTAL_MINI_ACE)
    );
    lines.append(
        xstring(QStringLiteral("cat_attn"))
        + scorePercent(getAttnScore(), TOTAL_ATTN_MINIACE)
    );
    lines.append(
        xstring(QStringLiteral("cat_mem"))
        + scorePercent(getMemScore(), TOTAL_MEM_MINIACE)
    );
    lines.append(
        xstring(QStringLiteral("cat_fluency"))
        + scorePercent(getFluencyScore(), TOTAL_FLUENCY_MINIACE)
    );
    lines.append(
        xstring(QStringLiteral("cat_vsp"))
        + scorePercent(getVisuospatialScore(), TOTAL_VSP_MINIACE)
    );
    return lines;
}

OpenableWidget* MiniAce::editor(const bool read_only)
{
    int pagenum = 1;
    auto makeTitle = [this, &pagenum](const QString& title) -> QString {
        return xstring(QStringLiteral("title_prefix_miniace"))
            + QString(QStringLiteral(" %1")).arg(pagenum++) + ": " + title;
    };

    // ------------------------------------------------------------------------
    // Preamble; age-leaving-full-time-education; handedness
    // ------------------------------------------------------------------------

    NameValueOptions options_task_version;
    const QStringList versions = addressVersionsAvailable();
    for (const auto& v : versions) {
        options_task_version.append(NameValuePair(v, v));
    }
    const NameValueOptions options_handedness{
        {xstring(QStringLiteral("left_handed")), "L"},
        {xstring(QStringLiteral("right_handed")), "R"},
    };
    FieldRefPtr fr_task_addr_version = fieldRef(FN_TASK_ADDRESS_VERSION);
    QuPagePtr page_preamble(
        (new QuPage{
             heading(X_EDITION_MINIACE),
             getClinicianQuestionnaireBlockRawPointer(),
             instruction(QStringLiteral("choose_task_version")),
             questionnairefunc::defaultGridRawPointer(
                 {
                     {"",
                      (new QuMcq(fr_task_addr_version, options_task_version))
                          ->setHorizontal(true)
                          ->addTag(TAG_EL_CHOOSE_TASK_VERSION)},
                     {"",
                      (new QuText(fr_task_addr_version))
                          ->addTag(TAG_EL_SHOW_TASK_VERSION)
                          ->setVisible(false)},
                     {"",
                      boolean(
                          QStringLiteral("q_remote"), FN_REMOTE_ADMINISTRATION
                      )},
                 },
                 uiconst::DEFAULT_COLSPAN_Q,
                 uiconst::DEFAULT_COLSPAN_A
             ),
             // remInstruct(QStringLiteral("instruction_remote_read_first")),
             // Mini-ACE doesn't have an official remote version and therefore
             // remote instructions. But it is very simple.
             stdInstruct(QStringLiteral("instruction_need_paper_miniace")),
             remInstruct(QStringLiteral("instruction_need_paper_remote_miniace"
             )),
             remInstruct(
                 QStringLiteral("instruction_remote_camera_to_participant")
             ),
             instruction(QStringLiteral("preamble_instruction")),
             questionnairefunc::defaultGridRawPointer(
                 {
                     {xstring(QStringLiteral("q_age_leaving_fte")),
                      new QuLineEditInteger(
                          fieldRef(FN_AGE_FT_EDUCATION), MIN_AGE, MAX_AGE_Y
                      )},
                     {xstring(QStringLiteral("q_occupation")),
                      new QuLineEdit(fieldRef(FN_OCCUPATION))},
                     {xstring(QStringLiteral("q_handedness")),
                      (new QuMcq(fieldRef(FN_HANDEDNESS), options_handedness))
                          ->setHorizontal(true)},
                 },
                 uiconst::DEFAULT_COLSPAN_Q,
                 uiconst::DEFAULT_COLSPAN_A
             ),
         })
            ->setTitle(makeTitle(tr("Preamble")))
            ->setType(QuPage::PageType::Clinician)
            ->addTag(TAG_PG_PREAMBLE)
    );

    // ------------------------------------------------------------------------
    // Attention/orientation
    // ------------------------------------------------------------------------

    const QDateTime now = datetime::now();
    const QString correct_date
        = "     " + now.toString(QStringLiteral("dddd d MMMM yyyy"));
    // ... e.g. "Monday 2 January 2016";

    QuPagePtr page_attn(
        (new QuPage{
             heading(QStringLiteral("cat_attn")),

             // Orientation
             instruction(QStringLiteral("attn_q_time")),
             new QuFlowContainer{
                 boolean(
                     QStringLiteral("attn_time1"), strnum(FP_ATTN_TIME, 1)
                 ),
                 boolean(
                     QStringLiteral("attn_time2"), strnum(FP_ATTN_TIME, 2)
                 ),
                 boolean(
                     QStringLiteral("attn_time3"), strnum(FP_ATTN_TIME, 3)
                 ),
                 boolean(
                     QStringLiteral("attn_time4"), strnum(FP_ATTN_TIME, 4)
                 ),
             },
             explanation(QStringLiteral("instruction_time_miniace")),
             (new QuText(correct_date))->setItalic(),

         })
            ->setTitle(makeTitle(tr("Attention")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Learning the address (comes before fluency in the mini-ACE)
    // ------------------------------------------------------------------------

    // Inelegance acknowledged! Address layouts are cosmetic.
    auto addrReg = [this](
                       int trial, int component, bool mandatory = false
                   ) -> QuElement* {
        return (new QuBoolean(
                    targetAddressComponent(component),
                    fieldRef(
                        FP_MEM_REPEAT_ADDR_GENERIC.arg(trial).arg(component),
                        mandatory
                    )
                ))
            ->addTag(tagAddressRegistration(trial, component));
    };
    QuPagePtr page_repeat_addr_famous(
        (new QuPage{
             heading(QStringLiteral("cat_mem")),
             instruction(QStringLiteral("memory_q_address")),
             explanation(QStringLiteral("memory_instruction_address_1")),
             explanation(QStringLiteral("memory_instruction_address_2")),

             // Address 1
             new QuVerticalContainer{
                 instructionRaw(xstring(QStringLiteral("trial")) + " 1"),
                 new QuFlowContainer{addrReg(1, 1), addrReg(1, 2)},
                 new QuFlowContainer{
                     addrReg(1, 3), addrReg(1, 4), addrReg(1, 5)},
                 addrReg(1, 6),
                 addrReg(1, 7),
             },

             // Address 2
             new QuVerticalContainer{
                 instructionRaw(xstring(QStringLiteral("trial")) + " 2"),
                 new QuFlowContainer{addrReg(2, 1), addrReg(2, 2)},
                 new QuFlowContainer{
                     addrReg(2, 3), addrReg(2, 4), addrReg(2, 5)},
                 addrReg(2, 6),
                 addrReg(2, 7),
             },

             // Address 3
             new QuVerticalContainer{
                 instructionRaw(xstring(QStringLiteral("trial")) + " 3"),
                 new QuFlowContainer{
                     addrReg(3, 1, true),
                     addrReg(3, 2, true),
                 },
                 new QuFlowContainer{
                     addrReg(3, 3, true),
                     addrReg(3, 4, true),
                     addrReg(3, 5, true),
                 },
                 addrReg(3, 6, true),
                 addrReg(3, 7, true),
             },
         })
            ->setTitle(makeTitle(tr("Memory")))
            ->addTag(TAG_PG_ADDRESS_LEARNING_FAMOUS)
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Fluency
    // ------------------------------------------------------------------------

    const NameValueOptions options_fluency_animals{
        {"0–4", 0},
        {"5–6", 1},
        {"7–8", 2},
        {"9–10", 3},
        {"11–13", 4},
        {"14–16", 5},
        {"17–21", 6},
        {"≥22", 7}};
    QuPagePtr page_fluency(
        (new QuPage{
             heading(QStringLiteral("cat_fluency")),

             // Animals
             subheading(QStringLiteral("fluency_subheading_animals")),
             instruction(QStringLiteral("fluency_q_animals")),
             new QuCountdown(FLUENCY_TIME_SEC),
             explanation(QStringLiteral("fluency_instruction_animals")),
             text(QStringLiteral("fluency_prompt_animals_cor")),
             (new QuMcq(
                  fieldRef(FN_FLUENCY_ANIMALS_SCORE), options_fluency_animals
              ))
                 ->setHorizontal(true),
         })
            ->setTitle(makeTitle(tr("Fluency")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Clock
    // ------------------------------------------------------------------------

    const NameValueOptions options_clock = NameValueOptions::makeNumbers(0, 5);
    QuPagePtr page_clock(
        (new QuPage{
             stdInstruct(QStringLiteral("vsp_q_clock")),
             remInstruct(QStringLiteral("vsp_q_clock_remote")),
             explanation(QStringLiteral("vsp_instruction_clock")),
             text(QStringLiteral("vsp_score_clock")),
             (new QuMcq(fieldRef(FN_VSP_DRAW_CLOCK), options_clock))
                 ->setHorizontal(true),
         })
            ->setTitle(makeTitle(tr("Clock")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Back to clinician
    // ------------------------------------------------------------------------

    QuPagePtr page_back_to_clinician(
        (new QuPage{
             textRaw(tr("Please make sure the subject can’t see the screen "
                        "before you proceed. (Memory prompts coming up.)")),
         })
            ->setTitle(makeTitle(tr("[reminder to clinician]")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Address recall: free
    // ------------------------------------------------------------------------

    auto addrFree = [this](int component) -> QuElement* {
        return (new QuBoolean(
                    targetAddressComponent(component),
                    fieldRef(strnum(FP_MEM_RECALL_ADDRESS, component), true)
                ))
            ->addTag(tagAddressFreeRecall(component));
    };
    QuPagePtr page_recall_address_free(
        (new QuPage{
             heading(QStringLiteral("cat_mem")),
             instruction(QStringLiteral("mem_q_recall_address")),
             new QuVerticalContainer{
                 new QuFlowContainer{addrFree(1), addrFree(2)},
                 new QuFlowContainer{addrFree(3), addrFree(4), addrFree(5)},
                 addrFree(6),
                 addrFree(7),
             },
         })
            ->setTitle(makeTitle(tr("Free recall")))
            ->addTag(TAG_PG_MEM_FREE_RECALL)
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Comments
    // ------------------------------------------------------------------------

    QuPagePtr page_comments(
        (new QuPage{
             instructionRaw(TextConst::examinerCommentsPrompt()),
             (new QuLineEdit(fieldRef(FN_COMMENTS, false)))
                 ->setHint(TextConst::examinerComments()),
         })
            ->setTitle(makeTitle(tr("Comments")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Photo 1
    // ------------------------------------------------------------------------

    QuPagePtr page_photo_1(
        (new QuPage{
             instruction(QStringLiteral("picture1_q")),
             explanation(QStringLiteral("picture_instruction1")),
             explanation(QStringLiteral("picture_instruction2_miniace")),
             new QuPhoto(blobFieldRef(FN_PICTURE1_BLOBID, false)),
         })
            ->setTitle(makeTitle(tr("Photo 1")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Photo 2
    // ------------------------------------------------------------------------

    QuPagePtr page_photo_2(
        (new QuPage{
             instruction(QStringLiteral("picture2_q")),
             explanation(QStringLiteral("picture_instruction1")),
             explanation(QStringLiteral("picture_instruction2_miniace")),
             new QuPhoto(blobFieldRef(FN_PICTURE2_BLOBID, false)),
         })
            ->setTitle(makeTitle(tr("Photo 2")))
            ->setType(QuPage::PageType::Clinician)
    );

    // ------------------------------------------------------------------------
    // Questionnaire
    // ------------------------------------------------------------------------

    m_questionnaire = new Questionnaire(
        m_app,
        {
            page_preamble,
            page_attn,
            page_repeat_addr_famous,
            page_fluency,
            page_clock,
            page_back_to_clinician,
            page_recall_address_free,
            page_comments,
            page_photo_1,
            page_photo_2,
        }
    );
    m_questionnaire->setReadOnly(read_only);

    // ------------------------------------------------------------------------
    // Signals and initial dynamic state
    // ------------------------------------------------------------------------

    // When the user changes the task address version (e.g. A/B/C).
    FieldRefPtr fr_task_version = fieldRef(FN_TASK_ADDRESS_VERSION);
    connect(
        fr_task_version.data(),
        &FieldRef::valueChanged,
        this,
        &MiniAce::updateTaskVersionAddresses
    );
    updateTaskVersionAddresses();

    // When the user changes the remote administration status.
    FieldRefPtr fr_remote = fieldRef(FN_REMOTE_ADMINISTRATION);
    connect(
        fr_remote.data(),
        &FieldRef::valueChanged,
        this,
        &MiniAce::showStandardOrRemoteInstructions
    );
    showStandardOrRemoteInstructions();

    // When the user writes data relating to a specific address, locking in
    // the address version selection. See isChangingAddressVersionOk().
    for (int i = 1; i <= N_MEM_REPEAT_RECALL_ADDR; ++i) {
        connect(
            fieldRef(strnum(FP_MEM_REPEAT_ADDR_TRIAL1, i)).data(),
            &FieldRef::valueChanged,
            this,
            &MiniAce::updateTaskVersionEditability
        );
        connect(
            fieldRef(strnum(FP_MEM_REPEAT_ADDR_TRIAL2, i)).data(),
            &FieldRef::valueChanged,
            this,
            &MiniAce::updateTaskVersionEditability
        );
        connect(
            fieldRef(strnum(FP_MEM_REPEAT_ADDR_TRIAL3, i)).data(),
            &FieldRef::valueChanged,
            this,
            &MiniAce::updateTaskVersionEditability
        );
    }
    for (int i = 1; i <= N_MEM_REPEAT_RECALL_ADDR; ++i) {
        connect(
            fieldRef(strnum(FP_MEM_RECALL_ADDRESS, i)).data(),
            &FieldRef::valueChanged,
            this,
            &MiniAce::updateTaskVersionEditability
        );
    }
    updateTaskVersionEditability();

    // ------------------------------------------------------------------------
    // Done
    // ------------------------------------------------------------------------

    return m_questionnaire;
}

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

int MiniAce::getAttnScore() const
{
    return sumInt(values(strseq(FP_ATTN_TIME, 1, N_ATTN_TIME_MINIACE)));
    // 4 points
}

int MiniAce::getFluencyScore() const
{
    return valueInt(FN_FLUENCY_ANIMALS_SCORE);
    // 7 points
}

int MiniAce::getMemScore() const
{
    return sumInt(values(
               strseq(FP_MEM_REPEAT_ADDR_TRIAL3, 1, N_MEM_REPEAT_RECALL_ADDR)
           ))
        + sumInt(values(
            strseq(FP_MEM_RECALL_ADDRESS, 1, N_MEM_REPEAT_RECALL_ADDR)
        ));
    // 14 points
}

int MiniAce::getVisuospatialScore() const
{
    return valueInt(FN_VSP_DRAW_CLOCK);
    // 5 points
}

int MiniAce::miniAceScore() const
{
    return getAttnScore() + getFluencyScore() + getMemScore()
        + getVisuospatialScore();
    // 30 points
}

// ============================================================================
// Task address version support functions
// ============================================================================

QString MiniAce::taskAddressVersion() const
{
    // Could be consolidated into AceFamily, but we follow the rule that access
    // to class-specific data is not put into the parent.
    const QString selected = valueString(FN_TASK_ADDRESS_VERSION);
    if (addressVersionsAvailable().contains(selected)) {
        return selected;
    }
    return TASK_DEFAULT_VERSION;
}

bool MiniAce::isChangingAddressVersionOk() const
{
    return allNull(values(
               strseq(FP_MEM_REPEAT_ADDR_TRIAL1, 1, N_MEM_REPEAT_RECALL_ADDR)
           ))
        && allNull(values(
            strseq(FP_MEM_REPEAT_ADDR_TRIAL2, 1, N_MEM_REPEAT_RECALL_ADDR)
        ))
        && allNull(values(
            strseq(FP_MEM_REPEAT_ADDR_TRIAL3, 1, N_MEM_REPEAT_RECALL_ADDR)
        ))
        && allNull(values(
            strseq(FP_MEM_RECALL_ADDRESS, 1, N_MEM_REPEAT_RECALL_ADDR)
        ));
}

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

void MiniAce::updateTaskVersionAddresses()
{
    // Set address components.
    for (int component = 1; component <= N_MEM_REPEAT_RECALL_ADDR;
         ++component) {

        // 1. Repetition.
        const QString target_text = targetAddressComponent(component);
        for (int trial = 1; trial <= ADDR_LEARN_N_TRIALS; ++trial) {
            auto repet = qobject_cast<QuBoolean*>(
                m_questionnaire->getFirstElementByTag(
                    tagAddressRegistration(trial, component),
                    false,
                    TAG_PG_ADDRESS_LEARNING_FAMOUS
                )
            );
            if (!repet) {
                continue;
            }
            repet->setText(target_text);
        }

        // 2. Free recall.
        auto free_recall
            = qobject_cast<QuBoolean*>(m_questionnaire->getFirstElementByTag(
                tagAddressFreeRecall(component), false, TAG_PG_MEM_FREE_RECALL
            ));
        if (!free_recall) {
            continue;
        }
        free_recall->setText(target_text);
    }
}

void MiniAce::showStandardOrRemoteInstructions()
{
    const bool remote = valueBool(FN_REMOTE_ADMINISTRATION);
    const bool standard = !remote;
    const QVector<QuElement*> standard_elements
        = m_questionnaire->getElementsByTag(TAG_STANDARD, false);
    for (auto e : standard_elements) {
        e->setVisible(standard);
    }
    const QVector<QuElement*> remote_elements
        = m_questionnaire->getElementsByTag(TAG_REMOTE, false);
    for (auto e : remote_elements) {
        e->setVisible(remote);
    }
}

void MiniAce::updateTaskVersionEditability()
{
    const bool editable = isChangingAddressVersionOk();
    m_questionnaire->setVisibleByTag(
        TAG_EL_CHOOSE_TASK_VERSION, editable, false, TAG_PG_PREAMBLE
    );
    m_questionnaire->setVisibleByTag(
        TAG_EL_SHOW_TASK_VERSION, !editable, false, TAG_PG_PREAMBLE
    );
}