15.1.983. tablet_qt/widgets/booleanwidget.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 "booleanwidget.h"
#include <QDebug>
#include <QPainter>
#include <QStyle>
#include <QVariant>
#include "common/cssconst.h"
#include "common/preprocessor_aid.h"  // IWYU pragma: keep
#include "common/uiconst.h"
#include "lib/sizehelpers.h"
#include "lib/uifunc.h"
#include "lib/widgetfunc.h"
#include "widgets/clickablelabelwordwrapwide.h"
#include "widgets/imagebutton.h"

const QString CHECK_DISABLED("check_disabled.png");
const QString CHECK_UNSELECTED("check_unselected.png");
const QString CHECK_UNSELECTED_REQUIRED("check_unselected_required.png");
const QString CHECK_FALSE_BLACK("check_false_black.png");
const QString CHECK_FALSE_RED("check_false_red.png");
const QString CHECK_TRUE_BLACK("check_true_black.png");
const QString CHECK_TRUE_RED("check_true_red.png");

const QString RADIO_DISABLED("radio_disabled.png");
const QString RADIO_UNSELECTED("radio_unselected.png");
const QString RADIO_UNSELECTED_REQUIRED("radio_unselected_required.png");
const QString RADIO_SELECTED("radio_selected.png");


BooleanWidget::BooleanWidget(QWidget* parent) :
    QAbstractButton(parent),
    m_read_only(false),
    m_big(true),
    m_bold(false),
    m_appearance(Appearance::CheckRed),
    m_as_image(true),
    m_state(State::Null)
{
    m_imagebutton = new ImageButton();
    m_textbutton = new ClickableLabelWordWrapWide();
    m_layout = new VBoxLayout();
    m_layout->setContentsMargins(uiconst::NO_MARGINS);
    m_layout->addWidget(m_imagebutton);
    m_layout->addWidget(m_textbutton);
    setLayout(m_layout);

    connect(m_imagebutton, &ImageButton::clicked,
            this, &BooleanWidget::clicked);
    connect(m_textbutton, &ClickableLabelWordWrapWide::clicked,
            this, &BooleanWidget::clicked);

    updateWidget(true);
}


void BooleanWidget::setReadOnly(const bool read_only)
{
    if (read_only != m_read_only) {
        m_read_only = read_only;
        updateWidget(false);
    }
}


void BooleanWidget::setSize(const bool big)
{
    if (big != m_big) {
        m_big = big;
        updateWidget(true);
    }
}


void BooleanWidget::setBold(const bool bold)
{
    if (bold != m_bold) {
        m_bold = bold;
        updateWidget(true);
    }
}


void BooleanWidget::setAppearance(const BooleanWidget::Appearance appearance)
{
    if (appearance != m_appearance) {
        m_appearance = appearance;
        m_as_image = (appearance != Appearance::Text);
        updateWidget(true);
    }
}


void BooleanWidget::setValue(const QVariant& value, const bool mandatory,
                             const bool disabled)
{
    if (disabled) {
        setState(State::Disabled);
    } else if (value.isNull()) {
        setState(mandatory ? State::NullRequired : State::Null);
    } else if (value.toBool()) {
        setState(State::True);
    } else {
        setState(State::False);
    }
}


void BooleanWidget::setState(const BooleanWidget::State state)
{
    if (state != m_state) {
        m_state = state;
        updateWidget(false);
    }
}


