15.1.105. tablet_qt/dbobjects/storedvar.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/>.
*/

// #define DEBUG_SET_VALUE

#include "storedvar.h"
#include "core/camcopsapp.h"
#include "db/databasemanager.h"
#include "lib/uifunc.h"

const QString STOREDVAR_TABLENAME("storedvar");

const QString NAME_FIELDNAME("name");
const QString TYPE_FIELDNAME("type");
// - No need to keep to legacy fieldnames (valueInteger, valueReal, valueText)
//   as we'll no longer be uploading these.
const QString VALUE_BOOL_FIELDNAME("value_bool");
const QString VALUE_INTEGER_FIELDNAME("value_integer");
const QString VALUE_REAL_FIELDNAME("value_real");
const QString VALUE_TEXT_FIELDNAME("value_text");

// - Also, SQLite is typeless... could make use of that, and store all values
//   in the same column. But for generality:
const QMap<const int, QString> COLMAP{
    // Which database field shall we use to store each QVariant type?
    {QMetaType::Bool, VALUE_BOOL_FIELDNAME},
    {QMetaType::QDateTime, VALUE_TEXT_FIELDNAME},
    {QMetaType::Double, VALUE_REAL_FIELDNAME},
    {QMetaType::Int, VALUE_INTEGER_FIELDNAME},
    {QMetaType::LongLong, VALUE_INTEGER_FIELDNAME},
    {QMetaType::QString, VALUE_TEXT_FIELDNAME},
    {QMetaType::QUuid, VALUE_TEXT_FIELDNAME},
};
const QMap<const int, QString> TYPEMAP{
    // What value should we put in the 'type' database column to indicate
    // the QVariant type in use?
    // https://doc.qt.io/qt-6.5/qvariant-obsolete.html#Type-enum
    {QMetaType::Bool, "Bool"},
    {QMetaType::QDateTime, "DateTime"},
    {QMetaType::Double, "Double"},
    {QMetaType::Int, "Int"},
    {QMetaType::LongLong, "LongLong"},
    {QMetaType::QString, "String"},
    {QMetaType::QUuid, "Uuid"},
};


StoredVar::StoredVar(CamcopsApp& app, DatabaseManager& db,
                     const QString& name, const QMetaType type,
                     const QVariant& default_value) :
    DatabaseObject(app, db, STOREDVAR_TABLENAME, dbconst::PK_FIELDNAME,
                   true, false, false, false),
    m_name(name),
    m_type(type),
    m_value_fieldname("")
{
    const int type_id = type.id();

    // ------------------------------------------------------------------------
    // Define fields
    // ------------------------------------------------------------------------
    addField(NAME_FIELDNAME, QMetaType::fromType<QString>(), true, true, false);
    addField(TYPE_FIELDNAME, QMetaType::fromType<QString>(), true, false, false);
    QMapIterator<const int, QString> i(COLMAP);
    while (i.hasNext()) {
        i.next();
        const int fieldtype = i.key();
        const QString fieldname = i.value();
        if (!hasField(fieldname)) {
            // We can have duplicate/overlapping fieldnames, and it will be
            // happy (if the types are appropriately interconvertible).
            // The Field will have the type of the FIRST one inserted.
            // However, it is dreadfully confusing if you put the Bool
            // definition before the Int one, and all your integers are
            // converted to 1 or 0. So use different ones!
            addField(fieldname, QMetaType(fieldtype), false, false, false);
        }
        if (fieldtype == type_id) {
            // Define our primary field
            m_value_fieldname = fieldname;
        }
    }
    if (m_value_fieldname.isEmpty()) {
        uifunc::stopApp(QString(
            "StoredVar::StoredVar: m_value_fieldname unknown to StoredVar "
            "with name=%1, type=%2; is the type missing from COLMAP "
            "(in storedvar.cpp)?")
                        .arg(name, QString::number(type_id)));
    }
    if (!TYPEMAP.contains(type_id)) {
        qCritical() << Q_FUNC_INFO << "QVariant type unknown:" << type_id;
        uifunc::stopApp(
            "StoredVar::StoredVar: type unknown to StoredVar; see debug "
            "console for details and check TYPEMAP (in storedvar.cpp)");
    }

    // ------------------------------------------------------------------------
    // Load from database (or create/save), unless this is a specimen
    // ------------------------------------------------------------------------
    if (!name.isEmpty()) {
        // Not a specimen; load, or set defaults and save
        const bool success = load(NAME_FIELDNAME, name);
        if (!success) {
            setValue(NAME_FIELDNAME, name);
            setValue(TYPE_FIELDNAME, TYPEMAP[type_id]);
            // qDebug() << "Setting type to:" << type;
            setValue(default_value);
            save();
        }
    }
}


bool StoredVar::setValue(const QVariant& value, const bool save_to_db)
{
#ifdef DEBUG_SET_VALUE
    qDebug() << Q_FUNC_INFO << "Setting" << m_name << "to" << value;
#endif
    const bool changed = setValue(m_value_fieldname, value);
    if (save_to_db) {
        save();
    }
    return changed;
}


QVariant StoredVar::value() const
{
    QVariant v = value(m_value_fieldname);
    v.convert(m_type);
    return v;
}


QString StoredVar::name() const
{
    return m_name;
}


void StoredVar::makeIndexes()
{
    m_db.createIndex("_idx_storedvar_name",
                     STOREDVAR_TABLENAME, {NAME_FIELDNAME});
}