// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include <qglobal.h>
#include "qcolumnview.h"

#if QT_CONFIG(columnview)

#include "qcolumnview_p.h"
#include "qcolumnviewgrip_p.h"

#include <qlistview.h>
#include <qabstractitemdelegate.h>
#include <qscrollbar.h>
#include <qpainter.h>
#include <qdebug.h>

QT_BEGIN_NAMESPACE

/*!
    \class QColumnView
    \brief The QColumnView class provides a model/view implementation of a column view.
    \ingroup model-view
    \ingroup advanced
    \inmodule QtWidgets

    QColumnView displays a model in a number of QListViews, one for each
    hierarchy in the tree.  This is sometimes referred to as a cascading list.

    The QColumnView class is one of the \l{Model/View Classes}
    and is part of Qt's \l{Model/View Programming}{model/view framework}.

    QColumnView implements the interfaces defined by the
    QAbstractItemView class to allow it to display data provided by
    models derived from the QAbstractItemModel class.

    \image qcolumnview.png {Address information in a column view}

    \sa {Model/View Programming}
*/

/*!
    Constructs a column view with a \a parent to represent a model's
    data. Use setModel() to set the model.

    \sa QAbstractItemModel
*/
QColumnView::QColumnView(QWidget * parent)
:  QAbstractItemView(*new QColumnViewPrivate, parent)
{
    Q_D(QColumnView);
    d->initialize();
}

/*!
    \internal
*/
QColumnView::QColumnView(QColumnViewPrivate & dd, QWidget * parent)
:  QAbstractItemView(dd, parent)
{
    Q_D(QColumnView);
    d->initialize();
}

void QColumnViewPrivate::initialize()
{
    Q_Q(QColumnView);
    q->setTextElideMode(Qt::ElideMiddle);
#if QT_CONFIG(animation)
    animationConnection =
        QObjectPrivate::connect(&currentAnimation, &QPropertyAnimation::finished,
                                this, &QColumnViewPrivate::changeCurrentColumn);
    currentAnimation.setTargetObject(hbar);
    currentAnimation.setPropertyName("value");
    currentAnimation.setEasingCurve(QEasingCurve::InOutQuad);
#endif // animation
    delete itemDelegate;
    q->setItemDelegate(new QColumnViewDelegate(q));
}

/*!
    Destroys the column view.
*/
QColumnView::~QColumnView()
{
    Q_D(QColumnView);
    // clear all connections:
#if QT_CONFIG(animation)
    disconnect(d->animationConnection);
#endif
    for (QMetaObject::Connection &conn : d->gripConnections)
        disconnect(conn);
    for (auto &&[_, conns] : d->viewConnections) {
        for (QMetaObject::Connection &conn : conns)
            disconnect(conn);
    }
}

/*!
    \property QColumnView::resizeGripsVisible
    \brief the way to specify if the list views gets resize grips or not

    By default, \c visible is set to true

    \sa setRootIndex()
*/
void QColumnView::setResizeGripsVisible(bool visible)
{
    Q_D(QColumnView);
    if (d->showResizeGrips == visible)
        return;
    d->showResizeGrips = visible;
    d->gripConnections.clear();
    for (QAbstractItemView *view : std::as_const(d->columns)) {
        if (visible) {
            QColumnViewGrip *grip = new QColumnViewGrip(view);
            view->setCornerWidget(grip);
            d->gripConnections.push_back(
                QObjectPrivate::connect(grip, &QColumnViewGrip::gripMoved,
                                        d, &QColumnViewPrivate::gripMoved)
            );
        } else {
            QWidget *widget = view->cornerWidget();
            view->setCornerWidget(nullptr);
            widget->deleteLater();
        }
    }
}

bool QColumnView::resizeGripsVisible() const
{
    Q_D(const QColumnView);
    return d->showResizeGrips;
}

/*!
    \property QColumnView::previewColumnVisible
    \brief whether the preview column is visible
    \since 6.11

    By default, \c visible is set to true
*/
void QColumnView::setPreviewColumnVisible(bool visible)
{
    Q_D(QColumnView);
    if (d->showPreviewColumn == visible)
        return;
    d->showPreviewColumn = visible;
    if (d->columns.constLast() == d->previewColumn)
        d->columns.constLast()->setVisible(d->showPreviewColumn);
}

bool QColumnView::isPreviewColumnVisible() const
{
    Q_D(const QColumnView);
    return d->showPreviewColumn;
}

