15.1.457. tablet_qt/questionnairelib/qugridcontainer.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_GRID_CREATION

#include "qugridcontainer.h"
#include <QDebug>
#include <QWidget>
#include "layouts/layouts.h"
#include "lib/sizehelpers.h"
#include "questionnairelib/questionnaire.h"
#include "widgets/basewidget.h"

#ifdef DEBUG_GRID_CREATION
#include "common/cssconst.h"
#include "lib/layoutdumper.h"
#endif


/*

MAKING COLUMN WIDTHS EQUAL

-   Prototypical problem with QGridLayout:

    widget1: fixed          widget2: expanding
    setColumnStretch(1)     setColumnStretch(1)
    |--------------------|  |---------------------------------------------|

    ... same stretch, different widths.
    I think that QGridLayout stretches any *spare* in proportion to
    setColumnStretch.

    https://doc.qt.io/qt-6.5/qgridlayout.html#details

    ... "If you want two columns to have the same width, you must set their
    minimum widths and stretch factors to be the same yourself. You do this
    using setColumnMinimumWidth() and setColumnStretch()."

What does not work properly:

-   widget->setMinimumWidth(1);
    grid->setColumnStretch(column, 1);

-   Encapsulating widget/layout so we can add a stretch, in case (for
    example) our left-hand cells have widgets with horizontal size
    policy Maximum, and the right-hand cells have Expanding; then the
    widgets' policies override our desired grid setColumnStretch()
    parameters.
        auto cell_widget = new QWidget();
        auto cell_layout = new QHBoxLayout();
        cell_layout->setContentsMargins(UiConst::NO_MARGINS);
        cell_widget->setLayout(cell_layout);
        QPointer<QWidget> w = e->widget(questionnaire);
        cell_layout->addWidget(w);
        cell_layout->addStretch();
        // ... then add the layout to the grid

-   Setting widget's size policy like:

        QSizePolicy sp = w->sizePolicy();
        sp.setHorizontalStretch(1);
        w->setSizePolicy(sp);

    Compare https://stackoverflow.com/questions/27808440/how-to-make-qt-grid-layout-auto-size-column-widths
    ... not sure you can just modify the returned size policy directly, though!
    sizePolicy() returns QSizePolicy, not a reference or pointer.

-   Notes:

    -   QGridLayoutPrivate::addData uses the widget's
        horizontalStretch() [via QQGridBox::hStretch()] only if no grid column
        stretch is applied.

-   What does work:

    // force widget's horizontal size policy to expanding
    grid->setColumnMinimumWidth(1);
    grid->setColumnStretch(1);

*/


QuGridContainer::QuGridContainer(QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(), parent)  // delegating constructor
{
}


QuGridContainer::QuGridContainer(const QVector<QuGridCell>& cells,
                                 QObject* parent) :
    QuElement(parent),
    m_cells(cells),
    m_expand(true),
    m_fixed_grid(true)

{
}


QuGridContainer::QuGridContainer(std::initializer_list<QuGridCell> cells,
                                 QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(cells), parent)  // delegating constructor
{
}


template<typename ElementList>
void constructFromElementList(const int n_columns,
                              ElementList elements,
                              const bool override_element_alignment,
                              QVector<QuGridCell>& cells)
{
    // Better than #define!
    int column = 0;
    int row = 0;
    for (auto e : (elements)) {
        QuGridCell cell(e, row, column, 1, 1, Qt::AlignLeft | Qt::AlignTop,
                        override_element_alignment);
        column = (column + 1) % n_columns;
        if (column == 0) {
            ++row;
        }
        cells.append(cell);
    }
}


QuGridContainer::QuGridContainer(const int n_columns,
                                 const QVector<QuElementPtr>& elements,
                                 const bool override_element_alignment,
                                 QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(), parent)  // delegating constructor
{
    constructFromElementList(n_columns, elements, override_element_alignment,
                             m_cells);
}


QuGridContainer::QuGridContainer(const int n_columns,
                                 const QVector<QuElement*>& elements,
                                 const bool override_element_alignment,
                                 QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(), parent)  // delegating constructor
{
    constructFromElementList(n_columns, elements, override_element_alignment,
                             m_cells);
}


QuGridContainer::QuGridContainer(const int n_columns,
                                 std::initializer_list<QuElementPtr> elements,
                                 const bool override_element_alignment,
                                 QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(), parent)  // delegating constructor
{
    constructFromElementList(n_columns, elements, override_element_alignment,
                             m_cells);
}


QuGridContainer::QuGridContainer(const int n_columns,
                                 std::initializer_list<QuElement*> elements,
                                 const bool override_element_alignment,
                                 QObject* parent) :
    QuGridContainer(QVector<QuGridCell>(), parent)  // delegating constructor
{
    constructFromElementList(n_columns, elements, override_element_alignment,
                             m_cells);
}


