15.1.433. tablet_qt/questionnairelib/qucanvas.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_SIZE

#include "qucanvas.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QLabel>
#include <QTimer>
#include "common/colourdefs.h"
#include "common/uiconst.h"
#include "db/blobfieldref.h"
#include "lib/convert.h"
#include "lib/timerfunc.h"
#include "lib/uifunc.h"
#include "questionnairelib/questionnaire.h"
#include "widgets/canvaswidget.h"
#include "widgets/imagebutton.h"
#include "widgets/spacer.h"

const int WRITE_DELAY_MS = 200;


QuCanvas::QuCanvas(BlobFieldRefPtr fieldref, const QSize& size,
                   const bool allow_shrink, const QImage::Format format,
                   const QColor& background_colour,
                   QObject* parent) :
    QuElement(parent),
    m_fieldref(fieldref),
    m_size(size),
    m_allow_shrink(allow_shrink),
    m_format(format),
    m_background_colour(background_colour),
    m_using_template(false)
{
    Q_ASSERT(m_fieldref);
    m_adjust_display_for_dpi = true;
    m_border_width_px = 2;
    m_border_colour = QCOLOR_SILVER;
    m_unused_space_colour = QCOLOR_TRANSPARENT;
    m_pen_colour = Qt::red;
    m_pen_width = 5;
    m_canvas = nullptr;
    m_missing_indicator = nullptr;
    m_no_missing_indicator = nullptr;
    m_field_write_pending = false;
    timerfunc::makeSingleShotTimer(m_timer);
    connect(m_timer.data(), &QTimer::timeout,
            this, &QuCanvas::completePendingFieldWrite);
    connect(m_fieldref.data(), &FieldRef::valueChanged,
            this, &QuCanvas::fieldValueChanged);
    connect(m_fieldref.data(), &FieldRef::mandatoryChanged,
            this, &QuCanvas::fieldValueChanged);
}


QuCanvas::QuCanvas(BlobFieldRefPtr fieldref, const QString& template_filename,
                   const QSize& size, const bool allow_shrink,
                   QObject* parent) :
    QuCanvas(fieldref, size, allow_shrink, QImage::Format_RGB32, Qt::white,
             parent)
{
    m_template_filename = template_filename;
    m_using_template = true;
}


QuCanvas* QuCanvas::setAdjustForDpi(const bool adjust_for_dpi)
{
    m_adjust_display_for_dpi = adjust_for_dpi;
    return this;
}


QuCanvas* QuCanvas::setBackgroundColour(const QColor& colour)
{
    m_background_colour = colour;
    return this;
}


QuCanvas* QuCanvas::setBorderWidth(const int width)
{
    m_border_width_px = width;
    return this;
}


QuCanvas* QuCanvas::setBorderColour(const QColor& colour)
{
    m_border_colour = colour;
    return this;
}


QuCanvas* QuCanvas::setUnusedSpaceColour(const QColor& colour)
{
    m_unused_space_colour = colour;
    return this;
}


QuCanvas* QuCanvas::setPenColour(const QColor& colour)
{
    m_pen_colour = colour;
    return this;
}


QuCanvas* QuCanvas::setPenWidth(const int width)
{
    m_pen_width = width;
    return this;
}


QuCanvas* QuCanvas::setAllowShrink(const bool allow_shrink)
{
    m_allow_shrink = allow_shrink;
    return this;
}


QPointer<QWidget> QuCanvas::makeWidget(Questionnaire* questionnaire)
{
    const bool read_only = questionnaire->readOnly();
    const Qt::Alignment align = Qt::AlignLeft | Qt::AlignTop;

    m_canvas = new CanvasWidget();
    QPen pen;
    pen.setColor(m_pen_colour);
    pen.setWidth(m_pen_width);
    m_canvas->setPen(pen);
    m_canvas->setBorder(m_border_width_px, m_border_colour);
    m_canvas->setUnusedSpaceColour(m_unused_space_colour);
    m_canvas->setEnabled(!read_only);
    m_canvas->setAllowShrink(m_allow_shrink);
    m_canvas->setAdjustDisplayForDpi(m_adjust_display_for_dpi);
    if (!read_only) {
        connect(m_canvas.data(), &CanvasWidget::imageChanged,
                this, &QuCanvas::imageChanged);
    }

    auto button_reset = new ImageButton(uiconst::CBS_DELETE);
    button_reset->setEnabled(!read_only);
    if (!read_only) {
        connect(button_reset, &QAbstractButton::clicked,
                this, &QuCanvas::resetFieldToNull);
    }
    m_missing_indicator = uifunc::iconWidget(
                uifunc::iconFilename(uiconst::ICON_WARNING));
    m_no_missing_indicator = QPointer<Spacer>(new Spacer(uiconst::g_iconsize));
    auto button_layout = new QVBoxLayout();
    button_layout->setContentsMargins(uiconst::NO_MARGINS);
    button_layout->addWidget(button_reset, 0, align);
    button_layout->addWidget(m_missing_indicator, 0, align);
    button_layout->addWidget(m_no_missing_indicator, 0, align);
    auto button_widget = new QWidget();
    button_widget->setLayout(button_layout);

    auto top_layout = new QHBoxLayout();
    top_layout->setContentsMargins(uiconst::NO_MARGINS);
    top_layout->addWidget(button_widget, 0, align);
    top_layout->addWidget(m_canvas, 0, align);

    auto widget = new QWidget();
    if (m_allow_shrink) {
        widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    } else {
        widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    }
    widget->setLayout(top_layout);

    setFromField();
    return widget;
}