/*!
    \reimp
*/
void QColumnView::setModel(QAbstractItemModel *model)
{
    Q_D(QColumnView);
    if (model == d->model)
        return;
    d->closeColumns();
    QAbstractItemView::setModel(model);
}

/*!
    \reimp
*/
void QColumnView::setRootIndex(const QModelIndex &index)
{
    Q_D(QColumnView);
    if (!model())
        return;

    d->closeColumns();
    Q_ASSERT(d->columns.size() == 0);

    QAbstractItemView *view = d->createColumn(index, true);
    if (view->selectionModel())
        view->selectionModel()->deleteLater();
    if (view->model())
        view->setSelectionModel(selectionModel());

    QAbstractItemView::setRootIndex(index);
    d->updateScrollbars();
}

/*!
    \reimp
*/
bool QColumnView::isIndexHidden(const QModelIndex &index) const
{
    Q_UNUSED(index);
    return false;
}

/*!
    \reimp
*/
QModelIndex QColumnView::indexAt(const QPoint &point) const
{
    Q_D(const QColumnView);
    for (int i = 0; i < d->columns.size(); ++i) {
        QPoint topLeft = d->columns.at(i)->frameGeometry().topLeft();
        QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
        QModelIndex index = d->columns.at(i)->indexAt(adjustedPoint);
        if (index.isValid())
            return index;
    }
    return QModelIndex();
}

/*!
    \reimp
*/
QRect QColumnView::visualRect(const QModelIndex &index) const
{
    if (!index.isValid())
        return QRect();

    Q_D(const QColumnView);
    for (int i = 0; i < d->columns.size(); ++i) {
        QRect rect = d->columns.at(i)->visualRect(index);
        if (!rect.isNull()) {
            rect.translate(d->columns.at(i)->frameGeometry().topLeft());
            return rect;
        }
    }
    return QRect();
}

/*!
    \reimp
 */
void QColumnView::scrollContentsBy(int dx, int dy)
{
    Q_D(QColumnView);
    if (d->columns.isEmpty() || dx == 0)
        return;

    dx = isRightToLeft() ? -dx : dx;
    for (int i = 0; i < d->columns.size(); ++i)
        d->columns.at(i)->move(d->columns.at(i)->x() + dx, 0);
    d->offset += dx;
    QAbstractItemView::scrollContentsBy(dx, dy);
}

/*!
    \reimp
*/
void QColumnView::scrollTo(const QModelIndex &index, ScrollHint hint)
{
    Q_D(QColumnView);
    Q_UNUSED(hint);
    if (!index.isValid() || d->columns.isEmpty())
        return;

#if QT_CONFIG(animation)
    if (d->currentAnimation.state() == QPropertyAnimation::Running)
        return;

    d->currentAnimation.stop();
#endif // animation

    // Fill up what is needed to get to index
    d->closeColumns(index, true);

    QModelIndex indexParent = index.parent();
    // Find the left edge of the column that contains index
    int currentColumn = 0;
    int leftEdge = 0;
    while (currentColumn < d->columns.size()) {
        if (indexParent == d->columns.at(currentColumn)->rootIndex())
            break;
        leftEdge += d->columns.at(currentColumn)->width();
        ++currentColumn;
    }

    // Don't let us scroll above the root index
    if (currentColumn == d->columns.size())
        return;

    int indexColumn = currentColumn;
    // Find the width of what we want to show (i.e. the right edge)
    int visibleWidth = d->columns.at(currentColumn)->width();
    // We want to always try to show two columns
    if (currentColumn + 1 < d->columns.size()) {
        ++currentColumn;
        visibleWidth += d->columns.at(currentColumn)->width();
    }

    int rightEdge = leftEdge + visibleWidth;
    if (isRightToLeft()) {
        leftEdge = viewport()->width() - leftEdge;
        rightEdge = leftEdge - visibleWidth;
        qSwap(rightEdge, leftEdge);
    }

    // If it is already visible don't animate
    if (leftEdge > -horizontalOffset()
        && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) {
            d->columns.at(indexColumn)->scrollTo(index);
            d->changeCurrentColumn();
            return;
    }

    int newScrollbarValue = 0;
    if (isRightToLeft()) {
        if (leftEdge < 0) {
            // scroll to the right
            newScrollbarValue = viewport()->size().width() - leftEdge;
        } else {
            // scroll to the left
            newScrollbarValue = rightEdge + horizontalOffset();
        }
    } else {
        if (leftEdge > -horizontalOffset()) {
            // scroll to the right
            newScrollbarValue = rightEdge - viewport()->size().width();
        } else {
            // scroll to the left
            newScrollbarValue = leftEdge;
        }
    }

#if QT_CONFIG(animation)
    if (const int animationDuration = style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this)) {
        d->currentAnimation.setDuration(animationDuration);
        d->currentAnimation.setEndValue(newScrollbarValue);
        d->currentAnimation.start();
    } else