void BooleanWidget::updateWidget(const bool full_refresh)
{
    QString img;
    switch (m_appearance) {
    case Appearance::CheckBlack:
        switch (m_state) {
        case State::Disabled:
            img = CHECK_DISABLED;
            break;
        case State::Null:
            img = CHECK_UNSELECTED;
            break;
        case State::NullRequired:
            img = CHECK_UNSELECTED_REQUIRED;
            break;
        case State::False:
            img = CHECK_FALSE_BLACK;
            break;
        case State::True:
            img = CHECK_TRUE_BLACK;
            break;
        }
        break;

    case Appearance::CheckBlackFalseAppearsBlank:
        switch (m_state) {
        case State::Disabled:
            img = CHECK_DISABLED;
            break;
        case State::Null:
            img = CHECK_UNSELECTED;
            break;
        case State::NullRequired:
            img = CHECK_UNSELECTED_REQUIRED;
            break;
        case State::False:
            img = CHECK_UNSELECTED;  // difference from CheckBlack
            break;
        case State::True:
            img = CHECK_TRUE_BLACK;
            break;
        }
        break;

    case Appearance::CheckRed:
#ifdef COMPILER_WANTS_DEFAULT_IN_EXHAUSTIVE_SWITCH
    default:
#endif
        switch (m_state) {
        case State::Disabled:
            img = CHECK_DISABLED;
            break;
        case State::Null:
            img = CHECK_UNSELECTED;
            break;
        case State::NullRequired:
            img = CHECK_UNSELECTED_REQUIRED;
            break;
        case State::False:
            img = CHECK_FALSE_RED;
            break;
        case State::True:
            img = CHECK_TRUE_RED;
            break;
        }
        break;

    case Appearance::CheckRedFalseAppearsBlank:
        switch (m_state) {
        case State::Disabled:
            img = CHECK_DISABLED;
            break;
        case State::Null:
            img = CHECK_UNSELECTED;
            break;
        case State::NullRequired:
            img = CHECK_UNSELECTED_REQUIRED;
            break;
        case State::False:
            img = CHECK_UNSELECTED;  // difference from CheckRed
            break;
        case State::True:
            img = CHECK_TRUE_RED;
            break;
        }
        break;

    case Appearance::Radio:
        switch (m_state) {
        case State::Disabled:
            img = RADIO_DISABLED;
            break;
        case State::Null:
            img = RADIO_UNSELECTED;
            break;
        case State::NullRequired:
            img = RADIO_UNSELECTED_REQUIRED;
            break;
        case State::False:
            // not so meaningful
            img = RADIO_UNSELECTED;
            break;
        case State::True:
            img = RADIO_SELECTED;
            break;
        }
        break;

    case Appearance::Text:
        // http://wiki.qt.io/DynamicPropertiesAndStylesheets
        {
            QString css = uifunc::textCSS(-1, m_bold, false);
            m_textbutton->setStyleSheet(css);

            switch (m_state) {
            case State::Disabled:
                m_textbutton->setProperty(cssconst::PROPERTY_STATE,
                                          cssconst::VALUE_DISABLED);
                break;
            case State::Null:
                m_textbutton->setProperty(cssconst::PROPERTY_STATE,
                                          cssconst::VALUE_NULL);
                break;
            case State::NullRequired:
                m_textbutton->setProperty(cssconst::PROPERTY_STATE,
                                          cssconst::VALUE_NULL_REQUIRED);
                break;
            case State::False:
                m_textbutton->setProperty(cssconst::PROPERTY_STATE,
                                          cssconst::VALUE_FALSE);
                break;
            case State::True:
                m_textbutton->setProperty(cssconst::PROPERTY_STATE,
                                          cssconst::VALUE_TRUE);
                break;
            }
            m_textbutton->setProperty(cssconst::PROPERTY_READ_ONLY,
                                      widgetfunc::cssBoolean(m_read_only));
        }
        break;
    }
    if (m_as_image) {
        if (full_refresh) {
            m_imagebutton->setVisible(true);
            m_textbutton->setVisible(false);
            m_imagebutton->setImageSize(m_big ? uiconst::g_iconsize
                                              : uiconst::g_small_iconsize);
            setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        }
        m_imagebutton->setImages(img, true, false, false, false, m_read_only);
        // ... don't alter unpressed images
        // ... FOR NOW, put pressed marker on top (as PNGs are not transparent
        //     inside the check boxes etc.)
    } else {  // Text
        if (full_refresh) {
            m_imagebutton->setVisible(false);
            m_textbutton->setVisible(true);
            setSizePolicy(sizehelpers::maximumFixedHFWPolicy());
        }
        widgetfunc::repolish(m_textbutton);
    }
    if (full_refresh) {
        updateGeometry();
    } else {
        update();
    }
}


void BooleanWidget::setText(const QString& text)
{
    // qDebug() << Q_FUNC_INFO << text;
    m_textbutton->setText(text);
    if (m_appearance == Appearance::Text) {
        updateGeometry();  // text change often implies size change
    }
}


void BooleanWidget::paintEvent(QPaintEvent* e)
{
    Q_UNUSED(e)
    /*
    // To draw child widgets explicitly, use render (since paintEvent is
    // protected).
    // http://stackoverflow.com/questions/18042969
    QPainter painter(this);
    if (m_as_image) {
        m_imagebutton->render(&painter);
    } else {
        m_textbutton->render(&painter);
    }
    // However, our child widgets draw themselves anyway.
    // We just have to implement this function somehow as QAbstractButton is
    // an abstract base class.
    */
}