15.1.565. tablet_qt/tasklib/task.h

/*
    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/>.
*/

#pragma once
#include <QCoreApplication>  // for Q_DECLARE_TR_FUNCTIONS
#include <QDateTime>
#include <QJsonObject>
#include <QString>
#include "common/aliases_camcops.h"
#include "db/databaseobject.h"

class CamcopsApp;
class OpenableWidget;
class QGraphicsScene;
class Version;

extern const QString PATIENT_FK_FIELDNAME;


class Task : public DatabaseObject
{
    Q_OBJECT
    friend class SingleTaskMenu;  // so it can call setupForEditingAndSave()
    friend class Patient;  // so it can call moveToPatient()
    friend class TaskChain;  // so it can call setupForEditingAndSave()
    friend class TaskScheduleItemEditor;  // so it can call setupForEditingAndSave()

public:
    enum class TaskImplementationType {
        Full,
        UpgradableSkeleton,
        Skeleton,
    };

public:
    // Constructor
    // Args:
    //      app: the CamCOPS app
    //      db: the database that will hold this task
    //      tablename: the base table name
    //      is_anonymous: is this is an anonymous task (with no patient)?
    //      has_clinician: add standard fields for a clinician?
    //      has_respondent: add standard fields for a respondent (e.g. carer)?
    Task(CamcopsApp& app,
         DatabaseManager& db,
         const QString& tablename,
         bool is_anonymous,
         bool has_clinician,
         bool has_respondent,
         QObject* parent = nullptr);

    // Destructor
    virtual ~Task() {}

    // ------------------------------------------------------------------------
    // General info
    // ------------------------------------------------------------------------
    // Things that should ideally be class methods but we'll do by instance:

    // tablename(): already implemented by DatabaseObject

    // Short name of the task (e.g. "PHQ-9")
    virtual QString shortname() const = 0;

    // Long name of the task (e.g. "Patient Health Questionnaire-9")
    virtual QString longname() const = 0;

    // How is the task implemented -- does it come with all its content, or
    // is it a bare-bones skeleton (for tasks whose content we can't
    // reproduce), or is it an upgradeable skeleton (depending on institutional
    // permissions)?
    virtual TaskImplementationType implementationType() const { return TaskImplementationType::Full; }

    QString implementationTypeDescription() const;

    // Suffix for menu title (e.g. symbols for restricted/defunct tasks).
    QString menuTitleSuffix() const;

    // Title to be used on the menu. By default this is of the format
    // "longname (shortname)".
    virtual QString menutitle() const;

    // Description to be used on the menu.
    virtual QString description() const = 0;

    // Suffix for menu subtitle.
    QString menuSubtitleSuffix() const;

    // Menu subtitle with any necessary information suffix.
    QString menusubtitle() const;

    // Filename stem (e.g. "phq9") that will be used to form a URL to the
    // online documentation for this task. By default, it's tablename().
    virtual QString infoFilenameStem() const;

    // Task name to use when looking up an xstring() for this task. By default,
    // it's tablename().
    virtual QString xstringTaskname() const;

    // Returns a title for an instance of this task. If the task is anonymous
    // or with_pid is false, the default implementation includes the shortname
    // and the task's creation date. If patient information is available and
    // with_pid is true, it also includes some brief patient details.
    virtual QString instanceTitle(bool with_pid = true) const;

    // Is the task anonymous (no patient)?
    virtual bool isAnonymous() const;

    // Does the task have a clinician?
    virtual bool hasClinician() const;

    // Does the task have a respondent (e.g. a carer answering on behalf of or
    // in relation to the patient)?
    virtual bool hasRespondent() const;

    // Does this task prohibit clinical use?
    virtual bool prohibitsClinical() const { return false; }

    // Does this task prohibit commerical use?
    virtual bool prohibitsCommercial() const { return false; }

    // Does this task prohibit research use?
    virtual bool prohibitsEducational() const { return false; }

    // Does this task prohibit research use?
    virtual bool prohibitsResearch() const { return false; }

    // If the task is an upgradable skeleton, and has not been upgraded, should
    // its use be prohibited (because the skeleton is so useless as to be
    // misleading/harmful)?
    virtual bool prohibitedIfSkeleton() const { return false; }

    // Is the task re-editable once it's been created?
    virtual bool isEditable() const { return true; }