#endif // animation
    {
        horizontalScrollBar()->setValue(newScrollbarValue);
    }
}

/*!
    \reimp
    Move left should go to the parent index
    Move right should go to the child index or down if there is no child
*/
QModelIndex QColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
    // the child views which have focus get to deal with this first and if
    // they don't accept it then it comes up this view and we only grip left/right
    Q_UNUSED(modifiers);
    if (!model())
        return QModelIndex();

    QModelIndex current = currentIndex();
    if (isRightToLeft()) {
        if (cursorAction == MoveLeft)
            cursorAction = MoveRight;
        else if (cursorAction == MoveRight)
            cursorAction = MoveLeft;
    }
    switch (cursorAction) {
    case MoveLeft:
        if (current.parent().isValid() && current.parent() != rootIndex())
            return (current.parent());
        else
            return current;

    case MoveRight:
        if (model()->hasChildren(current))
            return model()->index(0, 0, current);
        else
            return current.sibling(current.row() + 1, current.column());

    default:
        break;
    }

    return QModelIndex();
}

/*!
    \reimp
*/
void QColumnView::resizeEvent(QResizeEvent *event)
{
    Q_D(QColumnView);
    d->doLayout();
    d->updateScrollbars();
    if (!isRightToLeft()) {
        int diff = event->oldSize().width() - event->size().width();
        if (diff < 0 && horizontalScrollBar()->isVisible()
            && horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) {
            horizontalScrollBar()->setMaximum(horizontalScrollBar()->maximum() + diff);
        }
    }
    QAbstractItemView::resizeEvent(event);
}

/*!
    \internal
*/
void QColumnViewPrivate::updateScrollbars()
{
    Q_Q(QColumnView);
#if QT_CONFIG(animation)
    if (currentAnimation.state() == QPropertyAnimation::Running)
        return;
#endif // animation

    // find the total horizontal length of the laid out columns
    int horizontalLength = 0;
    if (!columns.isEmpty()) {
        horizontalLength = (columns.constLast()->x() + columns.constLast()->width()) - columns.constFirst()->x();
        if (horizontalLength <= 0) // reverse mode
            horizontalLength = (columns.constFirst()->x() + columns.constFirst()->width()) - columns.constLast()->x();
    }

    QSize viewportSize = viewport->size();
    if (horizontalLength < viewportSize.width() && hbar->value() == 0) {
        hbar->setRange(0, 0);
    } else {
        int visibleLength = qMin(horizontalLength + q->horizontalOffset(), viewportSize.width());
        int hiddenLength = horizontalLength - visibleLength;
        if (hiddenLength != hbar->maximum())
            hbar->setRange(0, hiddenLength);
    }
    if (!columns.isEmpty()) {
        int pageStepSize = columns.at(0)->width();
        if (pageStepSize != hbar->pageStep())
            hbar->setPageStep(pageStepSize);
    }
    bool visible = (hbar->maximum() > 0);
    if (visible != hbar->isVisible())
        hbar->setVisible(visible);
}

/*!
    \reimp
*/
int QColumnView::horizontalOffset() const
{
    Q_D(const QColumnView);
    return d->offset;
}

/*!
    \reimp
*/
int QColumnView::verticalOffset() const
{
    return 0;
}

/*!
    \reimp
*/
QRegion QColumnView::visualRegionForSelection(const QItemSelection &selection) const
{
    int ranges = selection.size();

    if (ranges == 0)
        return QRect();

    // Note that we use the top and bottom functions of the selection range
    // since the data is stored in rows.
    int firstRow = selection.at(0).top();
    int lastRow = selection.at(0).top();
    for (int i = 0; i < ranges; ++i) {
        firstRow = qMin(firstRow, selection.at(i).top());
        lastRow = qMax(lastRow, selection.at(i).bottom());
    }

    QModelIndex firstIdx = model()->index(qMin(firstRow, lastRow), 0, rootIndex());
    QModelIndex lastIdx = model()->index(qMax(firstRow, lastRow), 0, rootIndex());

    if (firstIdx == lastIdx)
        return visualRect(firstIdx);

    QRegion firstRegion = visualRect(firstIdx);
    QRegion lastRegion = visualRect(lastIdx);
    return firstRegion.united(lastRegion);
}

