diff --git a/src/filewidgets/CMakeLists.txt b/src/filewidgets/CMakeLists.txt --- a/src/filewidgets/CMakeLists.txt +++ b/src/filewidgets/CMakeLists.txt @@ -12,9 +12,11 @@ kpreviewwidgetbase.cpp krecentdirs.cpp defaultviewadapter.cpp - + kcolumnview.cpp + kcolumnviewgrip.cpp kdiroperator.cpp kdiroperatordetailview.cpp + kdiroperatorcolumnview.cpp kdirsortfilterproxymodel.cpp #used in combination with kdirmodel.cpp kencodingfiledialog.cpp kfilebookmarkhandler.cpp diff --git a/src/filewidgets/kcolumnview.h b/src/filewidgets/kcolumnview.h new file mode 100644 --- /dev/null +++ b/src/filewidgets/kcolumnview.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef KCOLUMNVIEW_H +#define KCOLUMNVIEW_H + +#include + +class Q_WIDGETS_EXPORT KColumnView : public QAbstractItemView { + +Q_OBJECT + Q_PROPERTY(bool resizeGripsVisible READ resizeGripsVisible WRITE setResizeGripsVisible) + +Q_SIGNALS: + void updatePreviewWidget(const QModelIndex &index); + +public: + explicit KColumnView(QWidget *parent = Q_NULLPTR); + ~KColumnView(); + + // QAbstractItemView overloads + QModelIndex indexAt(const QPoint &point) const Q_DECL_OVERRIDE; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) Q_DECL_OVERRIDE; + QSize sizeHint() const Q_DECL_OVERRIDE; + QRect visualRect(const QModelIndex &index) const Q_DECL_OVERRIDE; + void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE; + void setSelectionModel(QItemSelectionModel * selectionModel) Q_DECL_OVERRIDE; + void setRootIndex(const QModelIndex &index) Q_DECL_OVERRIDE; + void selectAll() Q_DECL_OVERRIDE; + + /* Shows all columns from the root until index and scrolls to + * the rightmost one. If index hasChildren(), a column with + * index as rootIndex is shown and scrolled to. */ + void showPathTo(const QModelIndex &index); + + // KColumnView functions + void setResizeGripsVisible(bool visible); + bool resizeGripsVisible() const; + + QWidget *previewWidget() const; + void setPreviewWidget(QWidget *widget); + + void setColumnWidths(const QList &list); + QList columnWidths() const; + +protected: + // QAbstractItemView overloads + bool isIndexHidden(const QModelIndex &index) const Q_DECL_OVERRIDE; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; + void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) Q_DECL_OVERRIDE; + QRegion visualRegionForSelection(const QItemSelection &selection) const Q_DECL_OVERRIDE; + int horizontalOffset() const Q_DECL_OVERRIDE; + int verticalOffset() const Q_DECL_OVERRIDE; + void rowsInserted(const QModelIndex &parent, int start, int end) Q_DECL_OVERRIDE; + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) Q_DECL_OVERRIDE; + void changeEvent(QEvent *event) Q_DECL_OVERRIDE; + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) Q_DECL_OVERRIDE; + + bool eventFilter(QObject *, QEvent *event) Q_DECL_OVERRIDE; + + // KColumnView functions + void scrollContentsBy(int dx, int dy) Q_DECL_OVERRIDE; + virtual QAbstractItemView* createColumn(const QModelIndex &rootIndex); + void initializeColumn(QAbstractItemView *column); + +private: + class Private; + Private * const d; + Q_DISABLE_COPY(KColumnView) + Q_PRIVATE_SLOT(d, void _q_gripMoved(int)) + Q_PRIVATE_SLOT(d, void _q_changeCurrentColumn()) + Q_PRIVATE_SLOT(d, void _q_columnsInserted(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d, void _q_iconSizeChanged(QSize)) + Q_PRIVATE_SLOT(d, void _q_clicked(const QModelIndex &)) +}; + +QT_END_NAMESPACE + +#endif // KCOLUMNVIEW_H + diff --git a/src/filewidgets/kcolumnview.cpp b/src/filewidgets/kcolumnview.cpp new file mode 100644 --- /dev/null +++ b/src/filewidgets/kcolumnview.cpp @@ -0,0 +1,1268 @@ +/** + * This file is imported from qtbase (c7ec07d40115bef849574c81d619b629af9434a9) + * and adjusted to build as part of KIO without needing any private + * headers. This was necessary as QColumnView had too many drawbacks + * and issues that could not be fixed by inheritance. + * The changes are: + * - Relay setIconSize to the child columns + * - Relay setMouseTracking to the child columns + * - Add rowsAboutToBeRemoved overload that closes to-be removed columns + * - Change scrollTo(index) to scroll to the column with index as root or + * the current preview of index if possible + * - Change scrollTo(index) to always scroll the column into the target + * position, even if an animation is pending or the column is visible, + * but in the wrong place + * - Change closeColumns to always relayout + * - Support having no previewWidget (d->previewqColumn == nullptr) + * - Install an event filter on the columns' viewports to react on a left + * click into an empty spot to clear the selection + * - Call _q_changeCurrentColumn directly instead of after the scroll + * animation finished + * - Remove show parameter of the private's createColumn method + * + * Unfortunately custom row delegates are not implemented as they are not + * easily accessible by QAbstractItemView's public API. + */ + +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "kcolumnview.h" + +#include "kcolumnview_p.h" +#include "kcolumnviewgrip_p.h" + +#include +#include +#include +#include +#include +#include + +#define ANIMATION_DURATION_MSEC 150 + +/*! + \since 4.3 + \class KColumnView + \brief The KColumnView class provides a model/view implementation of a column view. + \ingroup model-view + \ingroup advanced + \inmodule QtWidgets + + KColumnView 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 KColumnView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + KColumnView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + \image kcolumnview.png + + \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 +*/ +KColumnView::KColumnView(QWidget * parent) +: QAbstractItemView(parent), d(new Private(this)) +{ + d->initialize(); + + connect(this, SIGNAL(iconSizeChanged(QSize)), this, SLOT(_q_iconSizeChanged(QSize))); +} + +void KColumnView::Private::initialize() +{ + q->setTextElideMode(Qt::ElideMiddle); +#ifndef QT_NO_ANIMATION + currentAnimation.setDuration(ANIMATION_DURATION_MSEC); + currentAnimation.setTargetObject(q->horizontalScrollBar()); + currentAnimation.setPropertyName("value"); + currentAnimation.setEasingCurve(QEasingCurve::InOutQuad); +#endif //QT_NO_ANIMATION + delete q->itemDelegate(); + q->setItemDelegate(new KColumnViewDelegate(q)); +} + +/*! + Destroys the column view. +*/ +KColumnView::~KColumnView() +{ +} + +/*! + \property KColumnView::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 KColumnView::setResizeGripsVisible(bool visible) +{ + if (d->showResizeGrips == visible) + return; + d->showResizeGrips = visible; + for (int i = 0; i < d->columns.count(); ++i) { + QAbstractItemView *view = d->columns[i]; + if (visible) { + KColumnViewGrip *grip = new KColumnViewGrip(view); + view->setCornerWidget(grip); + connect(grip, SIGNAL(gripMoved(int)), this, SLOT(_q_gripMoved(int))); + } else { + QWidget *widget = view->cornerWidget(); + view->setCornerWidget(0); + widget->deleteLater(); + } + } +} + +bool KColumnView::resizeGripsVisible() const +{ + return d->showResizeGrips; +} + +/*! + \reimp +*/ +void KColumnView::setModel(QAbstractItemModel *model) +{ + if (model == this->model()) + return; + + if (this->model()) { + disconnect(this->model(), 0, this, 0); + d->closeColumns(); + } + + QAbstractItemView::setModel(model); + + connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(_q_columnsInserted(QModelIndex,int,int))); +} + +/*! + \reimp +*/ +void KColumnView::setRootIndex(const QModelIndex &index) +{ + if (!model()) + return; + + d->closeColumns(); + Q_ASSERT(d->columns.count() == 0); + + QAbstractItemView *view = d->createColumn(index); + if (view) { + if (view->selectionModel()) { + view->selectionModel()->deleteLater(); + } + + if (view->model()) { + view->setSelectionModel(selectionModel()); + } + } + + QAbstractItemView::setRootIndex(index); + d->updateScrollbars(); +} + +/*! + \reimp +*/ +bool KColumnView::isIndexHidden(const QModelIndex &index) const +{ + Q_UNUSED(index); + return false; +} + +/*! + \reimp +*/ +QModelIndex KColumnView::indexAt(const QPoint &point) const +{ + 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 KColumnView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid()) + return QRect(); + + 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 KColumnView::scrollContentsBy(int dx, int dy) +{ + if (d->columns.isEmpty() || dx == 0) + return; + + dx = isRightToLeft() ? -dx : dx; + for (int i = 0; i < d->columns.count(); ++i) + d->columns.at(i)->move(d->columns.at(i)->x() + dx, 0); + d->offset += dx; + QAbstractItemView::scrollContentsBy(dx, dy); +} + +/*! + \reimp +*/ +void KColumnView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ +#ifndef QT_NO_ANIMATION + d->currentAnimation.stop(); +#endif //QT_NO_ANIMATION + + // Fill up what is needed to get to index + if (index.isValid()) { + d->closeColumns(index, true); + } + + // Nothing to do + if (d->columns.isEmpty()) { + return; + } + + int targetColumn = -1; + + // Find the column we want to focus on + for (int i = d->columns.count() - 1; i >= 0; --i) { + auto column = d->columns[i]; + + // If the index is shown in the preview, it's the target + if (column == d->previewColumn) { + if (index == currentIndex()) { + targetColumn = i; + break; + } + + continue; + } + + // Otherwise it's the column with rootIndex() == index + // or the column that contains index + if (d->columns.at(i)->rootIndex() == index + || d->columns.at(i)->rootIndex() == index.parent()) { + targetColumn = i; + break; + } + } + + // Not found? + if (targetColumn == -1) { + return; + } + + { + // Scroll all the columns to make the path visible + int containerColumn = targetColumn; + QModelIndex scrollIndex = index; + if (d->columns[containerColumn]->rootIndex() == index) { + // Scroll the column with index as root to the top + d->columns.at(containerColumn)->scrollToTop(); + containerColumn -= 1; + } + + while (containerColumn >= 0) { + // scrollTo in the container of index itself, setCurrentIndex for the path items + if (containerColumn == targetColumn) { + d->columns.at(containerColumn)->scrollTo(index, hint); + } else { + if(d->columns.at(containerColumn)->currentIndex() != scrollIndex) { + d->columns.at(containerColumn)->setCurrentIndex(scrollIndex); + } + } + scrollIndex = scrollIndex.parent(); + containerColumn -= 1; + } + } + + int newScrollbarValue = 0; + + if (isLeftToRight()) { + newScrollbarValue = d->columns.at(targetColumn)->x() + d->columns.at(targetColumn)->width() - viewport()->width(); + } else { + newScrollbarValue = d->columns.at(targetColumn)->x() + viewport()->width(); + } + + newScrollbarValue += horizontalScrollBar()->value(); + newScrollbarValue = qMax(0, newScrollbarValue); + +#ifndef QT_NO_ANIMATION + if (style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) { + d->currentAnimation.setEndValue(newScrollbarValue); + d->currentAnimation.start(); + } else +#endif //QT_NO_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 KColumnView::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 KColumnView::resizeEvent(QResizeEvent *event) +{ + 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 KColumnView::Private::updateScrollbars() +{ +#ifndef QT_NO_ANIMATION + if (currentAnimation.state() == QPropertyAnimation::Running) + return; +#endif //QT_NO_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 = q->viewport()->size(); + auto hbar = q->horizontalScrollBar(); + 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 KColumnView::horizontalOffset() const +{ + return d->offset; +} + +/*! + \reimp +*/ +int KColumnView::verticalOffset() const +{ + return 0; +} + +/*! + \reimp +*/ +QRegion KColumnView::visualRegionForSelection(const QItemSelection &selection) const +{ + int ranges = selection.count(); + + 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 KColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_UNUSED(rect); + Q_UNUSED(command); +} + +/*! + \reimp +*/ +void KColumnView::setSelectionModel(QItemSelectionModel *newSelectionModel) +{ + 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 KColumnView::sizeHint() const +{ + 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 KColumnView::Private::_q_gripMoved(int offset) +{ + 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 KColumnView::Private::closeColumns(const QModelIndex &parent, bool build) +{ + if (columns.isEmpty()) + return; + + bool clearAll = !parent.isValid(); + bool passThroughRoot = false; + + QVector 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 (q->rootIndex() == 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 = (!q->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(); + } + + if (columns.isEmpty()) { + offset = 0; + updateScrollbars(); + } + + // Now fill in missing columns + while (!dirsToAppend.isEmpty()) { + QAbstractItemView *newView = createColumn(dirsToAppend.takeLast()); + if (!dirsToAppend.isEmpty()) + newView->setCurrentIndex(dirsToAppend.constLast()); + } + + if (build && !alreadyExists) { + createColumn(parent); + } + + doLayout(); + updateScrollbars(); + _q_changeCurrentColumn(); +} + +void KColumnView::Private::_q_clicked(const QModelIndex &index) +{ + QModelIndex parent = index.parent(); + QAbstractItemView *columnClicked = 0; + for (int column = 0; column < columns.count(); ++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. If index has no children and no previewWidget is set, + it will return nullptr; + + \sa createColumn(), setPreviewWidget() + \sa doLayout() +*/ +QAbstractItemView *KColumnView::Private::createColumn(const QModelIndex &index) +{ + QAbstractItemView *view = 0; + auto viewport = q->viewport(); + if (q->model()->hasChildren(index)) { + view = q->createColumn(index); + q->connect(view, SIGNAL(clicked(QModelIndex)), + q, SLOT(_q_clicked(QModelIndex))); + } else { + if (!previewColumn) { + return nullptr; + } + view = previewColumn; + view->setMinimumWidth(qMax(view->minimumWidth(), previewWidget->minimumWidth())); + } + + q->connect(view, SIGNAL(activated(QModelIndex)), + q, SIGNAL(activated(QModelIndex))); + q->connect(view, SIGNAL(clicked(QModelIndex)), + q, SIGNAL(clicked(QModelIndex))); + q->connect(view, SIGNAL(doubleClicked(QModelIndex)), + q, SIGNAL(doubleClicked(QModelIndex))); + q->connect(view, SIGNAL(entered(QModelIndex)), + q, SIGNAL(entered(QModelIndex))); + q->connect(view, SIGNAL(pressed(QModelIndex)), + q, SIGNAL(pressed(QModelIndex))); + + view->setFocusPolicy(Qt::NoFocus); + view->setParent(viewport); + Q_ASSERT(view); + + // Setup corner grip + if (showResizeGrips) { + KColumnViewGrip *grip = new KColumnViewGrip(view); + view->setCornerWidget(grip); + q->connect(grip, SIGNAL(gripMoved(int)), q, SLOT(_q_gripMoved(int))); + } + + if (columnSizes.count() > columns.count()) { + view->setGeometry(0, 0, columnSizes.at(columns.count()), 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.count(), columns.count() + 1)); + columnSizes[columns.count()] = initialWidth; + } + if (!columns.isEmpty() && columns.constLast()->isHidden()) + columns.constLast()->setVisible(true); + + columns.append(view); + doLayout(); + updateScrollbars(); + view->setVisible(true); + return view; +} + +/*! + \fn void KColumnView::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. KColumnView will automatically take ownership of the widget. + + \sa setPreviewWidget() + */ +QAbstractItemView *KColumnView::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(). + + \since 4.4 + \sa createColumn() + */ +void KColumnView::initializeColumn(QAbstractItemView *column) +{ + column->setFrameShape(QFrame::NoFrame); + column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + column->setMinimumWidth(100); + column->setAttribute(Qt::WA_MacShowFocusRect, false); + column->viewport()->installEventFilter(this); + +#ifndef QT_NO_DRAGANDDROP + column->setDragDropMode(dragDropMode()); + column->setDragDropOverwriteMode(dragDropOverwriteMode()); + column->setDropIndicatorShown(showDropIndicator()); +#endif + column->setAlternatingRowColors(alternatingRowColors()); + column->setAutoScroll(hasAutoScroll()); + column->setEditTriggers(editTriggers()); + column->setHorizontalScrollMode(horizontalScrollMode()); + column->setMouseTracking(hasMouseTracking()); + 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(itemDelegate()); + delete delegate; +} + +/*! + Returns the preview widget, or 0 if there is none. + + \sa setPreviewWidget(), updatePreviewWidget() +*/ +QWidget *KColumnView::previewWidget() const +{ + 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 KColumnView::setPreviewWidget(QWidget *widget) +{ + d->setPreviewWidget(widget); +} + +/*! + \internal +*/ +void KColumnView::Private::setPreviewWidget(QWidget *widget) +{ + if (previewColumn) { + if (!columns.isEmpty() && columns.constLast() == previewColumn) + columns.removeLast(); + previewColumn->deleteLater(); + previewColumn = nullptr; + } + + if (!widget) { + return; + } + + KColumnViewPreviewColumn *column = new KColumnViewPreviewColumn(q); + column->setPreviewWidget(widget); + previewColumn = column; + previewColumn->hide(); + previewColumn->setFrameShape(QFrame::NoFrame); + previewColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + previewColumn->setSelectionMode(QAbstractItemView::NoSelection); + previewColumn->setMinimumWidth(qMax(previewColumn->verticalScrollBar()->width(), + previewColumn->minimumWidth())); + previewWidget = widget; + previewWidget->setParent(previewColumn->viewport()); +} + +/*! + 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 KColumnView::setColumnWidths(const QList &list) +{ + int i = 0; + const int listCount = list.count(); + const int count = qMin(listCount, d->columns.count()); + 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 KColumnView::columnWidths() const +{ + QList list; + const int columnCount = d->columns.count(); + list.reserve(columnCount); + for (int i = 0; i < columnCount; ++i) + list.append(d->columnSizes.at(i)); + return list; +} + +/*! + \reimp +*/ +void KColumnView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + QAbstractItemView::rowsInserted(parent, start, end); + d->checkColumnCreation(parent); +} + +/*! + \reimp +*/ +void KColumnView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + 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); + } + break; + } + } + } + if (!found) + d->closeColumns(current, true); + + if (!model()->hasChildren(current)) + emit updatePreviewWidget(current); + + QAbstractItemView::currentChanged(current, previous); +} + +void KColumnView::changeEvent(QEvent *event) +{ + if (event->type() != QEvent::MouseTrackingChange) + return; + + for (auto *column : d->columns) { + column->setMouseTracking(hasMouseTracking()); + } +} + +void KColumnView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + // Columns may need to be closed + for (int i = 0; i < d->columns.count(); ++i) { + auto column = d->columns[i]; + if (column->rootIndex().parent() == parent + && column->rootIndex().row() >= start && column->rootIndex().row() <= end) { + d->closeColumns(parent, true); + } + } + + QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); +} + +bool KColumnView::eventFilter(QObject *obj, QEvent *event) +{ + // Clear the selection if clicking on an empty spot without + // having ctrl or shift pressed + if (event->type() == QEvent::MouseButtonPress) { + auto column = qobject_cast(obj->parent()); + if (!column) { + return false; + } + + const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); + if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) { + const QModelIndex index = column->indexAt(static_cast(event)->pos()); + if (!index.isValid()) { + clearSelection(); + } + } + } + + return false; +} + +/* + We have change the current column and need to update focus and selection models + on the new current column. +*/ +void KColumnView::Private::_q_changeCurrentColumn() +{ + if (columns.isEmpty()) + return; + + QModelIndex current = q->currentIndex(); + + // Set up the "current" column with focus + int currentColumn = qMax(0, columns.size() - 1); + if (currentColumn != 0 && columns.constLast() == previewColumn) + --currentColumn; + + 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()) { + columns.constLast()->setVisible(true); + } + if (columns.constLast() == previewColumn && columns.constLast()->selectionModel()) + columns.constLast()->selectionModel()->clear(); + updateScrollbars(); +} + +/*! + \reimp +*/ +void KColumnView::selectAll() +{ + if (!model() || !selectionModel()) + return; + + QModelIndexList indexList = selectionModel()->selectedIndexes(); + QModelIndex parent = rootIndex(); + QItemSelection selection; + if (indexList.count() >= 1) + parent = indexList.at(0).parent(); + if (indexList.count() == 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); +} + +void KColumnView::showPathTo(const QModelIndex &index) +{ + // Most of the work is done by the currentChanged method, + // but it is not called for the root index (QModelIndex()), + // so we need to handle that special case here. + if (!index.isValid()) { + // Root + + while (d->columns.count() > 1) { + auto c = d->columns.constLast(); + if (c == d->previewColumn) { + break; + } + + d->columns.removeLast(); + c->setVisible(false); + delete c; + } + + setCurrentIndex(index); + scrollTo(index); + } else if (currentIndex() != index) { + setCurrentIndex(index); + } +} + +/* + * private object implementation + */ +KColumnView::Private::Private(KColumnView *parent) +: q(parent) +,showResizeGrips(true) +,offset(0) +,previewWidget(0) +,previewColumn(0) +{ +} + +KColumnView::Private::~Private() +{ +} + +/*! + \internal + + */ +void KColumnView::Private::_q_columnsInserted(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + + checkColumnCreation(parent); +} + +void KColumnView::Private::_q_iconSizeChanged(QSize size) +{ + for (auto column : columns) + column->setIconSize(size); +} + +/*! + \internal + + Makes sure we create a corresponding column as a result of changing the model. + + */ +void KColumnView::Private::checkColumnCreation(const QModelIndex &parent) +{ + if (parent == q->currentIndex() && q->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.count(); ++i) { + QAbstractItemView *view = columns.at(i); + if (view->rootIndex() == parent) { + if (view == previewColumn) { + //let's recreate the parent + closeColumns(parent, false); + createColumn(parent); + } + break; + } + } + } +} + +/*! + \internal + Place all of the columns where they belong inside of the viewport, resize as necessary. +*/ +void KColumnView::Private::doLayout() +{ + if (!q->model() || columns.isEmpty()) + return; + + auto viewport = q->viewport(); + 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}, QItemDelegate +*/ +void KColumnViewDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + drawBackground(painter, option, index ); + + 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; + } + + QItemDelegate::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); + } +} + +#include "moc_kcolumnview.cpp" diff --git a/src/filewidgets/kcolumnview_p.h b/src/filewidgets/kcolumnview_p.h new file mode 100644 --- /dev/null +++ b/src/filewidgets/kcolumnview_p.h @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef KCOLUMNVIEW_P_H +#define KCOLUMNVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "kcolumnview.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_REQUIRE_CONFIG(columnview); + +QT_BEGIN_NAMESPACE + +class KColumnViewPreviewColumn : public QAbstractItemView { + +public: + explicit KColumnViewPreviewColumn(QWidget *parent) : QAbstractItemView(parent), previewWidget(0) { + } + + void setPreviewWidget(QWidget *widget) { + previewWidget = widget; + setMinimumWidth(previewWidget->minimumWidth()); + } + + void resizeEvent(QResizeEvent * event) Q_DECL_OVERRIDE{ + if (!previewWidget) + return; + previewWidget->resize( + qMax(previewWidget->minimumWidth(), event->size().width()), + previewWidget->height()); + QSize p = viewport()->size(); + QSize v = previewWidget->size(); + horizontalScrollBar()->setRange(0, v.width() - p.width()); + horizontalScrollBar()->setPageStep(p.width()); + verticalScrollBar()->setRange(0, v.height() - p.height()); + verticalScrollBar()->setPageStep(p.height()); + + QAbstractScrollArea::resizeEvent(event); + } + + void scrollContentsBy(int dx, int dy) Q_DECL_OVERRIDE + { + if (!previewWidget) + return; + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); + + QAbstractItemView::scrollContentsBy(dx, dy); + } + + QRect visualRect(const QModelIndex &) const Q_DECL_OVERRIDE + { + return QRect(); + } + void scrollTo(const QModelIndex &, ScrollHint) Q_DECL_OVERRIDE + { + } + QModelIndex indexAt(const QPoint &) const Q_DECL_OVERRIDE + { + return QModelIndex(); + } + QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers) Q_DECL_OVERRIDE + { + return QModelIndex(); + } + int horizontalOffset () const Q_DECL_OVERRIDE { + return 0; + } + int verticalOffset () const Q_DECL_OVERRIDE { + return 0; + } + QRegion visualRegionForSelection(const QItemSelection &) const Q_DECL_OVERRIDE + { + return QRegion(); + } + bool isIndexHidden(const QModelIndex &) const Q_DECL_OVERRIDE + { + return false; + } + void setSelection(const QRect &, QItemSelectionModel::SelectionFlags) Q_DECL_OVERRIDE + { + } +private: + QWidget *previewWidget; +}; + +class Q_AUTOTEST_EXPORT KColumnView::Private +{ +public: + Private(KColumnView *parent); + ~Private(); + void initialize(); + + QAbstractItemView *createColumn(const QModelIndex &index); + + void updateScrollbars(); + void closeColumns(const QModelIndex &parent = QModelIndex(), bool build = false); + void doLayout(); + void setPreviewWidget(QWidget *widget); + void checkColumnCreation(const QModelIndex &parent); + + + void _q_gripMoved(int offset); + void _q_changeCurrentColumn(); + void _q_clicked(const QModelIndex &index); + void _q_columnsInserted(const QModelIndex &parent, int start, int end); + void _q_iconSizeChanged(QSize size); + + KColumnView * const q; + + QList columns; + QVector columnSizes; // used during init and corner moving + bool showResizeGrips; + int offset; +#ifndef QT_NO_ANIMATION + QPropertyAnimation currentAnimation; +#endif + QWidget *previewWidget; + QAbstractItemView *previewColumn; +}; + +/*! + * This is a delegate that will paint the triangle + */ +class KColumnViewDelegate : public QItemDelegate +{ + +public: + explicit KColumnViewDelegate(QObject *parent = 0) : QItemDelegate(parent) {} + ~KColumnViewDelegate() {} + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif //KCOLUMNVIEW_P_H diff --git a/src/filewidgets/kcolumnviewgrip.cpp b/src/filewidgets/kcolumnviewgrip.cpp new file mode 100644 --- /dev/null +++ b/src/filewidgets/kcolumnviewgrip.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "kcolumnviewgrip_p.h" +#include +#include +#include +#include +#include + +class KColumnViewGrip::Private +{ +public: + int originalXLocation = -1; +}; + +/* + \internal + class KColumnViewGrip + + KColumnViewGrip is created to go inside QAbstractScrollArea's corner. + When the mouse it moved it will resize the scroll area and emit's a signal. + */ + +/* + \internal + \fn void KColumnViewGrip::gripMoved() + Signal that is emitted when the grip moves the parent widget. + */ + +/*! + Creates a new KColumnViewGrip with the given \a parent to view a model. + Use setModel() to set the model. +*/ +KColumnViewGrip::KColumnViewGrip(QWidget *parent) +: QWidget(parent), d(new Private) +{ +#ifndef QT_NO_CURSOR + setCursor(Qt::SplitHCursor); +#endif +} + +/*! + Destroys the view. +*/ +KColumnViewGrip::~KColumnViewGrip() +{ +} + +/*! + Attempt to resize the parent object by \a offset + returns the amount of offset that it was actually able to resized +*/ +int KColumnViewGrip::moveGrip(int offset) +{ + QWidget *parentWidget = (QWidget*)parent(); + + // first resize the parent + int oldWidth = parentWidget->width(); + int newWidth = oldWidth; + if (isRightToLeft()) + newWidth -= offset; + else + newWidth += offset; + newWidth = qMax(parentWidget->minimumWidth(), newWidth); + parentWidget->resize(newWidth, parentWidget->height()); + + // Then have the view move the widget + int realOffset = parentWidget->width() - oldWidth; + int oldX = parentWidget->x(); + if (realOffset != 0) + emit gripMoved(realOffset); + if (isRightToLeft()) + realOffset = -1 * (oldX - parentWidget->x()); + return realOffset; +} + +/*! + \reimp +*/ +void KColumnViewGrip::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + QStyleOption opt; + opt.initFrom(this); + style()->drawControl(QStyle::CE_ColumnViewGrip, &opt, &painter, this); + event->accept(); +} + +/*! + \reimp + Resize the parent window to the sizeHint +*/ +void KColumnViewGrip::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + QWidget *parentWidget = (QWidget*)parent(); + int offset = parentWidget->sizeHint().width() - parentWidget->width(); + if (isRightToLeft()) + offset *= -1; + moveGrip(offset); + event->accept(); +} + +/*! + \reimp + Begin watching for mouse movements +*/ +void KColumnViewGrip::mousePressEvent(QMouseEvent *event) +{ + d->originalXLocation = event->globalX(); + event->accept(); +} + +/*! + \reimp + Calculate the movement of the grip and moveGrip() and emit gripMoved +*/ +void KColumnViewGrip::mouseMoveEvent(QMouseEvent *event) +{ + int offset = event->globalX() - d->originalXLocation; + d->originalXLocation = moveGrip(offset) + d->originalXLocation; + event->accept(); +} + +/*! + \reimp + Stop watching for mouse movements +*/ +void KColumnViewGrip::mouseReleaseEvent(QMouseEvent *event) +{ + d->originalXLocation = -1; + event->accept(); +} + +#include "moc_kcolumnviewgrip_p.cpp" diff --git a/src/filewidgets/kcolumnviewgrip_p.h b/src/filewidgets/kcolumnviewgrip_p.h new file mode 100644 --- /dev/null +++ b/src/filewidgets/kcolumnviewgrip_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef KCOLUMNVIEWGRIP_P_H +#define KCOLUMNVIEWGRIP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +class KColumnViewGripPrivate; + +class Q_AUTOTEST_EXPORT KColumnViewGrip : public QWidget { + +Q_OBJECT + +Q_SIGNALS: + void gripMoved(int offset); + +public: + explicit KColumnViewGrip(QWidget *parent = 0); + ~KColumnViewGrip(); + int moveGrip(int offset); + +protected: + KColumnViewGrip(KColumnViewGripPrivate &, QWidget *parent = 0, Qt::WindowFlags f = 0); + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + void mouseDoubleClickEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + +private: + class Private; + Private * const d; + Q_DISABLE_COPY(KColumnViewGrip) +}; + +QT_END_NAMESPACE + +#endif //KCOLUMNVIEWGRIP_P_H diff --git a/src/filewidgets/kdiroperator.h b/src/filewidgets/kdiroperator.h --- a/src/filewidgets/kdiroperator.h +++ b/src/filewidgets/kdiroperator.h @@ -774,6 +774,11 @@ */ void setIconsZoom(int value); + /** + * Only used for column views. + */ + void scrollToUrl(const QUrl &url); + protected Q_SLOTS: /** * Restores the normal cursor after showing the busy-cursor. Also hides @@ -899,6 +904,7 @@ Q_PRIVATE_SLOT(d, void _k_slotSimpleView()) Q_PRIVATE_SLOT(d, void _k_slotTreeView()) Q_PRIVATE_SLOT(d, void _k_slotDetailedTreeView()) + Q_PRIVATE_SLOT(d, void _k_slotColumnView()) Q_PRIVATE_SLOT(d, void _k_slotToggleHidden(bool)) Q_PRIVATE_SLOT(d, void _k_togglePreview(bool)) Q_PRIVATE_SLOT(d, void _k_toggleInlinePreviews(bool)) diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp --- a/src/filewidgets/kdiroperator.cpp +++ b/src/filewidgets/kdiroperator.cpp @@ -23,6 +23,7 @@ #include #include "kdirmodel.h" #include "kdiroperatordetailview_p.h" +#include "kdiroperatorcolumnview_p.h" #include "kdirsortfilterproxymodel.h" #include "kfileitem.h" #include "kfilemetapreview_p.h" @@ -33,6 +34,7 @@ #include // ConfigGroup, DefaultShowHidden, DefaultDirsFirst, DefaultSortReversed #include +#include #include #include #include @@ -194,6 +196,7 @@ void _k_slotSimpleView(); void _k_slotTreeView(); void _k_slotDetailedTreeView(); + void _k_slotColumnView(); void _k_slotToggleHidden(bool); void _k_togglePreview(bool); void _k_toggleInlinePreviews(bool); @@ -579,7 +582,7 @@ KFile::FileView KDirOperator::Private::allViews() { - return static_cast(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree); + return static_cast(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree | KFile::ColumnView); } void KDirOperator::Private::_k_slotDetailedView() @@ -606,6 +609,12 @@ parent->setView(view); } +void KDirOperator::Private::_k_slotColumnView() +{ + KFile::FileView view = static_cast((viewKind & ~allViews()) | KFile::ColumnView); + parent->setView(view); +} + void KDirOperator::Private::_k_slotToggleHidden(bool show) { dirLister->setShowingDotFiles(show); @@ -907,6 +916,17 @@ emit currentIconSizeChanged(value); } +void KDirOperator::scrollToUrl(const QUrl &url) +{ + if (!KFile::isColumnView(viewMode())) + return; + + const KFileItem item = d->dirLister->findByUrl(url); + const QModelIndex index = d->dirModel->indexForItem(item); + const QModelIndex _index = d->proxyModel->mapFromSource(index); + d->itemView->scrollTo(_index); +} + void KDirOperator::close() { resetCursor(); @@ -1057,6 +1077,32 @@ bool KDirOperator::Private::openUrl(const QUrl &url, KDirLister::OpenUrlFlags flags) { + if (KFile::isColumnView(parent->viewMode())) { + QUrl root = url; + root.setPath(QStringLiteral("/")); + if (dirLister->url() != root || flags & KDirLister::Reload) { + dirLister->clear(); + dirLister->openUrl(root); + } + + auto urlIndex = dirModel->indexForUrl(url); + if (urlIndex.isValid() || url == root) { + auto columnView = qobject_cast(itemView); + columnView->showPathTo(proxyModel->mapFromSource(urlIndex)); + _k_slotIOFinished(); + } else { + if (!KProtocolManager::supportsListing(url)) { + _k_slotCanceled(); + return false; + } + + // Calls openURL internally + dirModel->expandToUrl(url); + } + + return true; + } + const bool result = KProtocolManager::supportsListing(url) && dirLister->openUrl(url, flags); if (!result) { // in that case, neither completed() nor canceled() will be emitted by KDL _k_slotCanceled(); @@ -1429,6 +1475,8 @@ KDirOperatorDetailView *detailView = new KDirOperatorDetailView(parent); detailView->setViewMode(viewKind); itemView = detailView; + } else if (KFile::isColumnView(viewKind)) { + itemView = new KDirOperatorColumnView(parent); } else { itemView = new KDirOperatorIconView(this, parent); } @@ -1463,6 +1511,8 @@ viewKind = KFile::Tree; } else if (KFile::isDetailTreeView((KFile::FileView)d->defaultView)) { viewKind = KFile::DetailTree; + } else if (KFile::isColumnView((KFile::FileView)d->defaultView)) { + viewKind = KFile::ColumnView; } else { viewKind = KFile::Simple; } @@ -1520,7 +1570,14 @@ // TODO: do a real timer and restart it after that d->pendingMimeTypes.clear(); - const bool listDir = (d->itemView == nullptr); + bool listDir = d->itemView == nullptr; + + if (!listDir) { + const bool wasColumnView = qobject_cast(d->itemView) || qobject_cast(d->itemView); + const bool isColumnView = qobject_cast(view) || qobject_cast(view); + // Column Views need a different representation of the directory tree, with the root node being the filesystem root and not an arbitrary changing directory. So reload if the requirement changes. + listDir = wasColumnView != isColumnView; + } if (d->mode & KFile::Files) { view->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -1540,18 +1597,24 @@ setFocusProxy(nullptr); delete d->itemView; d->itemView = view; - d->itemView->setModel(d->proxyModel); - setFocusProxy(d->itemView); - - view->viewport()->installEventFilter(this); + /* QColumnView/KColumnView do not react correctly to ItemDelegate changes after colums got created + * (it can't, the delegate is not shared between the child list views and there is no + * notification mechanism), so make sure the the delegate is the right one + * before assigning a model. + */ KFileItemDelegate *delegate = new KFileItemDelegate(d->itemView); d->itemView->setItemDelegate(delegate); d->itemView->viewport()->setAttribute(Qt::WA_Hover); d->itemView->setContextMenuPolicy(Qt::CustomContextMenu); d->itemView->setMouseTracking(true); //d->itemView->setDropOptions(d->dropOptions); + d->itemView->setModel(d->proxyModel); + setFocusProxy(d->itemView); + + view->viewport()->installEventFilter(this); + // first push our settings to the view, then listen for changes from the view QTreeView *treeView = qobject_cast(d->itemView); if (treeView) { @@ -1595,7 +1658,7 @@ // needs to be done here, and not in createView, since we can be set an external view d->decorationMenu->setEnabled(qobject_cast(d->itemView)); - d->shouldFetchForItems = qobject_cast(view); + d->shouldFetchForItems = qobject_cast(view) || KFile::isColumnView(viewMode()); if (d->shouldFetchForItems) { connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex))); } else { @@ -1641,7 +1704,7 @@ d->dirModel->setDirLister(d->dirLister); d->dirModel->setDropsAllowed(KDirModel::DropOnDirectory); - d->shouldFetchForItems = qobject_cast(d->itemView); + d->shouldFetchForItems = qobject_cast(d->itemView) || KFile::isColumnView(viewMode()); if (d->shouldFetchForItems) { connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex))); } else { @@ -1932,11 +1995,17 @@ detailedTreeAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); connect(detailedTreeAction, SIGNAL(triggered()), SLOT(_k_slotDetailedTreeView())); + KToggleAction *columnViewAction = new KToggleAction(i18n("Column View"), this); + d->actionCollection->addAction(QStringLiteral("column view"), columnViewAction); + columnViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-file-columns"))); + connect(columnViewAction, SIGNAL(triggered()), SLOT(_k_slotColumnView())); + QActionGroup *viewGroup = new QActionGroup(this); shortAction->setActionGroup(viewGroup); detailedAction->setActionGroup(viewGroup); treeAction->setActionGroup(viewGroup); detailedTreeAction->setActionGroup(viewGroup); + columnViewAction->setActionGroup(viewGroup); KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Files"), this); d->actionCollection->addAction(QStringLiteral("show hidden"), showHiddenAction); @@ -1971,6 +2040,7 @@ // Comment following lines to hide the extra two modes viewMenu->addAction(treeAction); viewMenu->addAction(detailedTreeAction); + viewMenu->addAction(columnViewAction); // TODO: QAbstractItemView does not offer an action collection. Provide // an interface to add a custom action collection. @@ -2071,6 +2141,7 @@ d->actionCollection->action(QStringLiteral("detailed view"))->setChecked(KFile::isDetailView(fv)); d->actionCollection->action(QStringLiteral("tree view"))->setChecked(KFile::isTreeView(fv)); d->actionCollection->action(QStringLiteral("detailed tree view"))->setChecked(KFile::isDetailTreeView(fv)); + d->actionCollection->action(QStringLiteral("column view"))->setChecked(KFile::isColumnView(fv)); } void KDirOperator::readConfig(const KConfigGroup &configGroup) @@ -2083,6 +2154,8 @@ d->defaultView |= KFile::Tree; } else if (viewStyle == QLatin1String("DetailTree")) { d->defaultView |= KFile::DetailTree; + } else if (viewStyle == QLatin1String("Columns")) { + d->defaultView |= KFile::ColumnView; } else { d->defaultView |= KFile::Simple; } @@ -2184,6 +2257,8 @@ style = QStringLiteral("Tree"); } else if (KFile::isDetailTreeView(fv)) { style = QStringLiteral("DetailTree"); + } else if (KFile::isColumnView(fv)) { + style = QStringLiteral("Columns"); } configGroup.writeEntry(QStringLiteral("View Style"), style); @@ -2266,6 +2341,9 @@ void KDirOperator::Private::_k_slotIOFinished() { + if (itemView && KFile::isColumnView(parent->viewMode())) + qobject_cast(itemView)->showPathTo(proxyModel->mapFromSource(dirModel->indexForUrl(currUrl))); + progressDelayTimer->stop(); _k_slotProgress(100); progressBar->hide(); @@ -2493,10 +2571,6 @@ { QTreeView *treeView = qobject_cast(itemView); - if (!treeView) { - return; - } - const KFileItem item = dirModel->itemForIndex(index); if (item.isNull()) { @@ -2514,11 +2588,12 @@ if (!_item.isNull() && _item.isDir()) { const QModelIndex _index = dirModel->indexForItem(_item); const QModelIndex _proxyIndex = proxyModel->mapFromSource(_index); - treeView->expand(_proxyIndex); + if (treeView) + treeView->expand(_proxyIndex); // if we have expanded the last parent of this item, select it if (item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash)) { - treeView->selectionModel()->select(proxyIndex, QItemSelectionModel::Select); + itemView->selectionModel()->select(proxyIndex, QItemSelectionModel::Select); } } it = itemsToBeSetAsCurrent.erase(it); diff --git a/src/filewidgets/kdiroperatorcolumnview.cpp b/src/filewidgets/kdiroperatorcolumnview.cpp new file mode 100644 --- /dev/null +++ b/src/filewidgets/kdiroperatorcolumnview.cpp @@ -0,0 +1,55 @@ +/***************************************************************************** + * Copyright (C) 2017 by Fabian Vogt * + * Copyright (C) 2007 by Peter Penz * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License version 2 as published by the Free Software Foundation. * + * * + * This library 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 * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#include "kdiroperatorcolumnview_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KDirOperatorColumnView::KDirOperatorColumnView(QWidget *parent) : + KColumnView(parent) +{ + setDragDropMode(QAbstractItemView::DragOnly); + setSelectionBehavior(QAbstractItemView::SelectRows); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setResizeGripsVisible(false); + setPreviewWidget(nullptr); +} + +KDirOperatorColumnView::~KDirOperatorColumnView() +{ +} + +void KDirOperatorColumnView::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } +} diff --git a/src/filewidgets/kdiroperatorcolumnview_p.h b/src/filewidgets/kdiroperatorcolumnview_p.h new file mode 100644 --- /dev/null +++ b/src/filewidgets/kdiroperatorcolumnview_p.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * Copyright (C) 2017 by Fabian Vogt * + * Copyright (C) 2007 by Peter Penz * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License version 2 as published by the Free Software Foundation. * + * * + * This library 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 * + * Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public License * + * along with this library; see the file COPYING.LIB. If not, write to * + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02110-1301, USA. * + *****************************************************************************/ + +#ifndef KDIROPERATORCOLUMNVIEW_P_H +#define KDIROPERATORCOLUMNVIEW_P_H + +#include "kcolumnview.h" + +#include + +class QAbstractItemModel; +class KDirOperatorColumnViewPrivate; + +class KDirOperatorColumnView : public KColumnView +{ + Q_OBJECT + +public: + KDirOperatorColumnView(QWidget *parent = nullptr); + virtual ~KDirOperatorColumnView(); + +protected: + void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE; +}; + +#endif diff --git a/src/widgets/delegateanimationhandler.cpp b/src/widgets/delegateanimationhandler.cpp --- a/src/widgets/delegateanimationhandler.cpp +++ b/src/widgets/delegateanimationhandler.cpp @@ -53,8 +53,13 @@ // --------------------------------------------------------------------------- -CachedRendering::CachedRendering(QStyle::State state, const QSize &size, QModelIndex index, qreal devicePixelRatio) - : state(state), regular(QPixmap(size*devicePixelRatio)), hover(QPixmap(size*devicePixelRatio)), valid(true), validityIndex(index) +CachedRendering::CachedRendering(QStyle::State state, const QSize &size, QModelIndex index, bool columnView, qreal devicePixelRatio) + : state(state), + regular(QPixmap(size*devicePixelRatio)), + hover(QPixmap(size*devicePixelRatio)), + columnView(columnView), + valid(true), + validityIndex(index) { regular.setDevicePixelRatio(devicePixelRatio); hover.setDevicePixelRatio(devicePixelRatio); diff --git a/src/widgets/delegateanimationhandler_p.h b/src/widgets/delegateanimationhandler_p.h --- a/src/widgets/delegateanimationhandler_p.h +++ b/src/widgets/delegateanimationhandler_p.h @@ -41,15 +41,16 @@ Q_OBJECT public: - CachedRendering(QStyle::State state, const QSize &size, QModelIndex validityIndex, qreal devicePixelRatio = 1.0); - bool checkValidity(QStyle::State current) const + CachedRendering(QStyle::State state, const QSize &size, QModelIndex validityIndex, bool columnView, qreal devicePixelRatio = 1.0); + bool checkValidity(QStyle::State currentState, bool currentColumnView) const { - return state == current && valid; + return currentState == state && valid && currentColumnView == columnView; } QStyle::State state; QPixmap regular; QPixmap hover; + bool columnView; bool valid; QPersistentModelIndex validityIndex; diff --git a/src/widgets/kfile.h b/src/widgets/kfile.h --- a/src/widgets/kfile.h +++ b/src/widgets/kfile.h @@ -62,6 +62,7 @@ PreviewInfo = 16, Tree = 32, DetailTree = 64, + ColumnView = 128, FileViewMax = 65536 }; @@ -107,6 +108,8 @@ static bool isDetailTreeView(const FileView &view); + static bool isColumnView(const FileView &view); + private: KFile(); // forbidden }; diff --git a/src/widgets/kfile.cpp b/src/widgets/kfile.cpp --- a/src/widgets/kfile.cpp +++ b/src/widgets/kfile.cpp @@ -87,7 +87,12 @@ bool KFile::isDetailTreeView(const FileView &view) { - return (view & DetailTree) == DetailTree; + return (view & DetailTree) == ColumnView; +} + +bool KFile::isColumnView(const FileView &view) +{ + return (view & ColumnView) == ColumnView; } #include "moc_kfile.cpp" diff --git a/src/widgets/kfileitemdelegate.cpp b/src/widgets/kfileitemdelegate.cpp --- a/src/widgets/kfileitemdelegate.cpp +++ b/src/widgets/kfileitemdelegate.cpp @@ -108,6 +108,7 @@ QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) const; void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const; void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const; + void drawColumnArrow(QPainter *painter, const QStyleOptionViewItem &option, int width) const; void gotNewIcon(const QModelIndex &index); @@ -1181,6 +1182,20 @@ painter->setRenderHint(QPainter::Antialiasing); } +void KFileItemDelegate::Private::drawColumnArrow(QPainter *painter, const QStyleOptionViewItem &option, int width) const +{ + auto arrowOpt = option; + if (option.direction == Qt::RightToLeft) { + arrowOpt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height()); + } else { + arrowOpt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(), + width, option.rect.height()); + } + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_IndicatorColumnViewArrow, &arrowOpt, painter, arrowOpt.widget); +} + void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -1205,6 +1220,9 @@ const QAbstractItemView *view = qobject_cast(opt.widget); + bool isColumnView = view && view->parentWidget() && view->parentWidget()->parentWidget() + && (view->parentWidget()->parentWidget()->inherits("KColumnView") || view->parentWidget()->parentWidget()->inherits("QColumnView")); + // Check if the item is being animated // ======================================================================== KIO::AnimationState *state = d->animationState(opt, index, view); @@ -1233,7 +1251,7 @@ // If we have a cached rendering, draw the item from the cache if (cache) { - if (cache->checkValidity(opt.state) && cache->regular.size() == opt.rect.size()) { + if (cache->checkValidity(opt.state, isColumnView) && cache->regular.size() == opt.rect.size()) { QPixmap pixmap = d->transition(cache->regular, cache->hover, progress); if (state->cachedRenderingFadeFrom() && state->fadeProgress() != 1.0) { @@ -1252,7 +1270,7 @@ return; } - if (!cache->checkValidity(opt.state)) { + if (!cache->checkValidity(opt.state, isColumnView)) { if (opt.widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, opt.widget)) { // Fade over from the old icon to the new one // Only start a new fade if the previous one is ready @@ -1275,18 +1293,32 @@ // ======================================================================== const QPen pen = QPen(d->foregroundBrush(opt, index), 0); + bool drawArrow = isColumnView && index.model() && index.model()->hasChildren(index); + bool reverse = (option.direction == Qt::RightToLeft); + int arrowWidth = ((option.rect.height() * 2) / 3); + QStyleOptionViewItem textOpt = opt; + if (isColumnView) { + // Modify the options to give us room to add an arrow + if (reverse) { + textOpt.rect.adjust(arrowWidth,0,0,0); + } else { + textOpt.rect.adjust(0,0,-arrowWidth,0); + } + } + //### Apply the selection effect to the icon when the item is selected and // showDecorationSelected is false. QTextLayout labelLayout, infoLayout; QRect textBoundingRect; - d->layoutTextItems(opt, index, &labelLayout, &infoLayout, &textBoundingRect); + d->layoutTextItems(textOpt, index, &labelLayout, &infoLayout, &textBoundingRect); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); int focusHMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin); int focusVMargin = style->pixelMetric(QStyle::PM_FocusFrameVMargin); + QRect focusRect = textBoundingRect.adjusted(-focusHMargin, -focusVMargin, +focusHMargin, +focusVMargin); @@ -1301,15 +1333,18 @@ const qreal dpr = painter->device()->devicePixelRatio(); #endif - cache = new KIO::CachedRendering(opt.state, option.rect.size(), index, dpr); + cache = new KIO::CachedRendering(opt.state, option.rect.size(), index, isColumnView, dpr); QPainter p; p.begin(&cache->regular); p.translate(-option.rect.topLeft()); p.setRenderHint(QPainter::Antialiasing); p.setPen(pen); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget); p.drawPixmap(iconPos, icon); + if (drawArrow) { + d->drawColumnArrow(&p, opt, arrowWidth); + } d->drawTextItems(&p, labelLayout, infoLayout, textBoundingRect); d->drawFocusRect(&p, opt, focusRect); p.end(); @@ -1323,6 +1358,9 @@ p.setPen(pen); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget); p.drawPixmap(iconPos, icon); + if (drawArrow) { + d->drawColumnArrow(&p, opt, arrowWidth); + } d->drawTextItems(&p, labelLayout, infoLayout, textBoundingRect); d->drawFocusRect(&p, opt, focusRect); p.end(); @@ -1365,6 +1403,10 @@ style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); painter->drawPixmap(iconPos, icon); + if (drawArrow) { + d->drawColumnArrow(painter, opt, arrowWidth); + } + d->drawTextItems(painter, labelLayout, infoLayout, textBoundingRect); d->drawFocusRect(painter, opt, focusRect);