    // Is the task less than fully functional, e.g.
    // - intrinsically a "skeleton" task at best;
    // - requiring strings that have not been downloaded (or are not available
    //   or are too old) from a CamCOPS server;
    // - or that the server is too old to accept the task?
    virtual bool isCrippled() const;

    // Is this an experimental task? (Affects labelling.)
    virtual bool isExperimental() const { return false; }

    // Is this a defunct task? (Affects labelling.)
    virtual bool isDefunct() const { return false; }

    // Are there any extra strings (xstrings) for the task, downloaded from the
    // server?
    virtual bool hasExtraStrings() const;

    // Is it permissible to create a new instance of the task? (If not, why
    // not?) Writes to failure_reason only on failure.
    virtual bool isTaskPermissible(QString& failure_reason) const;

    // What is the minimum CamCOPS server version that will accept this task?
    virtual Version minimumServerVersion() const;

    // Is this task uploadable? Reasons that it may not be include:
    // - the server doesn't have the task's table;
    // - the client says the server is too old (in general, or for this task);
    // - the server says the client is too old.
    // The user can override these, but gets a warning.
    // Writes to failure_reason only on failure.
    virtual bool isTaskUploadable(QString& failure_reason) const;

protected:
    // Is there some barrier to creating the task, not dealt with already by
    // isTaskUploadable()? Reasons may include:
    // - the server strings are too old.
    // The user can override these, but gets a warning.
    // Writes to failure_reason only on failure.
    virtual bool isTaskProperlyCreatable(QString& failure_reason) const;

    // Used internally by isTaskCreatable(): are the server's strings
    // sufficiently recent? Writes to failure_reason only on failure.
    bool isServerStringVersionEnough(const Version& minimum_server_version,
                                     QString& failure_reason) const;

    // ------------------------------------------------------------------------
    // Tables and other classmethods
    // ------------------------------------------------------------------------

public:
    // Return a list of names of ancillary tables used by this task. (For
    // example, the PhotoSequence task has an ancillary table to contain its
    // photos. One sequence, lots of photos.)
    virtual QStringList ancillaryTables() const { return QStringList(); }

    // Each ancillary table (if there are any) has a foreign key (FK) to the
    // base table. What's the FK column name?
    virtual QString ancillaryTableFKToTaskFieldname() const { return QString(); }

    // Return all tables used by this task (base + ancillary).
    QStringList allTables() const;

    // Make all tables (base table and any ancillary tables).
    virtual void makeTables();

    // Make all ancillary tables.
    virtual void makeAncillaryTables() {}

    // How many instances of this task type (optionally meeting a set of WHERE
    // criteria) exist in the database?
    int count(const WhereConditions& where = WhereConditions()) const;

    // How many instances of this task type exist in the database for the
    // specified patient (by the CamCOPS client's patient PK)?
    int countForPatient(int patient_id) const;

    // Perform any special steps required by this task as we upgrade the client
    // database.
    virtual void upgradeDatabase(const Version& old_version,
                                 const Version& new_version);

    // ------------------------------------------------------------------------
    // Database object functions
    // ------------------------------------------------------------------------

    // Load data from the database into the fields for this instance.
    // No need to override, but do need to CALL load() FROM CONSTRUCTOR:
    virtual bool load(int pk = dbconst::NONEXISTENT_PK);

    // Save data from this task instance to the database, if any data needs
    // saving.
    // - Performs some sanity checks, then calls DatabaseObject::save().
    virtual bool save();

    // ------------------------------------------------------------------------
    // Specific info
    // ------------------------------------------------------------------------

    // Is the task complete?
    virtual bool isComplete() const = 0;

    // Is the task complete? Cached version (automatically reloaded when task
    // data changes).
    bool isCompleteCached() const;

    // Returns summary information about the task. (Shown in the task menus
    // and in the summary view.)
    virtual QStringList summary() const;

    // Returns more detailed information about the task.
    virtual QStringList detail() const;

    // Returns an editor widget (e.g. a questionnaire or a graphics widget) for
    // editing this task (or viewing it, if read_only is true).
    virtual OpenableWidget* editor(bool read_only = false);

protected:
    void onDataChanged();