/*!
    \reimp
*/
void QColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
    Q_UNUSED(rect);
    Q_UNUSED(command);
}

/*!
    \reimp
*/
void QColumnView::setSelectionModel(QItemSelectionModel *newSelectionModel)
{
    Q_D(const QColumnView);
    for (int i = 0; i < d->columns.size(); ++i) {
        if (d->columns.at(i)->selectionModel() == selectionModel()) {
            d->columns.at(i)->setSelectionModel(newSelectionModel);
            break;
        }
    }
    QAbstractItemView::setSelectionModel(newSelectionModel);
}

/*!
    \reimp
*/
QSize QColumnView::sizeHint() const
{
    Q_D(const QColumnView);
    QSize sizeHint;
    for (int i = 0; i < d->columns.size(); ++i) {
        sizeHint += d->columns.at(i)->sizeHint();
    }
    return sizeHint.expandedTo(QAbstractItemView::sizeHint());
}

/*!
    \internal
    Move all widgets from the corner grip and to the right
  */
void QColumnViewPrivate::gripMoved(int offset)
{
    Q_Q(QColumnView);

    QObject *grip = q->sender();
    Q_ASSERT(grip);

    if (q->isRightToLeft())
        offset = -1 * offset;

    bool found = false;
    for (int i = 0; i < columns.size(); ++i) {
        if (!found && columns.at(i)->cornerWidget() == grip) {
            found = true;
            columnSizes[i] = columns.at(i)->width();
            if (q->isRightToLeft())
                columns.at(i)->move(columns.at(i)->x() + offset, 0);
            continue;
        }
        if (!found)
            continue;

        int currentX = columns.at(i)->x();
        columns.at(i)->move(currentX + offset, 0);
    }

    updateScrollbars();
}

/*!
    \internal

    Find where the current columns intersect parent's columns

    Delete any extra columns and insert any needed columns.
  */
void QColumnViewPrivate::closeColumns(const QModelIndex &parent, bool build)
{
    if (columns.isEmpty())
        return;

    bool clearAll = !parent.isValid();
    bool passThroughRoot = false;

    QList<QModelIndex> dirsToAppend;

    // Find the last column that matches the parent's tree
    int currentColumn = -1;
    QModelIndex parentIndex = parent;
    while (currentColumn == -1 && parentIndex.isValid()) {
        if (columns.isEmpty())
            break;
        parentIndex = parentIndex.parent();
        if (root == parentIndex)
            passThroughRoot = true;
        if (!parentIndex.isValid())
            break;
        for (int i = columns.size() - 1; i >= 0; --i) {
            if (columns.at(i)->rootIndex() == parentIndex) {
                currentColumn = i;
                break;
            }
        }
        if (currentColumn == -1)
            dirsToAppend.append(parentIndex);
    }

    // Someone wants to go to an index that can be reached without changing
    // the root index, don't allow them
    if (!clearAll && !passThroughRoot && currentColumn == -1)
        return;

    if (currentColumn == -1 && parent.isValid())
        currentColumn = 0;

    // Optimization so we don't go deleting and then creating the same thing
    bool alreadyExists = false;
    if (build && columns.size() > currentColumn + 1) {
        bool viewingParent = (columns.at(currentColumn + 1)->rootIndex() == parent);
        bool viewingChild = (!model->hasChildren(parent)
                             && !columns.at(currentColumn + 1)->rootIndex().isValid());
        if (viewingParent || viewingChild) {
            currentColumn++;
            alreadyExists = true;
        }
    }

    // Delete columns that don't match our path
    for (int i = columns.size() - 1; i > currentColumn; --i) {
        QAbstractItemView* notShownAnymore = columns.at(i);
        columns.removeAt(i);
        notShownAnymore->setVisible(false);
        if (notShownAnymore != previewColumn) {
            notShownAnymore->deleteLater();
            disconnectView(notShownAnymore);
        }
    }

    if (columns.isEmpty()) {
        offset = 0;
        updateScrollbars();
    }

    // Now fill in missing columns
    while (!dirsToAppend.isEmpty()) {
        QAbstractItemView *newView = createColumn(dirsToAppend.takeLast(), true);
        if (!dirsToAppend.isEmpty())
            newView->setCurrentIndex(dirsToAppend.constLast());
    }

    if (build && !alreadyExists)
        createColumn(parent, false);
}