void QuCanvas::imageChanged()
{
    m_field_write_pending = true;
    m_timer->start(WRITE_DELAY_MS);  // goes to completePendingFieldWrite
}


void QuCanvas::completePendingFieldWrite()
{
    if (!m_canvas || !m_field_write_pending) {
        return;
    }
    const QImage img = m_canvas->image();
    const bool changed = m_fieldref->setImage(img, this);
    m_field_write_pending = false;
    if (changed) {
        emit elementValueChanged();
    }
}


void QuCanvas::closing()
{
    completePendingFieldWrite();
}


void QuCanvas::setFromField()
{
    fieldValueChanged(m_fieldref.data(), nullptr);
}


void QuCanvas::fieldValueChanged(const FieldRef* fieldref,
                                 const QObject* originator)
{
    if (!m_canvas) {
        return;
    }

    // Mandatory: don't try to do it with a background; that doesn't work for
    // non-transparent templates, and it requires an immediate re-update when
    // the first strokes are drawn (but at all other times, we don't need to
    // redraw the widget when the user changes it).
    // So we'll do it with an indicator widget.

    const bool missing_input = fieldref->missingInput();
    if (m_missing_indicator) {
        m_missing_indicator->setVisible(missing_input);
    }
    if (m_no_missing_indicator) {
        m_no_missing_indicator->setVisible(!missing_input);
    }
    // This prevents the overall widget's vertical size from changing (which
    // looks odd) on first draw, if the canvas is smaller vertically than the
    // two buttons/indicators.

    if (originator != this) {
        if (fieldref->isNull()) {
            resetWidget();
        } else {
            bool success;
            QImage img = fieldref->image(&success);
            if (success) {
                m_canvas->setImage(img);
            } else {
                qWarning() << Q_FUNC_INFO
                           << "- bad image data in field; resetting";
                resetWidget();
            }
        }
    }
}


FieldRefPtrList QuCanvas::fieldrefs() const
{
    return FieldRefPtrList{m_fieldref};
}


void QuCanvas::resetWidget()
{
    QImage img;

    // If we're using a template, load it.
    bool loaded_template = false;
    if (m_using_template) {
        loaded_template = img.load(m_template_filename);
        if (!loaded_template) {
            qWarning() << Q_FUNC_INFO << "- failed to load:" << m_template_filename;
        }
    }
#ifdef DEBUG_SIZE
    qDebug() << Q_FUNC_INFO << "Initial template image size:" << img.size();
#endif

    // Determine size. Size is the image's size unless overridden by m_size...
    QSize size = m_size.isValid() ? m_size : img.size();
    // Adjustment for DPI is done by the CanvasWidget, not here.

#ifdef DEBUG_SIZE
    qDebug().nospace()
            << Q_FUNC_INFO << " Final internal image size (after m_size="
            << m_size << "): " << size;
#endif

    // Now we know the final size. Either we make sure the template is that
    // size, or if we don't have one, we make a background image.
    if (loaded_template) {
        if (img.size() != size) {
            img = img.scaled(size);
        }
    } else {
        // Make an image
        img = QImage(size, m_format);
        img.fill(m_background_colour);
    }

    // All ready. Set the canvas.
    m_canvas->setImage(img);
}


void QuCanvas::resetFieldToNull()
{
    resetWidget();
    m_fieldref->setValue(QVariant(), this);
    emit elementValueChanged();
}