    // ------------------------------------------------------------------------
    // Assistance functions
    // ------------------------------------------------------------------------
public:
    // When was this task created?
    QDateTime whenCreated() const;

    // If the task is incomplete, returns string(s) to indicate this
    // (otherwise, returns an empty list).
    QStringList completenessInfo() const;

    // Returns an xstring for this task. This is a named string, downloaded for
    // this task from the server.
    QString xstring(const QString& stringname,
                    const QString& default_str = QString()) const;

    // Returns an appstring. This is a named string, downloaded from the server
    // for the CamCOPS client in general.
    QString appstring(const QString& stringname,
                      const QString& default_str = QString()) const;

    // Assistance function for summary() or detail().
    // - Returns a list of strings of the format
    //   "<name><spacer><b>value</b><suffix>" for specified fields.
    // - The field name (from which <value> is taken) ranges from
    //   <fieldprefix><first> to <fieldprefix><last>.
    // - The name ranges from <xstringprefix><first><xstringsuffix> to
    //   <xstringprefix><last><xstringsuffix>.
    QStringList fieldSummaries(
        const QString& xstringprefix,
        const QString& xstringsuffix,
        const QString& spacer,
        const QString& fieldprefix,
        int first,
        int last,
        const QString& suffix = QString()
    ) const;

    // As for fieldSummaries(), but the value is shown as "Yes"/"No", for
    // Boolean fields.
    QStringList fieldSummariesYesNo(
        const QString& xstringprefix,
        const QString& xstringsuffix,
        const QString& spacer,
        const QString& fieldprefix,
        int first,
        int last,
        const QString& suffix = QString()
    ) const;

    // Returns a string list of the clinician's details (specialty, name,
    // etc.).
    QStringList clinicianDetails(const QString& separator = QStringLiteral(": ")) const;

    // Returns a string list of the respondent's details (name, relationship).
    QStringList respondentDetails() const;

    // ------------------------------------------------------------------------
    // Editing
    // ------------------------------------------------------------------------

    // How long has the user spent editing this task?
    double editingTimeSeconds() const;

protected:

    // Set up all defaults (including setting the patient ID, for non-anonymous
    // tasks) and save to database. Use when you've created a task and want
    // to edit it.
    void setupForEditingAndSave(const int patient_id = dbconst::NONEXISTENT_PK);

    // Single user mode: apply any settings (down to task implementation)
    virtual void applySettings(const QJsonObject& settings) {Q_UNUSED(settings)}

    // Set the clinician fields to the app's default clinician information.
    // Called when the task is first created from the menus.
    // Only relevant for tasks with a clinician.
    void setDefaultClinicianVariablesAtFirstUse();

    // Override if you need to do additional configuration for a new task.
    // Called when the task is first created from the menus.
    virtual void setDefaultsAtFirstUse() {}

    // Helper function for graphical/animated tasks to create their editor.
    // Makes an OpenableWidget containing a ScreenLikeGraphicsView to display
    // the specified QGraphicsScene.
    // - background_colour: the background colour of the ScreenLikeGraphicsView
    // - fullscreen: open this window in fullscreen mode?
    // - esc_can_abort: passed to OpenableWidget::setEscapeKeyCanAbort().
    OpenableWidget* makeGraphicsWidget(
            QGraphicsScene* scene, const QColor& background_colour,
            bool fullscreen = true, bool esc_can_abort = true);

    // Helper function for graphical/animated tasks to create their editor.
    // Calls makeGraphicsWidget() [q.v.], then hooks the widget's abort signal
    // to Task::onEditFinishedAbort(), and starts the editing clock.
    OpenableWidget* makeGraphicsWidgetForImmediateEditing(
            QGraphicsScene* scene, const QColor& background_colour,
            bool fullscreen = true, bool esc_can_abort = true);

    // Returns a questionnaire element representing clinician details
    // (specialty, name, etc.). Only applicable to tasks with a clinician.
    QuElement* getClinicianQuestionnaireBlockRawPointer();
    QuElementPtr getClinicianQuestionnaireBlockElementPtr();

    // Returns a questionnaire page representing clinician details.
    //  Only applicable to tasks with a clinician.
    QuPagePtr getClinicianDetailsPage();

    // Do we have enough information about the clinician (meaning their name)?
    //  Only applicable to tasks with a clinician.
    bool isClinicianComplete() const;