void QColumnViewPrivate::disconnectView(QAbstractItemView *view)
{
    const auto it = viewConnections.find(view);
    if (it == viewConnections.end())
        return;
    for (QMetaObject::Connection &connection : it.value())
        QObject::disconnect(connection);
    viewConnections.erase(it);
}

void QColumnViewPrivate::clicked(const QModelIndex &index)
{
    Q_Q(QColumnView);
    QModelIndex parent = index.parent();
    QAbstractItemView *columnClicked = nullptr;
    for (int column = 0; column < columns.size(); ++column) {
        if (columns.at(column)->rootIndex() == parent) {
            columnClicked = columns[column];
            break;
        }
    }
    if (q->selectionModel() && columnClicked) {
        QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current;
        if (columnClicked->selectionModel()->isSelected(index))
            flags |= QItemSelectionModel::Select;
        q->selectionModel()->setCurrentIndex(index, flags);
    }
}

/*!
    \internal
    Create a new column for \a index.  A grip is attached if requested and it is shown
    if requested.

    Return the new view

    \sa createColumn(), setPreviewWidget()
    \sa doLayout()
*/
QAbstractItemView *QColumnViewPrivate::createColumn(const QModelIndex &index, bool show)
{
    Q_Q(QColumnView);
    QAbstractItemView *view = nullptr;
    QMetaObject::Connection clickedConnection;
    if (model->hasChildren(index)) {
        view = q->createColumn(index);
        clickedConnection = QObjectPrivate::connect(view, &QAbstractItemView::clicked,
                                                    this, &QColumnViewPrivate::clicked);
    } else {
        if (!previewColumn)
            previewColumn = createPreviewColumn();
        view = previewColumn;
        if (previewWidget)
            view->setMinimumWidth(qMax(view->minimumWidth(), previewWidget->minimumWidth()));
    }

    viewConnections[view] = {
        QObject::connect(view, &QAbstractItemView::activated, q, &QColumnView::activated),
        QObject::connect(view, &QAbstractItemView::clicked, q, &QColumnView::clicked),
        QObject::connect(view, &QAbstractItemView::doubleClicked, q, &QColumnView::doubleClicked),
        QObject::connect(view, &QAbstractItemView::entered, q, &QColumnView::entered),
        QObject::connect(view, &QAbstractItemView::pressed, q, &QColumnView::pressed),
        std::move(clickedConnection),
    };

    view->setFocusPolicy(Qt::NoFocus);
    view->setParent(viewport);
    Q_ASSERT(view);

    // Setup corner grip
    if (showResizeGrips) {
        QColumnViewGrip *grip = new QColumnViewGrip(view);
        view->setCornerWidget(grip);
        gripConnections.push_back(
            QObjectPrivate::connect(grip, &QColumnViewGrip::gripMoved,
                                    this, &QColumnViewPrivate::gripMoved)
        );
    }

    if (columnSizes.size() > columns.size()) {
        view->setGeometry(0, 0, columnSizes.at(columns.size()), viewport->height());
    } else {
        int initialWidth = view->sizeHint().width();
        if (q->isRightToLeft())
            view->setGeometry(viewport->width() - initialWidth, 0, initialWidth, viewport->height());
        else
            view->setGeometry(0, 0, initialWidth, viewport->height());
        columnSizes.resize(qMax(columnSizes.size(), columns.size() + 1));
        columnSizes[columns.size()] = initialWidth;
    }
    if (!columns.isEmpty() && columns.constLast()->isHidden())
        columns.constLast()->setVisible(true);

    columns.append(view);
    doLayout();
    updateScrollbars();
    if (show && view->isHidden() && view != previewColumn)
        view->setVisible(true);
    if (view == previewColumn)
        view->setVisible(showPreviewColumn);
    return view;
}

/*!
    \fn void QColumnView::updatePreviewWidget(const QModelIndex &index)

    This signal is emitted when the preview widget should be updated to
    provide rich information about \a index

    \sa previewWidget()
 */

/*!
    To use a custom widget for the final column when you select
    an item overload this function and return a widget.
    \a index is the root index that will be assigned to the view.

    Return the new view.  QColumnView will automatically take ownership of the widget.

    \sa setPreviewWidget()
 */