QuGridContainer* QuGridContainer::addCell(const QuGridCell& cell)
{
    m_cells.append(cell);
    return this;
}


QuGridContainer* QuGridContainer::setColumnStretch(
        const int column, const int stretch)
{
    m_column_stretch[column] = stretch;
    return this;
}

QuGridContainer* QuGridContainer::setColumnMinimumWidthInPixels(
        const int column, const int width)
{
    m_column_minimum_width_in_pixels[column] = width;
    return this;
}

QuGridContainer* QuGridContainer::setFixedGrid(const bool fixed_grid)
{
    m_fixed_grid = fixed_grid;
    return this;
}


QuGridContainer* QuGridContainer::setExpandHorizontally(const bool expand)
{
    m_expand = expand;
    return this;
}


QPointer<QWidget> QuGridContainer::makeWidget(Questionnaire* questionnaire)
{
    // m_expand: using preferredFixedHFWPolicy() doesn't prevent it expanding
    // right, even if the contained widgets are small.
    // Instead, use a horizontal container with a stretch. That works.

    QPointer<QWidget> widget = new BaseWidget();
    widget->setSizePolicy(sizehelpers::expandingFixedHFWPolicy());

#ifdef DEBUG_GRID_CREATION
    qDebug() << Q_FUNC_INFO;
    qDebug() << "... m_fixed_grid =" << m_fixed_grid;
    widget->setObjectName(cssconst::DEBUG_GREEN);
#endif
    auto grid = new GridLayout();
    grid->setContentsMargins(uiconst::NO_MARGINS);
    if (m_expand) {
        widget->setLayout(grid);
    } else {
        auto hbox = new HBoxLayout();
        hbox->addLayout(grid);
        hbox->addStretch();
        widget->setLayout(hbox);
    }
    for (const QuGridCell& c : m_cells) {
        QuElementPtr e = c.element;
        QPointer<QWidget> w = e->widget(questionnaire);
        if (!w) {
            qWarning() << Q_FUNC_INFO << "Element failed to create a widget!";
            continue;
        }

#ifdef DEBUG_GRID_CREATION
        w->setObjectName(cssconst::DEBUG_RED);
        qDebug() << "... cell:" << c;
#endif
        if (m_fixed_grid) {
            // Set widget to horizontal expanding
            QSizePolicy sp = w->sizePolicy();
            sp.setHorizontalPolicy(QSizePolicy::Expanding);
            w->setSizePolicy(sp);
#ifdef DEBUG_GRID_CREATION
            qDebug().noquote()
                    << "... forcing widget horizontal size policy to Expanding";
#endif

            // Set column minimum width, and column stretch (may be modified
            // below).
            grid->setColumnMinimumWidth(c.column, 1);
            grid->setColumnStretch(c.column, 1);
        }
#ifdef DEBUG_GRID_CREATION
        {
            QSizePolicy sp = w->sizePolicy();
            qDebug().noquote() << "... widget sizePolicy():"
                               << layoutdumper::toString(sp);
        }
#endif
        const auto alignment = c.override_element_alignment
                ? c.alignment
                : e->getWidgetAlignment();
        grid->addWidget(w, c.row, c.column,
                        c.row_span, c.column_span, alignment);
    }
    QMapIterator<int, int> stretch_it(m_column_stretch);
    while (stretch_it.hasNext()) {
        stretch_it.next();
        const int column = stretch_it.key();
        const int stretch = stretch_it.value();
#ifdef DEBUG_GRID_CREATION
        qDebug().nospace() << "... setColumnStretch(" << column
                           << "," << stretch << ")";
#endif
        grid->setColumnStretch(column, stretch);
    }

    QMapIterator<int, int> width_it(m_column_minimum_width_in_pixels);
    while (width_it.hasNext()) {
        width_it.next();
        const int column = width_it.key();
        const int width = width_it.value();
#ifdef DEBUG_GRID_CREATION
        qDebug().nospace() << "... setMinimumWidthInPixels(" << column
                           << "," << width << ")";
#endif
        grid->setColumnMinimumWidth(column, width);
    }

    return widget;
}


QVector<QuElementPtr> QuGridContainer::subelements() const
{
    QVector<QuElementPtr> elements;
    for (auto cell : m_cells) {
        elements.append(cell.element);
    }
    return elements;
}


QDebug operator<<(QDebug debug, const QuGridContainer& grid)
{
    debug.nospace()
            << "QuGridContainer(m_cells=" << grid.m_cells
            << ", m_column_stretch=" << grid.m_column_stretch
            << ", m_column_minimum_width_in_pixels=" << grid.m_column_minimum_width_in_pixels
            << ", m_expand=" << grid.m_expand
            << ", m_fixed_grid=" << grid.m_fixed_grid;
    return debug;
}