15.1.413. tablet_qt/questionnairelib/dynamicquestionnaire.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 "dynamicquestionnaire.h"
#include <QDebug>
#include "lib/uifunc.h"


DynamicQuestionnaire::DynamicQuestionnaire(
        CamcopsApp& app,
        const MakePageFn& make_page_fn,
        const MorePagesToGoFn& more_pages_to_go_fn) :
    Questionnaire(app),
    m_make_page_fn(make_page_fn),
    m_more_pages_to_go_fn(more_pages_to_go_fn)
{
    Q_ASSERT(m_current_page_index == 0);
    // Rude to call back into our owner during construction.
    // See addFirstDynamicPage() instead.
}


bool DynamicQuestionnaire::isDynamic() const
{
    return true;
}


void DynamicQuestionnaire::addFirstDynamicPage()
{
    QuPagePtr first_page = m_make_page_fn(m_current_page_index);
    if (!first_page) {
        uifunc::stopApp("Dynamic questionnaire created but caller refuses to "
                        "supply first page");
    }
    m_pages.append(first_page);
}


void DynamicQuestionnaire::addAllAccessibleDynamicPages()
{
    // Now, it may be that there are more to come. For example, if we're
    // editing a task that has previously been completed, there may be lots
    // of pages we can traverse to. Unless we collect them now, we won't be
    // able to jump past pages (we'd just be permitted to click "Next" a lot).
    // Since what we can access depends on the read-only status, we call this
    // AFTER the client has had a chance to set the read-only status.

    trimFromCurrentPositionOnwards();
    // ... or potential for inconsistency, e.g. if we're jumping, and we've
    // made a different decision on this page.

    int page_index = nPages() - 1;  // current last page
    QuPagePtr page;
    do {
        page = m_pages[page_index];
        // Not quite the same as a standard questionnaire (see processNextClicked).
        // We don't allow progress for blocked/missing-input pages in the
        // read-only situation (or, for example, you get a ridiculous list of
        // inaccessible pages; try the CIS-R).
        bool may_progress = page && page->mayProgressIgnoringValidators();
        may_progress = may_progress && m_more_pages_to_go_fn(page_index);
        if (may_progress) {
            ++page_index;
            page = m_make_page_fn(page_index);
            if (page) {
                m_pages.append(page);
            }
        } else {
            page = nullptr;
        }
    } while (page);
}


bool DynamicQuestionnaire::morePagesToGo() const
{
    const int current_qnum = currentPageIndex();
    return m_more_pages_to_go_fn(current_qnum);
}


void DynamicQuestionnaire::addPage(const QuPagePtr& page)
{
    Q_UNUSED(page)
    uifunc::stopApp("Don't call addPage() on a DynamicQuestionnaire!");
}


void DynamicQuestionnaire::deletePage(const int index)
{
    Q_UNUSED(index)
    uifunc::stopApp("Don't call deletePage() on a DynamicQuestionnaire!");
}


void DynamicQuestionnaire::goToPage(const int index, const bool allow_refresh)
{
    if (index < 0 || index >= nPages()) {
        qWarning() << Q_FUNC_INFO << "Invalid index:" << index;
        return;
    }
    if (index == m_current_page_index && !allow_refresh) {
        qDebug() << "Page" << index <<
                    "(zero-based index) already selected";
        return;
    }
    pageClosing();
    m_current_page_index = index;

    // Now the bit that's different for DynamicQuestionnaire:
    trimFromCurrentPositionOnwards();

    // Back to Questionnaire behaviour:
    build();
}


void DynamicQuestionnaire::trimFromCurrentPositionOnwards()
{
    // Chop off all pages beyond the current one
    while (m_pages.length() > m_current_page_index + 1) {
        m_pages.removeLast();
    }
}


void DynamicQuestionnaire::processNextClicked()
{
    // As per Questionnaire:
    QuPage* page = currentPagePtr();
    // Not quite the same as a standard questionnaire (see processNextClicked).
    // We don't allow progress for blocked/missing-input pages in the
    // read-only situation (or, for example, you get a ridiculous list of
    // inaccessible pages; try the CIS-R).
    if (!page || !page->mayProgressIgnoringValidators() || !page->validate()) {
        return;
    }

    // Different:
    // not now: allowing jump-ahead // Q_ASSERT(m_current_page_index == m_pages.length() - 1);
    trimFromCurrentPositionOnwards();
    const int next_qnum = m_current_page_index + 1;
    QuPagePtr new_dynamic_page = m_make_page_fn(next_qnum);
    if (!new_dynamic_page) {
        qWarning()
                << Q_FUNC_INFO
                << "Miscalculation: we have offered a Next button but the "
                   "task wants to finish, so we should have offered a Finish "
                   "button; this implies the task has got its "
                   "'more_pages_to_go_fn' function wrong";
        doFinish();
        return;
    }
    m_pages.append(new_dynamic_page);
    goToPage(next_qnum);
}