QAbstractItemView *QColumnView::createColumn(const QModelIndex &index)
{
    QListView *view = new QListView(viewport());

    initializeColumn(view);

    view->setRootIndex(index);
    if (model()->canFetchMore(index))
        model()->fetchMore(index);

    return view;
}

/*!
    Copies the behavior and options of the column view and applies them to
    the \a column such as the iconSize(), textElideMode() and
    alternatingRowColors(). This can be useful when reimplementing
    createColumn().

    \sa createColumn()
 */
void QColumnView::initializeColumn(QAbstractItemView *column) const
{
    Q_D(const QColumnView);

    column->setFrameShape(QFrame::NoFrame);
    column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    column->setMinimumWidth(100);
    column->setAttribute(Qt::WA_MacShowFocusRect, false);

#if QT_CONFIG(draganddrop)
    column->setDragDropMode(dragDropMode());
    column->setDragDropOverwriteMode(dragDropOverwriteMode());
    column->setDropIndicatorShown(showDropIndicator());
#endif
    column->setAlternatingRowColors(alternatingRowColors());
    column->setAutoScroll(hasAutoScroll());
    column->setEditTriggers(editTriggers());
    column->setHorizontalScrollMode(horizontalScrollMode());
    column->setIconSize(iconSize());
    column->setSelectionBehavior(selectionBehavior());
    column->setSelectionMode(selectionMode());
    column->setTabKeyNavigation(tabKeyNavigation());
    column->setTextElideMode(textElideMode());
    column->setVerticalScrollMode(verticalScrollMode());

    column->setModel(model());

    // Copy the custom delegate per row
    for (auto i = d->rowDelegates.cbegin(), end = d->rowDelegates.cend(); i != end; ++i)
        column->setItemDelegateForRow(i.key(), i.value());

    // set the delegate to be the columnview delegate
    QAbstractItemDelegate *delegate = column->itemDelegate();
    column->setItemDelegate(d->itemDelegate);
    delete delegate;
}

/*!
    Returns the preview widget, or \nullptr if there is none.

    \sa setPreviewWidget(), updatePreviewWidget()
*/
QWidget *QColumnView::previewWidget() const
{
    Q_D(const QColumnView);
    return d->previewWidget;
}

/*!
    Sets the preview \a widget.

    The \a widget becomes a child of the column view, and will be
    destroyed when the column area is deleted or when a new widget is
    set.

    \sa previewWidget(), updatePreviewWidget()
*/
void QColumnView::setPreviewWidget(QWidget *widget)
{
    Q_D(QColumnView);
    d->setPreviewWidget(widget);
}

/*!
    \internal
*/
void QColumnViewPrivate::setPreviewWidget(QWidget *widget)
{
    QColumnViewPreviewColumn *column = createPreviewColumn();
    previewColumn = column;
    column->setPreviewWidget(widget);
    previewWidget = widget;
    if (previewWidget)
        previewWidget->setParent(column->viewport());
}

/*!
    \internal
*/
QColumnViewPreviewColumn *QColumnViewPrivate::createPreviewColumn()
{
    Q_Q(QColumnView);
    if (previewColumn) {
        if (!columns.isEmpty() && columns.constLast() == previewColumn)
            columns.removeLast();
        previewColumn->deleteLater();
    }
    QColumnViewPreviewColumn *column = new QColumnViewPreviewColumn(q);
    column->hide();
    column->setFrameShape(QFrame::NoFrame);
    column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    column->setSelectionMode(QAbstractItemView::NoSelection);
    column->setMinimumWidth(qMax(column->verticalScrollBar()->width(),
                                 column->minimumWidth()));
    column->setPreviewWidget(nullptr);
    return column;
}

/*!
    Sets the column widths to the values given in the \a list.  Extra values in the list are
    kept and used when the columns are created.

    If list contains too few values, only width of the rest of the columns will not be modified.

    \sa columnWidths(), createColumn()
*/
void QColumnView::setColumnWidths(const QList<int> &list)
{
    Q_D(QColumnView);
    int i = 0;
    const int listCount = list.size();
    const int count = qMin(listCount, d->columns.size());
    for (; i < count; ++i) {
        d->columns.at(i)->resize(list.at(i), d->columns.at(i)->height());
        d->columnSizes[i] = list.at(i);
    }

    d->columnSizes.reserve(listCount);
    for (; i < listCount; ++i)
        d->columnSizes.append(list.at(i));
}

