/*
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 "task.h"
#include <QObject>
#include <QVariant>
#include "common/preprocessor_aid.h" // IWYU pragma: keep
#include "common/textconst.h"
#include "common/uiconst.h"
#include "common/varconst.h"
#include "core/camcopsapp.h"
#include "db/databasemanager.h"
#include "dbobjects/patient.h"
#include "lib/datetime.h"
#include "lib/stringfunc.h"
#include "lib/uifunc.h"
#include "lib/version.h"
#include "maths/mathfunc.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/questionnairefunc.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qupage.h"
#include "questionnairelib/quspacer.h"
#include "version/camcopsversion.h"
#include "widgets/openablewidget.h"
#include "widgets/screenlikegraphicsview.h"
const QString Task::PATIENT_FK_FIELDNAME("patient_id");
const QString Task::FIRSTEXIT_IS_FINISH_FIELDNAME("firstexit_is_finish");
const QString Task::FIRSTEXIT_IS_ABORT_FIELDNAME("firstexit_is_abort");
const QString Task::WHEN_FIRSTEXIT_FIELDNAME("when_firstexit");
const QString Task::EDITING_TIME_S_FIELDNAME("editing_time_s");
const QString Task::CLINICIAN_SPECIALTY("clinician_specialty");
const QString Task::CLINICIAN_NAME("clinician_name");
const QString Task::CLINICIAN_PROFESSIONAL_REGISTRATION(
"clinician_professional_registration"
);
const QString Task::CLINICIAN_POST("clinician_post");
const QString Task::CLINICIAN_SERVICE("clinician_service");
const QString Task::CLINICIAN_CONTACT_DETAILS("clinician_contact_details");
const QString Task::RESPONDENT_NAME("respondent_name");
const QString Task::RESPONDENT_RELATIONSHIP("respondent_relationship");
Task::Task(
CamcopsApp& app,
DatabaseManager& db,
const QString& tablename,
const bool is_anonymous,
const bool has_clinician,
const bool has_respondent,
QObject* parent
) :
DatabaseObject(
app,
db,
tablename,
dbconst::PK_FIELDNAME,
true,
true,
true,
true,
parent
),
m_patient(nullptr),
m_editing(false),
m_is_complete_is_cached(false),
m_is_anonymous(is_anonymous),
m_has_clinician(has_clinician),
m_has_respondent(has_respondent)
{
// WATCH OUT: you can't call a derived class's overloaded function
// here; its vtable is incomplete.
// http://stackoverflow.com/questions/6561429/calling-virtual-function-of-derived-class-from-base-class-constructor
addField(FIRSTEXIT_IS_FINISH_FIELDNAME, QMetaType::fromType<bool>());
addField(FIRSTEXIT_IS_ABORT_FIELDNAME, QMetaType::fromType<bool>());
addField(WHEN_FIRSTEXIT_FIELDNAME, QMetaType::fromType<QDateTime>());
addField(Field(EDITING_TIME_S_FIELDNAME, QMetaType::fromType<double>())
.setCppDefaultValue(0.0));
if (!is_anonymous) {
addField(PATIENT_FK_FIELDNAME, QMetaType::fromType<int>());
}
if (has_clinician) {
addField(CLINICIAN_SPECIALTY, QMetaType::fromType<QString>());
addField(CLINICIAN_NAME, QMetaType::fromType<QString>());
addField(
CLINICIAN_PROFESSIONAL_REGISTRATION, QMetaType::fromType<QString>()
);
addField(CLINICIAN_POST, QMetaType::fromType<QString>());
addField(CLINICIAN_SERVICE, QMetaType::fromType<QString>());
addField(CLINICIAN_CONTACT_DETAILS, QMetaType::fromType<QString>());
}
if (has_respondent) {
addField(RESPONDENT_NAME, QMetaType::fromType<QString>());
addField(RESPONDENT_RELATIONSHIP, QMetaType::fromType<QString>());
}
connect(this, &DatabaseObject::dataChanged, this, &Task::onDataChanged);
}
// ============================================================================
// General info
// ============================================================================
QString Task::implementationTypeDescription() const
{
switch (implementationType()) {
case TaskImplementationType::Full:
#ifdef COMPILER_WANTS_DEFAULT_IN_EXHAUSTIVE_SWITCH
default:
#endif
return TextConst::fullTask();
case TaskImplementationType::UpgradableSkeleton:
return TextConst::DATA_COLLECTION_ONLY_UNLESS_UPGRADED_SYMBOL;
case TaskImplementationType::Skeleton:
return TextConst::DATA_COLLECTION_ONLY_SYMBOL;
}
}
QString Task::menuTitleSuffix() const
{
QStringList suffixes;
if (hasClinician()) {
suffixes += TextConst::HAS_CLINICIAN_SYMBOL;
}
if (hasRespondent()) {
suffixes += TextConst::HAS_RESPONDENT_SYMBOL;
}
switch (implementationType()) {
case TaskImplementationType::Full:
break;
case TaskImplementationType::UpgradableSkeleton:
suffixes += TextConst::DATA_COLLECTION_ONLY_UNLESS_UPGRADED_SYMBOL;
break;
case TaskImplementationType::Skeleton:
suffixes += TextConst::DATA_COLLECTION_ONLY_SYMBOL;
break;
}
if (isExperimental()) {
suffixes += TextConst::EXPERIMENTAL_SYMBOL;
}
if (isDefunct()) {
suffixes += TextConst::DEFUNCT_SYMBOL;
}
return suffixes.isEmpty() ? ""
: QString(" <i>[%1]</i>").arg(suffixes.join(""));
}
QString Task::menutitle() const
{
return QString("%1 (%2)%3")
.arg(longname(), shortname(), menuTitleSuffix());
}
QString Task::menuSubtitleSuffix() const
{
auto makeSuffix
= [](const QString& title, const QString& subtitle) -> QString {
return QString("%1: %2").arg(title, subtitle);
};
QStringList suffixes;
if (hasClinician()) {
suffixes += makeSuffix(
TextConst::HAS_CLINICIAN_SYMBOL,
TextConst::hasClinicianSubtitleSuffix()
);
}
if (hasRespondent()) {
suffixes += makeSuffix(
TextConst::HAS_RESPONDENT_SYMBOL,
TextConst::hasRespondentSubtitleSuffix()
);
}
switch (implementationType()) {
case TaskImplementationType::Full:
break;
case TaskImplementationType::UpgradableSkeleton:
suffixes += makeSuffix(
TextConst::DATA_COLLECTION_ONLY_UNLESS_UPGRADED_SYMBOL,
TextConst::dataCollectionOnlyUnlessUpgradedSubtitleSuffix()
);
break;
case TaskImplementationType::Skeleton:
suffixes += makeSuffix(
TextConst::DATA_COLLECTION_ONLY_SYMBOL,
TextConst::dataCollectionOnlySubtitleSuffix()
);
break;
}
if (isExperimental()) {
suffixes += makeSuffix(
TextConst::EXPERIMENTAL_SYMBOL,
TextConst::experimentalSubtitleSuffix()
);
}
if (isDefunct()) {
suffixes += makeSuffix(
TextConst::DEFUNCT_SYMBOL, TextConst::defunctSubtitleSuffix()
);
}
return suffixes.isEmpty()
? ""
: QString(" <i>[%1]</i>").arg(suffixes.join(" "));
}
QString Task::menusubtitle() const
{
return description() + menuSubtitleSuffix();
}
bool Task::isCrippled() const
{
QString failure_reason_dummy;
return implementationType() == TaskImplementationType::Skeleton
|| !hasExtraStrings() || !isTaskProperlyCreatable(failure_reason_dummy)
|| !isTaskUploadable(failure_reason_dummy);
}
bool Task::hasExtraStrings() const
{
return m_app.hasExtraStrings(xstringTaskname());
}
QString Task::infoFilenameStem() const
{
return m_tablename;
}
QString Task::xstringTaskname() const
{
return m_tablename;
}
QString Task::instanceTitle(bool with_pid) const
{
if (isAnonymous() || !with_pid) {
return QString("%1; %2").arg(
shortname(),
whenCreated().toString(datetime::SHORT_DATETIME_FORMAT)
);
}
Patient* pt = patient();
return QString("%1; %2; %3")
.arg(
shortname(),
pt ? pt->surnameUpperForename() : tr("MISSING PATIENT"),
whenCreated().toString(datetime::SHORT_DATETIME_FORMAT)
);
}
bool Task::isAnonymous() const
{
return m_is_anonymous;
}
bool Task::hasClinician() const
{
return m_has_clinician;
}
bool Task::hasRespondent() const
{
return m_has_respondent;
}
bool Task::isTaskPermissible(QString& failure_reason) const
{
const QVariant commercial = m_app.var(varconst::IP_USE_COMMERCIAL);
const QVariant clinical = m_app.var(varconst::IP_USE_CLINICAL);
const QVariant educational = m_app.var(varconst::IP_USE_EDUCATIONAL);
const QVariant research = m_app.var(varconst::IP_USE_RESEARCH);
auto not_definitely_false = [](const QVariant& v) -> bool {
return !mathfunc::eq(v, false);
};
auto is_unknown = [](const QVariant& v) -> bool {
return v.isNull() || v.toInt() == CommonOptions::UNKNOWN_INT;
};
const QString PROHIBITED_YES(
" "
+ tr("You have said you ARE using this software in that context "
"(see Settings). To use this task, you must seek permission "
"from the copyright holder (see Task Information).")
);
const QString PROHIBITED_UNKNOWN(
" "
+ tr("You have NOT SAID whether you are using this "
"software in that context (see Settings).")
);
if (prohibitsCommercial() && not_definitely_false(commercial)) {
failure_reason
= tr("Task not allowed for commercial use (see Task Information).")
+ (is_unknown(commercial) ? PROHIBITED_UNKNOWN : PROHIBITED_YES);
return false;
}
if (prohibitsClinical() && not_definitely_false(clinical)) {
failure_reason
= tr("Task not allowed for clinical use (see Task Information).")
+ (is_unknown(clinical) ? PROHIBITED_UNKNOWN : PROHIBITED_YES);
return false;
}
if (prohibitsEducational() && not_definitely_false(educational)) {
failure_reason
= tr("Task not allowed for educational use (see Task Information)."
)
+ (is_unknown(educational) ? PROHIBITED_UNKNOWN : PROHIBITED_YES);
return false;
}
if (prohibitsResearch() && not_definitely_false(research)) {
failure_reason
= tr("Task not allowed for research use (see Task Information).")
+ (is_unknown(research) ? PROHIBITED_UNKNOWN : PROHIBITED_YES);
return false;
}
if (implementationType() == TaskImplementationType::UpgradableSkeleton
&& prohibitedIfSkeleton() && !hasExtraStrings()) {
failure_reason = tr(
"Task may not be created in 'skeleton' form "
"(strings need to be downloaded from server)."
);
return false;
}
// Task doesn't have its data (e.g. strings present but too old)?
if (!isTaskProperlyCreatable(failure_reason)) {
return false;
}
return true;
}
Version Task::minimumServerVersion() const
{
return camcopsversion::MINIMUM_SERVER_VERSION;
}
bool Task::isTaskUploadable(QString& failure_reason) const
{
bool server_has_table;
Version min_client_version;
Version min_server_version;
const Version overall_min_server_version = Task::minimumServerVersion();
const Version server_version = m_app.serverVersion();
const QString table = tablename();
const bool may_upload = m_app.mayUploadTable(
table,
server_version,
server_has_table,
min_client_version,
min_server_version
);
#if 0
qDebug() << "table" << table
<< "server_version" << server_version
<< "may_upload" << may_upload
<< "server_has_table" << server_has_table
<< "min_client_version" << min_client_version
<< "min_server_version" << min_server_version;
#endif
if (may_upload) {
return true;
}
if (!server_has_table) {
failure_reason = tr("Table '%1' absent on server.").arg(table);
} else if (camcopsversion::CAMCOPS_CLIENT_VERSION < min_client_version) {
failure_reason
= tr("Server requires client version >=%1 for table '%2', "
"but we are only client version %3.")
.arg(
min_client_version.toString(),
table,
camcopsversion::CAMCOPS_CLIENT_VERSION.toString()
);
} else if (server_version < overall_min_server_version) {
failure_reason = tr("This client requires server version >=%1, "
"but the server is only version %2.")
.arg(
overall_min_server_version.toString(),
server_version.toString()
);
} else if (server_version < min_server_version) {
failure_reason
= tr("This client requires server version >=%1 for table '%2', "
"but the server is only version %3.")
.arg(
min_server_version.toString(),
table,
server_version.toString()
);
} else {
failure_reason
= "? [bug in Task::isTaskUploadable, "
"versus CamcopsApp::mayUploadTable]";
}
return false;
}
bool Task::isTaskProperlyCreatable(QString& failure_reason) const
{
Q_UNUSED(failure_reason)
return true;
}
bool Task::isServerStringVersionEnough(
const Version& minimum_server_version, QString& failure_reason
) const
{
const Version server_version = m_app.serverVersion();
if (server_version < minimum_server_version) {
failure_reason = tr("This client requires content strings from server "
"version >=%1, "
"but the server is only version %2. If the server "
"has recently "
"been updated, re-fetch the server information "
"from the Settings "
"menu.")
.arg(
minimum_server_version.toString(),
server_version.toString()
);
return false;
}
return true;
}
// ============================================================================
// Tables
// ============================================================================
QStringList Task::allTables() const
{
QStringList all_tables(tablename());
all_tables += ancillaryTables();
return all_tables;
}
void Task::makeTables()
{
makeTable();
makeAncillaryTables();
}
int Task::count(const WhereConditions& where) const
{
return m_db.count(m_tablename, where);
}
int Task::countForPatient(const int patient_id) const
{
if (isAnonymous()) {
return 0;
}
WhereConditions where;
where.add(PATIENT_FK_FIELDNAME, patient_id);
return count(where);
}
void Task::upgradeDatabase(
const Version& old_version, const Version& new_version
)
{
Q_UNUSED(old_version)
Q_UNUSED(new_version)
}
// ============================================================================
// Database object functions
// ============================================================================
bool Task::load(const int pk)
{
if (pk == dbconst::NONEXISTENT_PK) {
return false;
}
return DatabaseObject::load(pk);
}
bool Task::save()
{
// Sanity checks before we permit saving
if (!isAnonymous() && value(PATIENT_FK_FIELDNAME).isNull()) {
uifunc::stopApp(
"Task has no patient ID (and is not anonymous); "
"cannot save"
);
}
return DatabaseObject::save();
}
// ============================================================================
// Specific info
// ============================================================================
bool Task::isCompleteCached() const
{
if (!m_is_complete_is_cached) {
m_is_complete_cached_value = isComplete();
m_is_complete_is_cached = true;
}
return m_is_complete_cached_value;
}
void Task::onDataChanged()
{
m_is_complete_is_cached = false;
}
QStringList Task::summary() const
{
return QStringList{tr("MISSING SUMMARY")};
}
QStringList Task::detail() const
{
QStringList result = completenessInfo() + summary();
result.append(""); // blank line
result += recordSummaryLines();
return result;
}
OpenableWidget* Task::editor(const bool read_only)
{
Q_UNUSED(read_only)
qWarning() << "Base class Task::edit called - not a good thing!";
return nullptr;
}
// ============================================================================
// Assistance functions
// ============================================================================
QDateTime Task::whenCreated() const
{
return value(dbconst::CREATION_TIMESTAMP_FIELDNAME).toDateTime();
}
QStringList Task::completenessInfo() const
{
QStringList result;
if (!isCompleteCached()) {
result.append(incompleteMarker());
}
return result;
}
QString
Task::xstring(const QString& stringname, const QString& default_str) const
{
return m_app.xstring(xstringTaskname(), stringname, default_str);
}
QString Task::appstring(
const QString& stringname, const QString& default_str
) const
{
return m_app.appstring(stringname, default_str);
}
QStringList Task::fieldSummaries(
const QString& xstringprefix,
const QString& xstringsuffix,
const QString& spacer,
const QString& fieldprefix,
const int first,
const int last,
const QString& suffix
) const
{
using stringfunc::strseq;
const QStringList xstringnames
= strseq(xstringprefix, first, last, xstringsuffix);
const QStringList fieldnames = strseq(fieldprefix, first, last);
QStringList list;
for (int i = 0; i < fieldnames.length(); ++i) {
const QString& fieldname = fieldnames.at(i);
const QString& xstringname = xstringnames.at(i);
list.append(
fieldSummary(fieldname, xstring(xstringname), spacer, suffix)
);
}
return list;
}
QStringList Task::fieldSummariesYesNo(
const QString& xstringprefix,
const QString& xstringsuffix,
const QString& spacer,
const QString& fieldprefix,
const int first,
const int last,
const QString& suffix
) const
{
using stringfunc::strseq;
const QStringList xstringnames
= strseq(xstringprefix, first, last, xstringsuffix);
const QStringList fieldnames = strseq(fieldprefix, first, last);
QStringList list;
for (int i = 0; i < fieldnames.length(); ++i) {
const QString& fieldname = fieldnames.at(i);
const QString& xstringname = xstringnames.at(i);
list.append(
fieldSummaryYesNo(fieldname, xstring(xstringname), spacer, suffix)
);
}
return list;
}
QStringList Task::clinicianDetails(const QString& separator) const
{
if (!hasClinician()) {
return QStringList();
}
return QStringList{
fieldSummary(
CLINICIAN_SPECIALTY, TextConst::clinicianSpecialty(), separator
),
fieldSummary(CLINICIAN_NAME, TextConst::clinicianName(), separator),
fieldSummary(
CLINICIAN_PROFESSIONAL_REGISTRATION,
TextConst::clinicianProfessionalRegistration(),
separator
),
fieldSummary(CLINICIAN_POST, TextConst::clinicianPost(), separator),
fieldSummary(
CLINICIAN_SERVICE, TextConst::clinicianService(), separator
),
fieldSummary(
CLINICIAN_CONTACT_DETAILS,
TextConst::clinicianContactDetails(),
separator
),
};
}
QStringList Task::respondentDetails() const
{
if (!hasRespondent()) {
return QStringList();
}
return QStringList{
fieldSummary(RESPONDENT_NAME, TextConst::respondentNameThirdPerson()),
fieldSummary(
RESPONDENT_RELATIONSHIP,
TextConst::respondentRelationshipThirdPerson()
),
};
}
// ============================================================================
// Editing
// ============================================================================
void Task::setupForEditingAndSave(const int patient_id)
{
if (!isAnonymous()) {
setPatient(patient_id);
}
setDefaultClinicianVariablesAtFirstUse();
setDefaultsAtFirstUse();
save();
}
double Task::editingTimeSeconds() const
{
return valueDouble(EDITING_TIME_S_FIELDNAME);
}
void Task::setDefaultClinicianVariablesAtFirstUse()
{
if (!m_has_clinician) {
return;
}
setValue(
CLINICIAN_SPECIALTY,
m_app.varString(varconst::DEFAULT_CLINICIAN_SPECIALTY)
);
setValue(
CLINICIAN_NAME, m_app.varString(varconst::DEFAULT_CLINICIAN_NAME)
);
setValue(
CLINICIAN_PROFESSIONAL_REGISTRATION,
m_app.varString(varconst::DEFAULT_CLINICIAN_PROFESSIONAL_REGISTRATION)
);
setValue(
CLINICIAN_POST, m_app.varString(varconst::DEFAULT_CLINICIAN_POST)
);
setValue(
CLINICIAN_SERVICE, m_app.varString(varconst::DEFAULT_CLINICIAN_SERVICE)
);
setValue(
CLINICIAN_CONTACT_DETAILS,
m_app.varString(varconst::DEFAULT_CLINICIAN_CONTACT_DETAILS)
);
}
OpenableWidget* Task::makeGraphicsWidget(
QGraphicsScene* scene,
const QColor& background_colour,
const bool fullscreen,
const bool esc_can_abort
)
{
auto view = new ScreenLikeGraphicsView(scene);
view->setBackgroundColour(background_colour);
auto widget = new OpenableWidget();
widget->setWidgetAsOnlyContents(view, 0, fullscreen, esc_can_abort);
return widget;
}
OpenableWidget* Task::makeGraphicsWidgetForImmediateEditing(
QGraphicsScene* scene,
const QColor& background_colour,
const bool fullscreen,
const bool esc_can_abort
)
{
OpenableWidget* widget = makeGraphicsWidget(
scene, background_colour, fullscreen, esc_can_abort
);
connect(
widget, &OpenableWidget::aborting, this, &Task::onEditFinishedAbort
);
onEditStarted();
return widget;
}
QuElement* Task::getClinicianQuestionnaireBlockRawPointer()
{
return questionnairefunc::defaultGridRawPointer(
{
{TextConst::clinicianSpecialty(),
new QuLineEdit(fieldRef(CLINICIAN_SPECIALTY))},
{TextConst::clinicianName(),
new QuLineEdit(fieldRef(CLINICIAN_NAME))},
{TextConst::clinicianProfessionalRegistration(),
new QuLineEdit(fieldRef(CLINICIAN_PROFESSIONAL_REGISTRATION))},
{TextConst::clinicianPost(),
new QuLineEdit(fieldRef(CLINICIAN_POST))},
{TextConst::clinicianService(),
new QuLineEdit(fieldRef(CLINICIAN_SERVICE))},
{TextConst::clinicianContactDetails(),
new QuLineEdit(fieldRef(CLINICIAN_CONTACT_DETAILS))},
},
uiconst::DEFAULT_COLSPAN_Q,
uiconst::DEFAULT_COLSPAN_A
);
}
QuElementPtr Task::getClinicianQuestionnaireBlockElementPtr()
{
return QuElementPtr(getClinicianQuestionnaireBlockRawPointer());
}
QuPagePtr Task::getClinicianDetailsPage()
{
return QuPagePtr((new QuPage{getClinicianQuestionnaireBlockRawPointer()})
->setTitle(TextConst::clinicianDetails())
->setType(QuPage::PageType::Clinician));
}
bool Task::isClinicianComplete() const
{
if (!m_has_clinician) {
return false;
}
return !valueIsNullOrEmpty(CLINICIAN_NAME);
}
bool Task::isRespondentComplete() const
{
if (!m_has_respondent) {
return false;
}
return !valueIsNullOrEmpty(RESPONDENT_NAME)
&& !valueIsNullOrEmpty(RESPONDENT_RELATIONSHIP);
}
QVariant Task::respondentRelationship() const
{
if (!m_has_respondent) {
return QVariant();
}
return value(RESPONDENT_RELATIONSHIP);
}
QuElement*
Task::getRespondentQuestionnaireBlockRawPointer(const bool second_person)
{
const QString name = second_person
? TextConst::respondentNameSecondPerson()
: TextConst::respondentNameThirdPerson();
const QString relationship = second_person
? TextConst::respondentRelationshipSecondPerson()
: TextConst::respondentRelationshipThirdPerson();
return questionnairefunc::defaultGridRawPointer(
{
{name, new QuLineEdit(fieldRef(RESPONDENT_NAME))},
{relationship, new QuLineEdit(fieldRef(RESPONDENT_RELATIONSHIP))},
},
uiconst::DEFAULT_COLSPAN_Q,
uiconst::DEFAULT_COLSPAN_A
);
}
QuElementPtr
Task::getRespondentQuestionnaireBlockElementPtr(const bool second_person)
{
return QuElementPtr(getRespondentQuestionnaireBlockRawPointer(second_person
));
}
QuPagePtr Task::getRespondentDetailsPage(const bool second_person)
{
return QuPagePtr(
(new QuPage{getRespondentQuestionnaireBlockRawPointer(second_person)})
->setTitle(TextConst::respondentDetails())
->setType(
second_person ? QuPage::PageType::Patient
: QuPage::PageType::Clinician
)
);
}
QuPagePtr Task::getClinicianAndRespondentDetailsPage(const bool second_person)
{
return QuPagePtr(
(new QuPage{
getClinicianQuestionnaireBlockRawPointer(),
questionnairefunc::defaultGridRawPointer(
{
{"", new QuSpacer()},
},
uiconst::DEFAULT_COLSPAN_Q,
uiconst::DEFAULT_COLSPAN_A
),
getRespondentQuestionnaireBlockRawPointer(second_person)})
->setTitle(TextConst::clinicianAndRespondentDetails())
->setType(
second_person ? QuPage::PageType::ClinicianWithPatient
: QuPage::PageType::Clinician
)
);
}
NameValueOptions Task::makeOptionsFromXstrings(
const QString& xstring_prefix,
const int first,
const int last,
const QString& xstring_suffix
)
{
using stringfunc::strnum;
NameValueOptions options;
if (first > last) { // descending order
for (int i = first; i >= last; --i) {
options.append(NameValuePair(
xstring(strnum(xstring_prefix, i, xstring_suffix)), i
));
}
} else { // ascending order
for (int i = first; i <= last; ++i) {
options.append(NameValuePair(
xstring(strnum(xstring_prefix, i, xstring_suffix)), i
));
}
}
return options;
}
void Task::onEditStarted()
{
m_editing = true;
m_editing_started = datetime::now();
}
void Task::onEditFinished(const bool aborted)
{
if (!m_editing) {
qDebug() << Q_FUNC_INFO << "wasn't editing";
return;
}
m_editing = false;
// Time
const QDateTime now = datetime::now();
double editing_time_s = valueDouble(EDITING_TIME_S_FIELDNAME);
editing_time_s += datetime::doubleSecondsFrom(m_editing_started, now);
setValue(EDITING_TIME_S_FIELDNAME, editing_time_s);
// Exit flags
if (!valueBool(FIRSTEXIT_IS_FINISH_FIELDNAME)
&& !valueBool(FIRSTEXIT_IS_ABORT_FIELDNAME)) {
// First exit, so set flags:
setValue(WHEN_FIRSTEXIT_FIELDNAME, now);
setValue(FIRSTEXIT_IS_ABORT_FIELDNAME, aborted);
setValue(FIRSTEXIT_IS_FINISH_FIELDNAME, !aborted);
}
save();
if (aborted) {
emit editingAborted();
} else {
emit editingFinished();
}
}
void Task::onEditFinishedProperly()
{
onEditFinished(false);
}
void Task::onEditFinishedAbort()
{
onEditFinished(true);
}
// ============================================================================
// Patient functions (for non-anonymous tasks)
// ============================================================================
void Task::setPatient(const int patient_id)
{
// It's a really dangerous thing to set a patient ID invalidly, so this
// function will just stop the app if something stupid is attempted.
if (isAnonymous()) {
uifunc::stopApp("Attempt to set patient ID for an anonymous task");
}
if (!value(PATIENT_FK_FIELDNAME).isNull()) {
uifunc::stopApp("Setting patient ID, but it was already set");
}
setValue(PATIENT_FK_FIELDNAME, patient_id);
m_patient.clear();
}
void Task::moveToPatient(const int patient_id)
{
// This is used for patient merges.
// It is therefore more liberal than setPatient().
if (isAnonymous()) {
qWarning() << "Attempt to set patient ID for an anonymous task";
return;
}
setValue(PATIENT_FK_FIELDNAME, patient_id);
m_patient.clear();
}
Patient* Task::patient() const
{
if (!m_patient && !isAnonymous()) {
const QVariant patient_id_var = value(PATIENT_FK_FIELDNAME);
if (!patient_id_var.isNull()) {
const int patient_id = patient_id_var.toInt();
m_patient
= QSharedPointer<Patient>(new Patient(m_app, m_db, patient_id)
);
}
}
return m_patient.data();
}
QString Task::getPatientName() const
{
Patient* pt = patient();
if (!pt) {
return "";
}
return pt->forenameSurname();
}
bool Task::isFemale() const
{
Patient* pt = patient();
return pt ? pt->isFemale() : false;
}
bool Task::isMale() const
{
Patient* pt = patient();
return pt ? pt->isMale() : false;
}
// ============================================================================
// Translatable text
// ============================================================================
QString Task::incompleteMarker()
{
return tr("<b>(INCOMPLETE)</b>");
}