    // Do we have enough information about the respondent (meaning their name
    // and relationship)? Only applicable to tasks with a respondent.
    bool isRespondentComplete() const;

    // Returns the respondent's relationship to the patient (from our standard
    // field.). Only applicable to tasks with a respondent.
    QVariant respondentRelationship() const;

    // Returns a questionnaire element representing respondent details.
    // Only applicable to tasks with a respondent.
    QuElement* getRespondentQuestionnaireBlockRawPointer(bool second_person);
    QuElementPtr getRespondentQuestionnaireBlockElementPtr(bool second_person);

    // Returns a questionnaire page representing respondent details.
    // Only applicable to tasks with a respondent.
    QuPagePtr getRespondentDetailsPage(bool second_person);

    // Returns a questionnaire element representing clinician and respondent
    // details. Only applicable to tasks with a clinician and a respondent.
    QuPagePtr getClinicianAndRespondentDetailsPage(bool second_person);

    // Create a standard set of NameValueOptions from the task's xstrings,
    // in ascending or descending order.
    NameValueOptions makeOptionsFromXstrings(
            const QString& xstring_prefix,
            int first,
            int last,
            const QString& xstring_suffix = QString());

public slots:
    // "The user has started to edit this task."
    void onEditStarted();

    // "The user has finished editing this task, successfully or not."
    // Updates the "time spent editing" clock and may set the "first exit was
    // finish/abort" flags.
    void onEditFinished(bool aborted = false);

    // "The user has finished editing this task, successfully."
    // Calls editFinished(false).
    void onEditFinishedProperly();

    // "The user has finished editing this task, unsuccessfully."
    // Calls editFinished(true).
    void onEditFinishedAbort();

signals:
    // Task has been aborted (and all its internal cleanup is done).
    void editingAborted();

    // Task has finished cleanly (and all its internal cleanup is done).
    void editingFinished();

    // ------------------------------------------------------------------------
    // Patient functions (for non-anonymous tasks)
    // ------------------------------------------------------------------------
public:

    // Returns the task's patient, or nullptr.
    Patient* patient() const;

    // Returns the patient's name (e.g. "Bob Jones"), or "".
    QString getPatientName() const;

    // Is the patient present and female?
    bool isFemale() const;

    // Is the patient present and male?
    bool isMale() const;

protected:
    // Sets the task's patient. (Used when tasks are being added.)
    void setPatient(int patient_id);

    // Moves this task to another patient. (Used for patient merges.)
    void moveToPatient(int patient_id);

    // ------------------------------------------------------------------------
    // Instance data
    // ------------------------------------------------------------------------
protected:
    mutable QSharedPointer<Patient> m_patient;  // our patient
    bool m_editing;  // are we editing?
    QDateTime m_editing_started;  // when did the current edit start?
    mutable bool m_is_complete_is_cached;
    mutable bool m_is_complete_cached_value;

    // ------------------------------------------------------------------------
    // Class data
    // ------------------------------------------------------------------------
protected:
    bool m_is_anonymous;  // is the task anonymous?
    bool m_has_clinician;  // does the task have a clinician?
    bool m_has_respondent;  // does the task have a respondent?

    // ------------------------------------------------------------------------
    // Translatable text
    // ------------------------------------------------------------------------
public:
    // String for "task is incomplete", for summary views.
    static QString incompleteMarker();

    // ------------------------------------------------------------------------
    // Static data
    // ------------------------------------------------------------------------
public:
    // Standard fieldnames
    static const QString PATIENT_FK_FIELDNAME;
    static const QString FIRSTEXIT_IS_FINISH_FIELDNAME;
    static const QString FIRSTEXIT_IS_ABORT_FIELDNAME;
    static const QString WHEN_FIRSTEXIT_FIELDNAME;
    static const QString EDITING_TIME_S_FIELDNAME;

    static const QString CLINICIAN_SPECIALTY;
    static const QString CLINICIAN_NAME;
    static const QString CLINICIAN_PROFESSIONAL_REGISTRATION;
    static const QString CLINICIAN_POST;
    static const QString CLINICIAN_SERVICE;
    static const QString CLINICIAN_CONTACT_DETAILS;

    static const QString RESPONDENT_NAME;
    static const QString RESPONDENT_RELATIONSHIP;
};