/*!
    Returns a list of the width of all the columns in this view.

    \sa setColumnWidths()
*/
QList<int> QColumnView::columnWidths() const
{
    Q_D(const QColumnView);
    QList<int> list;
    const int columnCount = d->columns.size();
    list.reserve(columnCount);
    for (int i = 0; i < columnCount; ++i)
        list.append(d->columnSizes.at(i));
    return list;
}

/*!
    \reimp
*/
void QColumnView::rowsInserted(const QModelIndex &parent, int start, int end)
{
    QAbstractItemView::rowsInserted(parent, start, end);
    d_func()->checkColumnCreation(parent);
}

/*!
    \reimp
*/
void QColumnView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_D(QColumnView);
    if (!current.isValid()) {
        QAbstractItemView::currentChanged(current, previous);
        return;
    }

    QModelIndex currentParent = current.parent();
    // optimize for just moving up/down in a list where the child view doesn't change
    if (currentParent == previous.parent()
            && model()->hasChildren(current) && model()->hasChildren(previous)) {
        for (int i = 0; i < d->columns.size(); ++i) {
            if (currentParent == d->columns.at(i)->rootIndex()) {
                if (d->columns.size() > i + 1) {
                    QAbstractItemView::currentChanged(current, previous);
                    return;
                }
                break;
            }
        }
    }

    // Scrolling to the right we need to have an empty spot
    bool found = false;
    if (currentParent == previous) {
        for (int i = 0; i < d->columns.size(); ++i) {
            if (currentParent == d->columns.at(i)->rootIndex()) {
                found = true;
                if (d->columns.size() < i + 2) {
                    d->createColumn(current, false);
                }
                break;
            }
        }
    }
    if (!found)
        d->closeColumns(current, true);

    if (!model()->hasChildren(current))
        emit updatePreviewWidget(current);

    QAbstractItemView::currentChanged(current, previous);
}

/*
    We have change the current column and need to update focus and selection models
    on the new current column.
*/
void QColumnViewPrivate::changeCurrentColumn()
{
    Q_Q(QColumnView);
    if (columns.isEmpty())
        return;

    QModelIndex current = q->currentIndex();
    if (!current.isValid())
        return;

    // We might have scrolled far to the left so we need to close all of the children
    closeColumns(current, true);

    // Set up the "current" column with focus
    int currentColumn = qMax(0, columns.size() - 2);
    QAbstractItemView *parentColumn = columns.at(currentColumn);
    if (q->hasFocus())
        parentColumn->setFocus(Qt::OtherFocusReason);
    q->setFocusProxy(parentColumn);

    // find the column that is our current selection model and give it a new one.
    for (int i = 0; i < columns.size(); ++i) {
        if (columns.at(i)->selectionModel() == q->selectionModel()) {
            QItemSelectionModel *replacementSelectionModel =
                new QItemSelectionModel(parentColumn->model());
            replacementSelectionModel->setCurrentIndex(
                q->selectionModel()->currentIndex(), QItemSelectionModel::Current);
            replacementSelectionModel->select(
                q->selectionModel()->selection(), QItemSelectionModel::Select);
            QAbstractItemView *view = columns.at(i);
            view->setSelectionModel(replacementSelectionModel);
            view->setFocusPolicy(Qt::NoFocus);
            if (columns.size() > i + 1) {
                const QModelIndex newRootIndex = columns.at(i + 1)->rootIndex();
                if (newRootIndex.isValid())
                    view->setCurrentIndex(newRootIndex);
            }
            break;
        }
    }
    parentColumn->selectionModel()->deleteLater();
    parentColumn->setFocusPolicy(Qt::StrongFocus);
    parentColumn->setSelectionModel(q->selectionModel());
    // We want the parent selection to stay highlighted (but dimmed depending upon the color theme)
    if (currentColumn > 0) {
        parentColumn = columns.at(currentColumn - 1);
        if (parentColumn->currentIndex() != current.parent())
            parentColumn->setCurrentIndex(current.parent());
    }

    if (columns.constLast()->isHidden()) {
        if (columns.constLast() != previewColumn)
            columns.constLast()->setVisible(true);
        else
            columns.constLast()->setVisible(showPreviewColumn);
    }
    if (columns.constLast()->selectionModel())
        columns.constLast()->selectionModel()->clear();
    updateScrollbars();
}

/*!
    \reimp
*/
void QColumnView::selectAll()
{
    if (!model() || !selectionModel())
        return;

    QModelIndexList indexList = selectionModel()->selectedIndexes();
    QModelIndex parent = rootIndex();
    QItemSelection selection;
    if (indexList.size() >= 1)
        parent = indexList.at(0).parent();
    if (indexList.size() == 1) {
        parent = indexList.at(0);
        if (!model()->hasChildren(parent))
            parent = parent.parent();
        else
            selection.append(QItemSelectionRange(parent, parent));
    }

    QModelIndex tl = model()->index(0, 0, parent);
    QModelIndex br = model()->index(model()->rowCount(parent) - 1,
                                    model()->columnCount(parent) - 1,
                                    parent);
    selection.append(QItemSelectionRange(tl, br));
    selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
}

/*
 * private object implementation
 */
QColumnViewPrivate::QColumnViewPrivate()
:  QAbstractItemViewPrivate()
,showResizeGrips(true)
,showPreviewColumn(true)
,offset(0)
,previewWidget(nullptr)
,previewColumn(nullptr)
{
}

QColumnViewPrivate::~QColumnViewPrivate()
{
}

/*!
    \internal

  */
void QColumnViewPrivate::columnsInserted(const QModelIndex &parent, int start, int end)
{
    QAbstractItemViewPrivate::columnsInserted(parent, start, end);
    checkColumnCreation(parent);
}

/*!
    \internal

    Makes sure we create a corresponding column as a result of changing the model.

  */
void QColumnViewPrivate::checkColumnCreation(const QModelIndex &parent)
{
    if (parent == q_func()->currentIndex() && model->hasChildren(parent)) {
        //the parent has children and is the current
        //let's try to find out if there is already a mapping that is good
        for (int i = 0; i < columns.size(); ++i) {
            QAbstractItemView *view = columns.at(i);
            if (view->rootIndex() == parent) {
                if (view == previewColumn) {
                    //let's recreate the parent
                    closeColumns(parent, false);
                    createColumn(parent, true /*show*/);
                }
                break;
            }
        }
    }
}

/*!
    \internal
    Place all of the columns where they belong inside of the viewport, resize as necessary.
*/
void QColumnViewPrivate::doLayout()
{
    Q_Q(QColumnView);
    if (!model || columns.isEmpty())
        return;

    int viewportHeight = viewport->height();
    int x = columns.at(0)->x();

    if (q->isRightToLeft()) {
        x = viewport->width() + q->horizontalOffset();
        for (int i = 0; i < columns.size(); ++i) {
            QAbstractItemView *view = columns.at(i);
            x -= view->width();
            if (x != view->x() || viewportHeight != view->height())
                view->setGeometry(x, 0, view->width(), viewportHeight);
        }
    } else {
        for (int i = 0; i < columns.size(); ++i) {
            QAbstractItemView *view = columns.at(i);
            int currentColumnWidth = view->width();
            if (x != view->x() || viewportHeight != view->height())
                view->setGeometry(x, 0, currentColumnWidth, viewportHeight);
            x += currentColumnWidth;
        }
    }
}

/*!
    \internal

    Draws a delegate with a > if an object has children.

    \sa {Model/View Programming}, QStyledItemDelegate
*/
void QColumnViewDelegate::paint(QPainter *painter,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    bool reverse = (option.direction == Qt::RightToLeft);
    int width = ((option.rect.height() * 2) / 3);
    // Modify the options to give us room to add an arrow
    QStyleOptionViewItem opt = option;
    if (reverse)
        opt.rect.adjust(width,0,0,0);
    else
        opt.rect.adjust(0,0,-width,0);

    if (!(index.model()->flags(index) & Qt::ItemIsEnabled)) {
        opt.showDecorationSelected = true;
        opt.state |= QStyle::State_Selected;
    }

    QStyledItemDelegate::paint(painter, opt, index);

    if (reverse)
        opt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height());
    else
        opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(),
                         width, option.rect.height());

    // Draw >
    if (index.model()->hasChildren(index)) {
        const QWidget *view = opt.widget;
        QStyle *style = view ? view->style() : QApplication::style();
        style->drawPrimitive(QStyle::PE_IndicatorColumnViewArrow, &opt, painter, view);
    }
}

QT_END_NAMESPACE

#include "moc_qcolumnview.cpp"

#endif // QT_CONFIG(columnview)
