Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,117 @@
add_library(KF6ItemViews)
add_library(KF6::ItemViews ALIAS KF6ItemViews)
set_target_properties(KF6ItemViews PROPERTIES
VERSION ${KITEMVIEWS_VERSION}
SOVERSION ${KITEMVIEWS_SOVERSION}
EXPORT_NAME ItemViews
)
ecm_create_qm_loader(KF6ItemViews kitemviews6_qt)
target_sources(KF6ItemViews PRIVATE
kcategorizedsortfilterproxymodel.cpp
kcategorizedsortfilterproxymodel.h
kcategorizedsortfilterproxymodel_p.h
kcategorizedview.cpp
kcategorizedview.h
kcategorizedview_p.h
kcategorydrawer.cpp
kcategorydrawer.h
kextendableitemdelegate.cpp
kextendableitemdelegate.h
klistwidgetsearchline.cpp
klistwidgetsearchline.h
ktreewidgetsearchline.cpp
ktreewidgetsearchline.h
ktreewidgetsearchlinewidget.cpp
ktreewidgetsearchlinewidget.h
kwidgetitemdelegate.cpp
kwidgetitemdelegate.h
kwidgetitemdelegate_p.h
kwidgetitemdelegatepool.cpp
kwidgetitemdelegatepool_p.h
)
ecm_qt_declare_logging_category(KF6ItemViews
HEADER kitemviews_debug.h
IDENTIFIER KITEMVIEWS_LOG
CATEGORY_NAME kf.itemviews
DESCRIPTION "KItemViews"
EXPORT KITEMVIEWS
)
ecm_generate_export_header(KF6ItemViews
BASE_NAME KItemViews
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6ItemViews INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KItemViews>")
target_link_libraries(KF6ItemViews PUBLIC Qt6::Widgets)
ecm_generate_headers(KItemViews_HEADERS
HEADER_NAMES
KCategorizedSortFilterProxyModel
KCategorizedView
KCategoryDrawer
KExtendableItemDelegate
KListWidgetSearchLine
KTreeWidgetSearchLine
KTreeWidgetSearchLineWidget
KWidgetItemDelegate
REQUIRED_HEADERS KItemViews_HEADERS
)
install(TARGETS KF6ItemViews EXPORT KF6ItemViewsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kitemviews_export.h
${KItemViews_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KItemViews COMPONENT Devel
)
if(BUILD_DESIGNERPLUGIN)
add_subdirectory(designer)
endif()
if(BUILD_QCH)
ecm_add_qch(
KF6ItemViews_QCH
NAME KItemViews
BASE_NAME KF6ItemViews
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KItemViews_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Widgets_QCH
Qt6Gui_QCH
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KITEMVIEWS_EXPORT
KITEMVIEWS_DEPRECATED
KITEMVIEWS_DEPRECATED_EXPORT
"KITEMVIEWS_DEPRECATED_VERSION(x, y, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
ecm_qt_install_logging_categories(
EXPORT KITEMVIEWS
FILE kitemviews.categories
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Extract strings from all source files.
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
# lconvert.
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/kitemviews6_qt.pot
@@ -0,0 +1,33 @@
include(ECMAddQtDesignerPlugin)
ecm_qtdesignerplugin_widget(KCategorizedView
TOOLTIP "Categorized Item View (KF6)"
WHATSTHIS "Item view for listing items in a categorized fashion optionally."
GROUP "Views (KF6)"
)
ecm_qtdesignerplugin_widget(KListWidgetSearchLine
TOOLTIP "QListWidget Search Line (KF6)"
GROUP "Input (KF6)"
)
ecm_qtdesignerplugin_widget(KTreeWidgetSearchLine
TOOLTIP "QTreeWidget Search Line (KF6)"
GROUP "Input (KF6)"
)
ecm_qtdesignerplugin_widget(KTreeWidgetSearchLineWidget
TOOLTIP "QTreeWidget Search Line Widget (KF6)"
GROUP "Input (KF6)"
)
ecm_add_qtdesignerplugin(kitemviewswidgets
NAME KItemViewsWidgets
OUTPUT_NAME kitemviews6widgets
WIDGETS
KCategorizedView
KListWidgetSearchLine
KTreeWidgetSearchLine
KTreeWidgetSearchLineWidget
LINK_LIBRARIES
KF6::ItemViews
INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer"
COMPONENT Devel
)
@@ -0,0 +1,135 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcategorizedsortfilterproxymodel.h"
#include "kcategorizedsortfilterproxymodel_p.h"
#include <QCollator>
KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
, d(new KCategorizedSortFilterProxyModelPrivate())
{
}
KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel() = default;
void KCategorizedSortFilterProxyModel::sort(int column, Qt::SortOrder order)
{
d->sortColumn = column;
d->sortOrder = order;
QSortFilterProxyModel::sort(column, order);
}
bool KCategorizedSortFilterProxyModel::isCategorizedModel() const
{
return d->categorizedModel;
}
void KCategorizedSortFilterProxyModel::setCategorizedModel(bool categorizedModel)
{
if (categorizedModel == d->categorizedModel) {
return;
}
d->categorizedModel = categorizedModel;
invalidate();
}
int KCategorizedSortFilterProxyModel::sortColumn() const
{
return d->sortColumn;
}
Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const
{
return d->sortOrder;
}
void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison(bool sortCategoriesByNaturalComparison)
{
if (sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison) {
return;
}
d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison;
invalidate();
}
bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const
{
return d->sortCategoriesByNaturalComparison;
}
bool KCategorizedSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
if (d->categorizedModel) {
int compare = compareCategories(left, right);
if (compare > 0) { // left is greater than right
return false;
} else if (compare < 0) { // left is less than right
return true;
}
}
return subSortLessThan(left, right);
}
bool KCategorizedSortFilterProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
{
return QSortFilterProxyModel::lessThan(left, right);
}
int KCategorizedSortFilterProxyModel::compareCategories(const QModelIndex &left, const QModelIndex &right) const
{
QVariant l = (left.model() ? left.model()->data(left, CategorySortRole) : QVariant());
QVariant r = (right.model() ? right.model()->data(right, CategorySortRole) : QVariant());
Q_ASSERT(l.isValid());
Q_ASSERT(r.isValid());
Q_ASSERT(l.userType() == r.userType());
if (l.userType() == QMetaType::QString) {
QString lstr = l.toString();
QString rstr = r.toString();
if (d->sortCategoriesByNaturalComparison) {
return d->m_collator.compare(lstr, rstr);
} else {
if (lstr < rstr) {
return -1;
}
if (lstr > rstr) {
return 1;
}
return 0;
}
}
qlonglong lint = l.toLongLong();
qlonglong rint = r.toLongLong();
if (lint < rint) {
return -1;
}
if (lint > rint) {
return 1;
}
return 0;
}
#include "moc_kcategorizedsortfilterproxymodel.cpp"
@@ -0,0 +1,162 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_H
#define KCATEGORIZEDSORTFILTERPROXYMODEL_H
#include <QSortFilterProxyModel>
#include <memory>
#include <kitemviews_export.h>
class KCategorizedSortFilterProxyModelPrivate;
class QItemSelection;
/**
* @class KCategorizedSortFilterProxyModel kcategorizedsortfilterproxymodel.h KCategorizedSortFilterProxyModel
*
* This class lets you categorize a view. It is meant to be used along with
* KCategorizedView class.
*
* In general terms all you need to do is to reimplement subSortLessThan() and
* compareCategories() methods. In order to make categorization work, you need
* to also call setCategorizedModel() class to enable it, since the categorization
* is disabled by default.
*
* @see KCategorizedView
*
* @author Rafael Fernández López <ereslibre@kde.org>
*/
class KITEMVIEWS_EXPORT KCategorizedSortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum AdditionalRoles {
// Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
// to define additional roles.
CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index
CategorySortRole = 0x27857E60, ///< This role is used for sorting categories. You can return a
///< string or a long long value. Strings will be sorted alphabetically
///< while long long will be sorted by their value. Please note that this
///< value won't be shown on the view, is only for sorting purposes. What will
///< be shown as "Category" on the view will be asked with the role
///< CategoryDisplayRole.
};
KCategorizedSortFilterProxyModel(QObject *parent = nullptr);
~KCategorizedSortFilterProxyModel() override;
/**
* Overridden from QSortFilterProxyModel. Sorts the source model using
* @p column for the given @p order.
*/
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
/**
* @return whether the model is categorized or not. Disabled by default.
*/
bool isCategorizedModel() const;
/**
* Enables or disables the categorization feature.
*
* @param categorizedModel whether to enable or disable the categorization feature.
*/
void setCategorizedModel(bool categorizedModel);
/**
* @return the column being used for sorting.
*/
int sortColumn() const;
/**
* @return the sort order being used for sorting.
*/
Qt::SortOrder sortOrder() const;
/**
* Set if the sorting using CategorySortRole will use a natural comparison
* in the case that strings were returned. If enabled, QCollator
* will be used for sorting.
*
* @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not.
*/
void setSortCategoriesByNaturalComparison(bool sortCategoriesByNaturalComparison);
/**
* @return whether it is being used a natural comparison for sorting. Enabled by default.
*/
bool sortCategoriesByNaturalComparison() const;
protected:
/**
* Overridden from QSortFilterProxyModel. If you are subclassing
* KCategorizedSortFilterProxyModel, you will probably not need to reimplement this
* method.
*
* It calls compareCategories() to sort by category. If the both items are in the
* same category (i.e. compareCategories returns 0), then subSortLessThan is called.
*
* @return Returns true if the item @p left is less than the item @p right when sorting.
*
* @warning You usually won't need to reimplement this method when subclassing
* from KCategorizedSortFilterProxyModel.
*/
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
/**
* This method has a similar purpose as lessThan() has on QSortFilterProxyModel.
* It is used for sorting items that are in the same category.
*
* @return Returns true if the item @p left is less than the item @p right when sorting.
*/
virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
/**
* This method compares the category of the @p left index with the category
* of the @p right index.
*
* Internally and if not reimplemented, this method will ask for @p left and
* @p right models for role CategorySortRole. In order to correctly sort
* categories, the data() method of the model should return a qlonglong (or numeric) value, or
* a QString object. QString objects will be sorted with QString::localeAwareCompare if
* sortCategoriesByNaturalComparison() is true.
*
* @note Please have present that:
* QString(QChar(QChar::ObjectReplacementCharacter)) >
* QString(QChar(QChar::ReplacementCharacter)) >
* [ all possible strings ] >
* QString();
*
* This means that QString() will be sorted the first one, while
* QString(QChar(QChar::ObjectReplacementCharacter)) and
* QString(QChar(QChar::ReplacementCharacter)) will be sorted in last
* position.
*
* @warning Please note that data() method of the model should return always
* information of the same type. If you return a QString for an index,
* you should return always QStrings for all indexes for role CategorySortRole
* in order to correctly sort categories. You can't mix by returning
* a QString for one index, and a qlonglong for other.
*
* @note If you need a more complex layout, you will have to reimplement this
* method.
*
* @return A negative value if the category of @p left should be placed before the
* category of @p right. 0 if @p left and @p right are on the same category, and
* a positive value if the category of @p left should be placed after the
* category of @p right.
*/
virtual int compareCategories(const QModelIndex &left, const QModelIndex &right) const;
private:
std::unique_ptr<KCategorizedSortFilterProxyModelPrivate> const d;
};
#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H
@@ -0,0 +1,40 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
#include <QCollator>
#include "kcategorizedsortfilterproxymodel.h"
class KCategorizedSortFilterProxyModelPrivate
{
public:
KCategorizedSortFilterProxyModelPrivate()
: sortColumn(0)
, sortOrder(Qt::AscendingOrder)
, categorizedModel(false)
, sortCategoriesByNaturalComparison(true)
{
m_collator.setNumericMode(true);
m_collator.setCaseSensitivity(Qt::CaseSensitive);
}
~KCategorizedSortFilterProxyModelPrivate()
{
}
int sortColumn;
Qt::SortOrder sortOrder;
bool categorizedModel;
bool sortCategoriesByNaturalComparison;
QCollator m_collator;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,309 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATEGORIZEDVIEW_H
#define KCATEGORIZEDVIEW_H
#include <QListView>
#include <memory>
#include <kitemviews_export.h>
class KCategoryDrawer;
/**
* @class KCategorizedView kcategorizedview.h KCategorizedView
*
* @short Item view for listing items in a categorized fashion optionally
*
* KCategorizedView basically has the same functionality as QListView, only that it also lets you
* layout items in a way that they are categorized visually.
*
* For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer
* with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be
* flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true).
*
* The way it works (if categorization enabled):
*
* - When sorting, it does more things than QListView does. It will ask the model for the
* special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return
* a QString or an int in order to tell the view the order of categories. In this sense, for
* instance, if we are sorting by name ascending, "A" would be before than "B". If we are
* sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are
* also sorted.
*
* - When the view has to paint, it will ask the model with the role CategoryDisplayRole
* (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if
* we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example.
*
* For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own
* drawing.
*
* @note All examples cited before talk about filesystems and such, but have present that this
* is a completely generic class, and it can be used for whatever your purpose is. For
* instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In
* this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the
* same ("Mammal" and "Oviparous").
*
* @note There is a really performance boost if CategorySortRole returns an int instead of a QString.
* Have present that this role is asked (n * log n) times when sorting and compared. Comparing
* ints is always faster than comparing strings, without mattering how fast the string
* comparison is. Consider thinking of a way of returning ints instead of QStrings if your
* model can contain a high number of items.
*
* @warning Note that for really drawing items in blocks you will need some things to be done:
* - The model set to this view has to be (or inherit if you want to do special stuff
* in it) KCategorizedSortFilterProxyModel.
* - This model needs to be set setCategorizedModel to true.
* - Set a category drawer by calling setCategoryDrawer.
*
* @see KCategorizedSortFilterProxyModel, KCategoryDrawer
*
* @author Rafael Fernández López <ereslibre@kde.org>
*/
class KITEMVIEWS_EXPORT KCategorizedView : public QListView
{
Q_OBJECT
Q_PROPERTY(int categorySpacing READ categorySpacing WRITE setCategorySpacing NOTIFY categorySpacingChanged)
Q_PROPERTY(bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors NOTIFY alternatingBlockColorsChanged)
Q_PROPERTY(bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks NOTIFY collapsibleBlocksChanged)
public:
KCategorizedView(QWidget *parent = nullptr);
~KCategorizedView() override;
/**
* Reimplemented from QAbstractItemView.
*/
void setModel(QAbstractItemModel *model) override;
/**
* Calls to setGridSizeOwn().
*/
void setGridSize(const QSize &size);
/**
* @warning note that setGridSize is not virtual in the base class (QListView), so if you are
* calling to this method, make sure you have a KCategorizedView pointer around. This
* means that something like:
* @code
* QListView *lv = new KCategorizedView();
* lv->setGridSize(mySize);
* @endcode
*
* will not call to the expected setGridSize method. Instead do something like this:
*
* @code
* QListView *lv;
* ...
* KCategorizedView *cv = qobject_cast<KCategorizedView*>(lv);
* if (cv) {
* cv->setGridSizeOwn(mySize);
* } else {
* lv->setGridSize(mySize);
* }
* @endcode
*
* @note this method will call to QListView::setGridSize among other operations.
*
* @since 4.4
*/
void setGridSizeOwn(const QSize &size);
/**
* Reimplemented from QAbstractItemView.
*/
QRect visualRect(const QModelIndex &index) const override;
/**
* Returns the current category drawer.
*/
KCategoryDrawer *categoryDrawer() const;
/**
* The category drawer that will be used for drawing categories.
*/
void setCategoryDrawer(KCategoryDrawer *categoryDrawer);
/**
* @return Category spacing. The spacing between categories.
*
* @since 4.4
*/
int categorySpacing() const;
/**
* Stablishes the category spacing. This is the spacing between categories.
*
* @since 4.4
*/
void setCategorySpacing(int categorySpacing);
/**
* @return Whether blocks should be drawn with alternating colors.
*
* @since 4.4
*/
bool alternatingBlockColors() const;
/**
* Sets whether blocks should be drawn with alternating colors.
*
* @since 4.4
*/
void setAlternatingBlockColors(bool enable);
/**
* @return Whether blocks can be collapsed or not.
*
* @since 4.4
*/
bool collapsibleBlocks() const;
/**
* Sets whether blocks can be collapsed or not.
*
* @since 4.4
*/
void setCollapsibleBlocks(bool enable);
/**
* @return Block of indexes that are into @p category.
*
* @since 4.5
*/
QModelIndexList block(const QString &category);
/**
* @return Block of indexes that are represented by @p representative.
*
* @since 4.5
*/
QModelIndexList block(const QModelIndex &representative);
/**
* Reimplemented from QAbstractItemView.
*/
QModelIndex indexAt(const QPoint &point) const override;
/**
* Reimplemented from QAbstractItemView.
*/
void reset() override;
Q_SIGNALS:
void categorySpacingChanged(int spacing);
void alternatingBlockColorsChanged(bool enable);
void collapsibleBlocksChanged(bool enable);
protected:
/**
* Reimplemented from QWidget.
*/
void paintEvent(QPaintEvent *event) override;
/**
* Reimplemented from QWidget.
*/
void resizeEvent(QResizeEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override;
/**
* Reimplemented from QWidget.
*/
void mouseMoveEvent(QMouseEvent *event) override;
/**
* Reimplemented from QWidget.
*/
void mousePressEvent(QMouseEvent *event) override;
/**
* Reimplemented from QWidget.
*/
void mouseReleaseEvent(QMouseEvent *event) override;
/**
* Reimplemented from QWidget.
*/
void leaveEvent(QEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
void startDrag(Qt::DropActions supportedActions) override;
/**
* Reimplemented from QAbstractItemView.
*/
void dragMoveEvent(QDragMoveEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
void dragEnterEvent(QDragEnterEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
void dragLeaveEvent(QDragLeaveEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
void dropEvent(QDropEvent *event) override;
/**
* Reimplemented from QAbstractItemView.
*/
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
/**
* Reimplemented from QAbstractItemView.
*/
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
/**
* Reimplemented from QAbstractItemView.
*/
void updateGeometries() override;
/**
* Reimplemented from QAbstractItemView.
*/
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
/**
* Reimplemented from QAbstractItemView.
*/
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles = QList<int>()) override;
/**
* Reimplemented from QAbstractItemView.
*/
void rowsInserted(const QModelIndex &parent, int start, int end) override;
protected Q_SLOTS:
/**
* @internal
* Reposition items as needed.
*/
virtual void slotLayoutChanged();
private:
friend class KCategorizedViewPrivate;
std::unique_ptr<class KCategorizedViewPrivate> const d;
Q_PRIVATE_SLOT(d, void _k_slotCollapseOrExpandClicked(QModelIndex))
};
#endif // KCATEGORIZEDVIEW_H
@@ -0,0 +1,150 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATEGORIZEDVIEW_P_H
#define KCATEGORIZEDVIEW_P_H
#include "kcategorizedview.h"
class KCategorizedSortFilterProxyModel;
class KCategoryDrawer;
class KCategoryDrawerV2;
class KCategoryDrawerV3;
/**
* @internal
*/
class KCategorizedViewPrivate
{
public:
struct Block;
struct Item;
explicit KCategorizedViewPrivate(KCategorizedView *qq);
~KCategorizedViewPrivate();
/**
* @return whether this view has all required elements to be categorized.
*/
bool isCategorized() const;
/**
* Wrapper that returns the view's QStyleOptionViewItem, in Qt5 using viewOptions(), and
* in Qt6 using initViewItemOption().
*/
QStyleOptionViewItem viewOpts();
/**
* @return the block rect for the representative @p representative.
*/
QStyleOptionViewItem blockRect(const QModelIndex &representative);
/**
* Returns the first and last element that intersects with rect.
*
* @note see that here we cannot take out items between first and last (as we could
* do with the rubberband).
*
* Complexity: O(log(n)) where n is model()->rowCount().
*/
std::pair<QModelIndex, QModelIndex> intersectingIndexesWithRect(const QRect &rect) const;
/**
* Returns the position of the block of @p category.
*
* Complexity: O(n) where n is the number of different categories when the block has been
* marked as in quarantine. O(1) the rest of the times (the vast majority).
*/
QPoint blockPosition(const QString &category);
/**
* Returns the height of the block determined by @p category.
*/
int blockHeight(const QString &category);
/**
* Returns the actual viewport width.
*/
int viewportWidth() const;
/**
* Marks all elements as in quarantine.
*
* Complexity: O(n) where n is model()->rowCount().
*
* @warning this is an expensive operation
*/
void regenerateAllElements();
/**
* Update internal information, and keep sync with the real information that the model contains.
*/
void rowsInserted(const QModelIndex &parent, int start, int end);
/**
* Returns @p rect in viewport terms, taking in count horizontal and vertical offsets.
*/
QRect mapToViewport(const QRect &rect) const;
/**
* Returns @p rect in absolute terms, converted from viewport position.
*/
QRect mapFromViewport(const QRect &rect) const;
/**
* Returns the height of the highest element in last row. This is only applicable if there is
* no grid set and uniformItemSizes is false.
*
* @param block in which block are we searching. Necessary to stop the search if we hit the
* first item in this block.
*/
int highestElementInLastRow(const Block &block) const;
/**
* Returns whether the view has a valid grid size.
*/
bool hasGrid() const;
/**
* Returns the category for the given index.
*/
QString categoryForIndex(const QModelIndex &index) const;
/**
* Updates the visual rect for item when flow is LeftToRight.
*/
void leftToRightVisualRect(const QModelIndex &index, Item &item, const Block &block, const QPoint &blockPos) const;
/**
* Updates the visual rect for item when flow is TopToBottom.
* @note we only support viewMode == ListMode in this case.
*/
void topToBottomVisualRect(const QModelIndex &index, Item &item, const Block &block, const QPoint &blockPos) const;
/**
* Called when expand or collapse has been clicked on the category drawer.
*/
void _k_slotCollapseOrExpandClicked(QModelIndex);
KCategorizedView *const q;
KCategorizedSortFilterProxyModel *proxyModel = nullptr;
KCategoryDrawer *categoryDrawer = nullptr;
int categorySpacing = 0;
bool alternatingBlockColors = false;
bool collapsibleBlocks = false;
Block *const hoveredBlock;
QString hoveredCategory;
QModelIndex hoveredIndex;
QPoint pressedPosition;
QRect rubberBandRect;
QHash<QString, Block> blocks;
};
#endif // KCATEGORIZEDVIEW_P_H
@@ -0,0 +1,143 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2019 David Redondo <kde@david-redondo.de>
SPDX-FileCopyrightText: 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcategorydrawer.h"
#include <QApplication>
#include <QPainter>
#include <QStyleOption>
#include <kcategorizedsortfilterproxymodel.h>
#include <kcategorizedview.h>
#include <cmath>
class KCategoryDrawerPrivate
{
public:
KCategoryDrawerPrivate(KCategorizedView *view)
: view(view)
{
}
~KCategoryDrawerPrivate()
{
}
KCategorizedView *const view;
};
KCategoryDrawer::KCategoryDrawer(KCategorizedView *view)
: QObject(view)
, d(new KCategoryDrawerPrivate(view))
{
}
KCategoryDrawer::~KCategoryDrawer() = default;
void KCategoryDrawer::drawCategory(const QModelIndex &index, int /*sortRole*/, const QStyleOption &option, QPainter *painter) const
{
// Keep this in sync with Kirigami.ListSectionHeader
painter->setRenderHint(QPainter::Antialiasing);
const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
QFont font(QApplication::font());
font.setBold(true);
const QFontMetrics fontMetrics = QFontMetrics(font);
const int topPadding = 8 + 4; // Kirigami.Units.largeSpacing + smallSpacing
const int sidePadding = 8; // Kirigami.Units.largeSpacing
// BEGIN: text
{
QRect textRect(option.rect);
textRect.setTop(textRect.top() + topPadding);
textRect.setLeft(textRect.left() + sidePadding);
textRect.setRight(textRect.right() - sidePadding);
textRect.setHeight(fontMetrics.height());
painter->save();
painter->setFont(font);
QColor penColor(option.palette.text().color());
penColor.setAlphaF(0.7);
painter->setPen(penColor);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category);
painter->restore();
}
// END: text
// BEGIN: horizontal line
{
QColor backgroundColor = option.palette.text().color();
backgroundColor.setAlphaF(0.7 * 0.15); // replicate Kirigami.Separator color
QRect backgroundRect(option.rect);
backgroundRect.setLeft(fontMetrics.horizontalAdvance(category) + sidePadding * 2);
backgroundRect.setRight(backgroundRect.right() - sidePadding);
backgroundRect.setTop(backgroundRect.top() + topPadding + ceil(fontMetrics.height() / 2));
backgroundRect.setHeight(1);
painter->save();
painter->setBrush(backgroundColor);
painter->setPen(Qt::NoPen);
painter->drawRect(backgroundRect);
painter->restore();
}
// END: horizontal line
}
int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const
{
Q_UNUSED(index);
Q_UNUSED(option)
QFont font(QApplication::font());
QFontMetrics fontMetrics(font);
const int height = fontMetrics.height() + 8 + 8; // Kirigami.Units.largeSpacing + smallSpacing * 2
return height;
}
int KCategoryDrawer::leftMargin() const
{
return 0;
}
int KCategoryDrawer::rightMargin() const
{
return 0;
}
KCategorizedView *KCategoryDrawer::view() const
{
return d->view;
}
void KCategoryDrawer::mouseButtonPressed(const QModelIndex &, const QRect &, QMouseEvent *event)
{
event->ignore();
}
void KCategoryDrawer::mouseButtonReleased(const QModelIndex &, const QRect &, QMouseEvent *event)
{
event->ignore();
}
void KCategoryDrawer::mouseMoved(const QModelIndex &, const QRect &, QMouseEvent *event)
{
event->ignore();
}
void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex &, const QRect &, QMouseEvent *event)
{
event->ignore();
}
void KCategoryDrawer::mouseLeft(const QModelIndex &, const QRect &)
{
}
#include "moc_kcategorydrawer.cpp"
@@ -0,0 +1,156 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATEGORYDRAWER_H
#define KCATEGORYDRAWER_H
#include <kitemviews_export.h>
#include <QMouseEvent>
#include <QObject>
#include <memory>
class KCategoryDrawerPrivate;
class QPainter;
class QModelIndex;
class QStyleOption;
class KCategorizedView;
/**
* @class KCategoryDrawer kcategorydrawer.h KCategoryDrawer
*
* The category drawing is performed by this class. It also gives information about the category
* height and margins.
*
*/
class KITEMVIEWS_EXPORT KCategoryDrawer : public QObject
{
Q_OBJECT
friend class KCategorizedView;
public:
/*
* Construct a category drawer for a given view
*
* @since 5.0
*/
KCategoryDrawer(KCategorizedView *view);
~KCategoryDrawer() override;
/**
* @return The view this category drawer is associated with.
*/
KCategorizedView *view() const;
/**
* This method purpose is to draw a category represented by the given
* @param index with the given @param sortRole sorting role
*
* @note This method will be called one time per category, always with the
* first element in that category
*/
virtual void drawCategory(const QModelIndex &index, int sortRole, const QStyleOption &option, QPainter *painter) const;
/**
* @return The category height for the category represented by index @p index with
* style options @p option.
*/
virtual int categoryHeight(const QModelIndex &index, const QStyleOption &option) const;
/**
* @note 0 by default
*
* @since 4.4
*/
virtual int leftMargin() const;
/**
* @note 0 by default
*
* @since 4.4
*/
virtual int rightMargin() const;
Q_SIGNALS:
/**
* This signal becomes emitted when collapse or expand has been clicked.
*/
void collapseOrExpandClicked(const QModelIndex &index);
/**
* Emit this signal on your subclass implementation to notify that something happened. Usually
* this will be triggered when you have received an event, and its position matched some "hot spot".
*
* You give this action the integer you want, and having connected this signal to your code,
* the connected slot can perform the needed changes (view, model, selection model, delegate...)
*/
void actionRequested(int action, const QModelIndex &index);
protected:
/**
* Method called when the mouse button has been pressed.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonPressed(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event);
/**
* Method called when the mouse button has been released.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonReleased(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event);
/**
* Method called when the mouse has been moved.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*/
virtual void mouseMoved(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event);
/**
* Method called when the mouse button has been double clicked.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonDoubleClicked(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event);
/**
* Method called when the mouse button has left this block.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
*/
virtual void mouseLeft(const QModelIndex &index, const QRect &blockRect);
private:
std::unique_ptr<KCategoryDrawerPrivate> const d;
};
#endif // KCATEGORYDRAWER_H
@@ -0,0 +1,411 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006, 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kextendableitemdelegate.h"
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QScrollBar>
#include <QTreeView>
class KExtendableItemDelegatePrivate
{
public:
KExtendableItemDelegatePrivate(KExtendableItemDelegate *parent)
: q(parent)
, stateTick(0)
, cachedStateTick(-1)
, cachedRow(-20)
, // Qt uses -1 for invalid indices
extenderHeight(0)
{
}
void _k_extenderDestructionHandler(QObject *destroyed);
void _k_verticalScroll();
QSize maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QModelIndex indexOfExtendedColumnInSameRow(const QModelIndex &index) const;
void scheduleUpdateViewLayout();
KExtendableItemDelegate *const q;
/**
* Delete all active extenders
*/
void deleteExtenders();
// this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex
QHash<QPersistentModelIndex, QWidget *> extenders;
QHash<QWidget *, QPersistentModelIndex> extenderIndices;
QMultiHash<QWidget *, QPersistentModelIndex> deletionQueue;
QPixmap extendPixmap;
QPixmap contractPixmap;
int stateTick;
int cachedStateTick;
int cachedRow;
QModelIndex cachedParentIndex;
QWidget *extender = nullptr;
int extenderHeight;
};
KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView *parent)
: QStyledItemDelegate(parent)
, d(new KExtendableItemDelegatePrivate(this))
{
connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(_k_verticalScroll()));
}
KExtendableItemDelegate::~KExtendableItemDelegate() = default;
void KExtendableItemDelegate::extendItem(QWidget *ext, const QModelIndex &index)
{
// qDebug() << "Creating extender at " << ext << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
if (!ext || !index.isValid()) {
return;
}
// maintain the invariant "zero or one extender per row"
d->stateTick++;
contractItem(d->indexOfExtendedColumnInSameRow(index));
d->stateTick++;
// reparent, as promised in the docs
QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(parent());
if (!aiv) {
return;
}
ext->setParent(aiv->viewport());
d->extenders.insert(index, ext);
d->extenderIndices.insert(ext, index);
connect(ext, SIGNAL(destroyed(QObject *)), this, SLOT(_k_extenderDestructionHandler(QObject *)));
Q_EMIT extenderCreated(ext, index);
d->scheduleUpdateViewLayout();
}
void KExtendableItemDelegate::contractItem(const QModelIndex &index)
{
QWidget *extender = d->extenders.value(index);
if (!extender) {
return;
}
// qDebug() << "Collapse extender at " << extender << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
extender->hide();
extender->deleteLater();
QPersistentModelIndex persistentIndex = d->extenderIndices.take(extender);
d->extenders.remove(persistentIndex);
d->deletionQueue.insert(extender, persistentIndex);
d->scheduleUpdateViewLayout();
}
void KExtendableItemDelegate::contractAll()
{
d->deleteExtenders();
}
// slot
void KExtendableItemDelegatePrivate::_k_extenderDestructionHandler(QObject *destroyed)
{
// qDebug() << "Removing extender at " << destroyed;
QWidget *extender = static_cast<QWidget *>(destroyed);
stateTick++;
QPersistentModelIndex persistentIndex = deletionQueue.take(extender);
if (persistentIndex.isValid() && q->receivers(SIGNAL(extenderDestroyed(QWidget *, QModelIndex))) != 0) {
QModelIndex index = persistentIndex;
Q_EMIT q->extenderDestroyed(extender, index);
}
scheduleUpdateViewLayout();
}
// slot
void KExtendableItemDelegatePrivate::_k_verticalScroll()
{
for (QWidget *extender : std::as_const(extenders)) {
// Fast scrolling can lead to artifacts where extenders stay in the viewport
// of the parent's scroll area even though their items are scrolled out.
// Therefore we hide all extenders when scrolling.
// In paintEvent() show() will be called on actually visible extenders and
// Qt's double buffering takes care of eliminating flicker.
// ### This scales badly to many extenders. There are probably better ways to
// avoid the artifacts.
extender->hide();
}
}
bool KExtendableItemDelegate::isExtended(const QModelIndex &index) const
{
return d->extenders.value(index);
}
QSize KExtendableItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize ret;
if (!d->extenders.isEmpty()) {
ret = d->maybeExtendedSize(option, index);
} else {
ret = QStyledItemDelegate::sizeHint(option, index);
}
bool showExtensionIndicator = index.model() ? index.model()->data(index, ShowExtensionIndicatorRole).toBool() : false;
if (showExtensionIndicator) {
ret.rwidth() += d->extendPixmap.width() / d->extendPixmap.devicePixelRatio();
}
return ret;
}
void KExtendableItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
int indicatorX = 0;
int indicatorY = 0;
QStyleOptionViewItem indicatorOption(option);
initStyleOption(&indicatorOption, index);
if (index.column() == 0) {
indicatorOption.viewItemPosition = QStyleOptionViewItem::Beginning;
} else if (index.column() == index.model()->columnCount() - 1) {
indicatorOption.viewItemPosition = QStyleOptionViewItem::End;
} else {
indicatorOption.viewItemPosition = QStyleOptionViewItem::Middle;
}
QStyleOptionViewItem itemOption(option);
initStyleOption(&itemOption, index);
if (index.column() == 0) {
itemOption.viewItemPosition = QStyleOptionViewItem::Beginning;
} else if (index.column() == index.model()->columnCount() - 1) {
itemOption.viewItemPosition = QStyleOptionViewItem::End;
} else {
itemOption.viewItemPosition = QStyleOptionViewItem::Middle;
}
const bool showExtensionIndicator = index.model()->data(index, ShowExtensionIndicatorRole).toBool();
if (showExtensionIndicator) {
const QSize extendPixmapSize = d->extendPixmap.size() / d->extendPixmap.devicePixelRatio();
if (QApplication::isRightToLeft()) {
indicatorX = option.rect.right() - extendPixmapSize.width();
itemOption.rect.setRight(indicatorX);
indicatorOption.rect.setLeft(indicatorX);
} else {
indicatorX = option.rect.left();
indicatorOption.rect.setRight(indicatorX + extendPixmapSize.width());
itemOption.rect.setLeft(indicatorX + extendPixmapSize.width());
}
indicatorY = option.rect.top() + ((option.rect.height() - extendPixmapSize.height()) >> 1);
}
// fast path
if (d->extenders.isEmpty()) {
QStyledItemDelegate::paint(painter, itemOption, index);
if (showExtensionIndicator) {
painter->save();
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
painter->restore();
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
}
return;
}
int row = index.row();
QModelIndex parentIndex = index.parent();
// indexOfExtendedColumnInSameRow() is very expensive, try to avoid calling it.
if (row != d->cachedRow //
|| d->cachedStateTick != d->stateTick //
|| d->cachedParentIndex != parentIndex) {
d->extender = d->extenders.value(d->indexOfExtendedColumnInSameRow(index));
d->cachedStateTick = d->stateTick;
d->cachedRow = row;
d->cachedParentIndex = parentIndex;
if (d->extender) {
d->extenderHeight = d->extender->sizeHint().height();
}
}
if (!d->extender) {
QStyledItemDelegate::paint(painter, itemOption, index);
if (showExtensionIndicator) {
painter->save();
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
painter->restore();
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
}
return;
}
// an extender is present - make two rectangles: one to paint the original item, one for the extender
if (isExtended(index)) {
QStyleOptionViewItem extOption(option);
initStyleOption(&extOption, index);
extOption.rect = extenderRect(d->extender, option, index);
updateExtenderGeometry(d->extender, extOption, index);
// if we show it before, it will briefly flash in the wrong location.
// the downside is, of course, that an api user effectively can't hide it.
d->extender->show();
}
indicatorOption.rect.setHeight(option.rect.height() - d->extenderHeight);
itemOption.rect.setHeight(option.rect.height() - d->extenderHeight);
// tricky:make sure that the modified options' rect really has the
// same height as the unchanged option.rect if no extender is present
//(seems to work OK)
QStyledItemDelegate::paint(painter, itemOption, index);
if (showExtensionIndicator) {
const int extendPixmapHeight = d->extendPixmap.height() / d->extendPixmap.devicePixelRatio();
// indicatorOption's height changed, change this too
indicatorY = indicatorOption.rect.top() + ((indicatorOption.rect.height() - extendPixmapHeight) >> 1);
painter->save();
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, painter);
painter->restore();
if (d->extenders.contains(index)) {
painter->drawPixmap(indicatorX, indicatorY, d->contractPixmap);
} else {
painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
}
}
}
QRect KExtendableItemDelegate::extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_ASSERT(extender);
QRect rect(option.rect);
rect.setTop(rect.bottom() + 1 - extender->sizeHint().height());
int indentation = 0;
if (QTreeView *tv = qobject_cast<QTreeView *>(parent())) {
int indentSteps = 0;
for (QModelIndex idx(index.parent()); idx.isValid(); idx = idx.parent()) {
indentSteps++;
}
if (tv->rootIsDecorated()) {
indentSteps++;
}
indentation = indentSteps * tv->indentation();
}
QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent());
Q_ASSERT(container);
if (qApp->isLeftToRight()) {
rect.setLeft(indentation);
rect.setRight(container->viewport()->width() - 1);
} else {
rect.setRight(container->viewport()->width() - 1 - indentation);
rect.setLeft(0);
}
return rect;
}
QSize KExtendableItemDelegatePrivate::maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QWidget *extender = extenders.value(index);
QSize size(q->QStyledItemDelegate::sizeHint(option, index));
if (!extender) {
return size;
}
// add extender height to maximum height of any column in our row
int itemHeight = size.height();
int row = index.row();
int thisColumn = index.column();
// this is quite slow, but Qt is smart about when to call sizeHint().
for (int column = 0; index.model()->columnCount() < column; column++) {
if (column == thisColumn) {
continue;
}
QModelIndex neighborIndex(index.sibling(row, column));
if (!neighborIndex.isValid()) {
break;
}
itemHeight = qMax(itemHeight, q->QStyledItemDelegate::sizeHint(option, neighborIndex).height());
}
// we only want to reserve vertical space, the horizontal extender layout is our private business.
size.rheight() = itemHeight + extender->sizeHint().height();
return size;
}
QModelIndex KExtendableItemDelegatePrivate::indexOfExtendedColumnInSameRow(const QModelIndex &index) const
{
const QAbstractItemModel *const model = index.model();
const QModelIndex parentIndex(index.parent());
const int row = index.row();
const int columnCount = model->columnCount();
// slow, slow, slow
for (int column = 0; column < columnCount; column++) {
QModelIndex indexOfExt(model->index(row, column, parentIndex));
if (extenders.value(indexOfExt)) {
return indexOfExt;
}
}
return QModelIndex();
}
void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
extender->setGeometry(option.rect);
}
void KExtendableItemDelegatePrivate::deleteExtenders()
{
for (QWidget *ext : std::as_const(extenders)) {
ext->hide();
ext->deleteLater();
}
deletionQueue.unite(extenderIndices);
extenders.clear();
extenderIndices.clear();
}
// make the view re-ask for sizeHint() and redisplay items with their new size
//### starting from Qt 4.4 we could emit sizeHintChanged() instead
void KExtendableItemDelegatePrivate::scheduleUpdateViewLayout()
{
QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(q->parent());
// prevent crashes during destruction of the view
if (aiv) {
// dirty hack to call aiv's protected scheduleDelayedItemsLayout()
aiv->setRootIndex(aiv->rootIndex());
}
}
void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap)
{
d->extendPixmap = pixmap;
}
void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap)
{
d->contractPixmap = pixmap;
}
QPixmap KExtendableItemDelegate::extendPixmap()
{
return d->extendPixmap;
}
QPixmap KExtendableItemDelegate::contractPixmap()
{
return d->contractPixmap;
}
#include "moc_kextendableitemdelegate.cpp"
@@ -0,0 +1,144 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006, 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KEXTENDABLEITEMDELEGATE_H
#define KEXTENDABLEITEMDELEGATE_H
#include <QStyledItemDelegate>
#include <memory>
#include <kitemviews_export.h>
class QAbstractItemView;
/**
* @class KExtendableItemDelegate kextendableitemdelegate.h KExtendableItemDelegate
*
* This delegate makes it possible to display an arbitrary QWidget ("extender") that spans all columns below a line of items.
* The extender will logically belong to a column in the row above it.
*
* It is your responsibility to devise a way to trigger extension and contraction of items, by calling
* extendItem() and contractItem(). You can e.g. reimplement itemActivated() and similar functions.
*
* @warning extendItem() reparents the provided widget @a extender to the
* viewport of the itemview it belongs to. The @a extender is destroyed when
* you call contractItem() for the associated index. If you fail to do that
* and the associated item gets deleted you're in trouble. It remains as a
* visible artefact in your treeview. Additionally when closing your
* application you get an assertion failure from KExtendableItemDelegate. Make
* sure that you always call contractItem for indices before you delete them.
*
* @author Andreas Hartmetz <ahartmetz@gmail.com>
*
* @since 4.1
*/
class KITEMVIEWS_EXPORT KExtendableItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
enum auxDataRoles {
ShowExtensionIndicatorRole = Qt::UserRole + 200,
};
/**
* Create a new KExtendableItemDelegate that belongs to @p parent. In contrast to generic
* QAbstractItemDelegates, an instance of this class can only ever be the delegate for one
* instance of af QAbstractItemView subclass.
*/
KExtendableItemDelegate(QAbstractItemView *parent);
~KExtendableItemDelegate() override;
/**
* Re-implemented for internal reasons. API not affected.
*/
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Re-implemented for internal reasons. API not affected.
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Insert the @p extender for item at @p index into the view.
* If you need a parent for the extender at construction time, use the itemview's viewport().
* The delegate takes ownership of the extender; the extender will also be reparented and
* resized to the viewport.
*/
void extendItem(QWidget *extender, const QModelIndex &index);
/**
* Remove the extender of item at @p index from the view. The extender widget
* will be deleted.
*/
void contractItem(const QModelIndex &index);
/**
* Close all extenders and delete all extender widgets.
*/
void contractAll();
/**
* Return whether there is an extender that belongs to @p index.
*/
bool isExtended(const QModelIndex &index) const;
/**
* Reimplement this function to adjust the internal geometry of the extender.
* The external geometry of the extender will be set by the delegate.
*/
virtual void updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const;
Q_SIGNALS:
/**
* This signal indicates that the item at @p index was extended with @p extender.
*/
void extenderCreated(QWidget *extender, const QModelIndex &index);
/**
* This signal indicates that the @p extender belonging to @p index has emitted the destroyed() signal.
*/
void extenderDestroyed(QWidget *extender, const QModelIndex &index);
protected:
/**
* Reimplement this function to fine-tune the position of the extender. @p option.rect will be a rectangle
* that is as wide as the viewport and as high as the usual item height plus the extender size hint's height.
* Its upper left corner will be at the upper left corner of the usual item.
* You can place the returned rectangle of this function anywhere inside that area.
*/
QRect extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const;
/**
* The pixmap that is displayed to extend an item. @p pixmap must have the same size as the pixmap in setContractPixmap.
*/
void setExtendPixmap(const QPixmap &pixmap);
/**
* The pixmap that is displayed to contract an item. @p pixmap must have the same size as the pixmap in setExtendPixmap.
*/
void setContractPixmap(const QPixmap &pixmap);
/**
* Return the pixmap that is displayed to extend an item.
*/
QPixmap extendPixmap();
/**
* Return the pixmap that is displayed to contract an item.
*/
QPixmap contractPixmap();
private:
friend class KExtendableItemDelegatePrivate;
std::unique_ptr<class KExtendableItemDelegatePrivate> const d;
Q_PRIVATE_SLOT(d, void _k_extenderDestructionHandler(QObject *destroyed))
Q_PRIVATE_SLOT(d, void _k_verticalScroll())
};
#endif // KEXTENDABLEITEMDELEGATE_H
@@ -0,0 +1,253 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2004 Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "klistwidgetsearchline.h"
#include <QApplication>
#include <QEvent>
#include <QKeyEvent>
#include <QListWidget>
#include <QTimer>
class KListWidgetSearchLinePrivate
{
public:
KListWidgetSearchLinePrivate(KListWidgetSearchLine *parent)
: q(parent)
{
}
void _k_listWidgetDeleted();
void _k_queueSearch(const QString &);
void _k_activateSearch();
void _k_rowsInserted(const QModelIndex &, int, int);
void _k_dataChanged(const QModelIndex &, const QModelIndex &);
void init(QListWidget *listWidget = nullptr);
void updateHiddenState(int start, int end);
KListWidgetSearchLine *const q;
QListWidget *listWidget = nullptr;
Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
bool activeSearch = false;
QString search;
int queuedSearches = 0;
};
/******************************************************************************
* Public Methods *
*****************************************************************************/
KListWidgetSearchLine::KListWidgetSearchLine(QWidget *parent, QListWidget *listWidget)
: QLineEdit(parent)
, d(new KListWidgetSearchLinePrivate(this))
{
d->init(listWidget);
}
KListWidgetSearchLine::~KListWidgetSearchLine()
{
clear(); // returning items back to listWidget
}
Qt::CaseSensitivity KListWidgetSearchLine::caseSensitive() const
{
return d->caseSensitivity;
}
QListWidget *KListWidgetSearchLine::listWidget() const
{
return d->listWidget;
}
/******************************************************************************
* Public Slots *
*****************************************************************************/
void KListWidgetSearchLine::updateSearch(const QString &s)
{
d->search = s.isNull() ? text() : s;
if (d->listWidget) {
d->updateHiddenState(0, d->listWidget->count() - 1);
}
}
void KListWidgetSearchLine::clear()
{
// Show items back to QListWidget
if (d->listWidget != nullptr) {
for (int i = 0; i < d->listWidget->count(); ++i) {
d->listWidget->item(i)->setHidden(false);
}
}
d->search = QString();
d->queuedSearches = 0;
QLineEdit::clear();
}
void KListWidgetSearchLine::setCaseSensitivity(Qt::CaseSensitivity cs)
{
d->caseSensitivity = cs;
}
void KListWidgetSearchLine::setListWidget(QListWidget *lw)
{
if (d->listWidget != nullptr) {
disconnect(d->listWidget, SIGNAL(destroyed()), this, SLOT(_k_listWidgetDeleted()));
d->listWidget->model()->disconnect(this);
}
d->listWidget = lw;
if (lw != nullptr) {
// clang-format off
connect(d->listWidget, SIGNAL(destroyed()), this, SLOT(_k_listWidgetDeleted()));
connect(d->listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(_k_rowsInserted(QModelIndex,int,int)));
connect(d->listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
// clang-format on
setEnabled(true);
} else {
setEnabled(false);
}
}
/******************************************************************************
* Protected Methods *
*****************************************************************************/
bool KListWidgetSearchLine::itemMatches(const QListWidgetItem *item, const QString &s) const
{
if (s.isEmpty()) {
return true;
}
if (item == nullptr) {
return false;
}
return (item->text().indexOf(s, 0, caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0);
}
void KListWidgetSearchLinePrivate::init(QListWidget *_listWidget)
{
listWidget = _listWidget;
QObject::connect(q, SIGNAL(textChanged(QString)), q, SLOT(_k_queueSearch(QString)));
if (listWidget != nullptr) {
// clang-format off
QObject::connect(listWidget, SIGNAL(destroyed()), q, SLOT(_k_listWidgetDeleted()));
QObject::connect(listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_rowsInserted(QModelIndex,int,int)));
QObject::connect(listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
// clang-format on
q->setEnabled(true);
} else {
q->setEnabled(false);
}
q->setClearButtonEnabled(true);
}
void KListWidgetSearchLinePrivate::updateHiddenState(int start, int end)
{
if (!listWidget) {
return;
}
QListWidgetItem *currentItem = listWidget->currentItem();
// Remove Non-Matching items
for (int index = start; index <= end; ++index) {
QListWidgetItem *item = listWidget->item(index);
if (!q->itemMatches(item, search)) {
item->setHidden(true);
if (item == currentItem) {
currentItem = nullptr; // It's not in listWidget anymore.
}
} else if (item->isHidden()) {
item->setHidden(false);
}
}
if (listWidget->isSortingEnabled()) {
listWidget->sortItems();
}
if (currentItem != nullptr) {
listWidget->scrollToItem(currentItem);
}
}
bool KListWidgetSearchLine::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine)
|| keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine)
|| keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)
|| keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage)) {
if (d->listWidget) {
QApplication::sendEvent(d->listWidget, event);
return true;
}
} else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
if (d->listWidget) {
QApplication::sendEvent(d->listWidget, event);
return true;
}
}
}
return QLineEdit::event(event);
}
/******************************************************************************
* Protected Slots *
*****************************************************************************/
void KListWidgetSearchLinePrivate::_k_queueSearch(const QString &s)
{
queuedSearches++;
search = s;
QTimer::singleShot(200, q, SLOT(_k_activateSearch()));
}
void KListWidgetSearchLinePrivate::_k_activateSearch()
{
queuedSearches--;
if (queuedSearches <= 0) {
q->updateSearch(search);
queuedSearches = 0;
}
}
/******************************************************************************
* KListWidgetSearchLinePrivate Slots *
*****************************************************************************/
void KListWidgetSearchLinePrivate::_k_listWidgetDeleted()
{
listWidget = nullptr;
q->setEnabled(false);
}
void KListWidgetSearchLinePrivate::_k_rowsInserted(const QModelIndex &parent, int start, int end)
{
if (parent.isValid()) {
return;
}
updateHiddenState(start, end);
}
void KListWidgetSearchLinePrivate::_k_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
if (topLeft.parent().isValid()) {
return;
}
updateHiddenState(topLeft.row(), bottomRight.row());
}
#include "moc_klistwidgetsearchline.cpp"
@@ -0,0 +1,113 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2004 Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLISTWIDGETSEARCHLINE_H
#define KLISTWIDGETSEARCHLINE_H
#include <QLineEdit>
#include <memory>
#include <kitemviews_export.h>
class QListWidget;
class QListWidgetItem;
class QModelIndex;
/**
* @class KListWidgetSearchLine klistwidgetsearchline.h KListWidgetSearchLine
*
* This class makes it easy to add a search line for filtering the items in a
* listwidget based on a simple text search.
*
* No changes to the application other than instantiating this class with an
* appropriate QListWidget should be needed.
*/
class KITEMVIEWS_EXPORT KListWidgetSearchLine : public QLineEdit
{
Q_OBJECT
public:
/**
* Constructs a KListWidgetSearchLine with \a listWidget being the QListWidget to
* be filtered.
*
* If \a listWidget is null then the widget will be disabled until a listWidget
* is set with setListWidget().
*/
explicit KListWidgetSearchLine(QWidget *parent = nullptr, QListWidget *listWidget = nullptr);
/**
* Destroys the KListWidgetSearchLine.
*/
~KListWidgetSearchLine() override;
/**
* Returns if the search is case sensitive. This defaults to Qt::CaseInsensitive.
*
* @see setCaseSensitive()
*/
Qt::CaseSensitivity caseSensitive() const;
/**
* Returns the listWidget that is currently filtered by the search.
*
* @see setListWidget()
*/
QListWidget *listWidget() const;
public Q_SLOTS:
/**
* Updates search to only make visible the items that match \a s. If
* \a s is null then the line edit's text will be used.
*/
virtual void updateSearch(const QString &s = QString());
/**
* Make the search case sensitive or case insensitive.
*
* @see caseSenstive()
*/
void setCaseSensitivity(Qt::CaseSensitivity cs);
/**
* Sets the QListWidget that is filtered by this search line. If \a lv is null
* then the widget will be disabled.
*
* @see listWidget()
*/
void setListWidget(QListWidget *lv);
/**
* Clear line edit and empty hiddenItems, returning elements to listWidget.
*/
void clear();
protected:
/**
* Returns true if \a item matches the search \a s. This will be evaluated
* based on the value of caseSensitive(). This can be overridden in
* subclasses to implement more complicated matching schemes.
*/
virtual bool itemMatches(const QListWidgetItem *item, const QString &s) const;
/**
* Re-implemented for internal reasons. API not affected.
*/
bool event(QEvent *event) override;
private:
friend class KListWidgetSearchLinePrivate;
std::unique_ptr<class KListWidgetSearchLinePrivate> const d;
Q_PRIVATE_SLOT(d, void _k_listWidgetDeleted())
Q_PRIVATE_SLOT(d, void _k_queueSearch(const QString &))
Q_PRIVATE_SLOT(d, void _k_activateSearch())
Q_PRIVATE_SLOT(d, void _k_rowsInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d, void _k_dataChanged(const QModelIndex &, const QModelIndex &))
};
#endif /* KLISTWIDGETSEARCHLINE_H */
@@ -0,0 +1,565 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktreewidgetsearchline.h"
#include <QActionGroup>
#include <QApplication>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QList>
#include <QMenu>
#include <QTimer>
#include <QTreeWidget>
class KTreeWidgetSearchLinePrivate
{
public:
KTreeWidgetSearchLinePrivate(KTreeWidgetSearchLine *_q)
: q(_q)
{
}
KTreeWidgetSearchLine *const q;
QList<QTreeWidget *> treeWidgets;
Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive;
bool keepParentsVisible = true;
bool canChooseColumns = true;
QString search;
int queuedSearches = 0;
QList<int> searchColumns;
void _k_rowsInserted(const QModelIndex &parent, int start, int end) const;
void _k_treeWidgetDeleted(QObject *treeWidget);
void _k_slotColumnActivated(QAction *action);
void _k_slotAllVisibleColumns();
void _k_queueSearch(const QString &);
void _k_activateSearch();
void checkColumns();
void checkItemParentsNotVisible(QTreeWidget *treeWidget);
bool checkItemParentsVisible(QTreeWidgetItem *item);
};
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
// Hack to make a protected method public
class QTreeWidgetWorkaround : public QTreeWidget
{
public:
QTreeWidgetItem *itemFromIndex(const QModelIndex &index) const
{
return QTreeWidget::itemFromIndex(index);
}
};
void KTreeWidgetSearchLinePrivate::_k_rowsInserted(const QModelIndex &parentIndex, int start, int end) const
{
QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(q->sender());
if (!model) {
return;
}
QTreeWidget *widget = nullptr;
for (QTreeWidget *tree : std::as_const(treeWidgets)) {
if (tree->model() == model) {
widget = tree;
break;
}
}
if (!widget) {
return;
}
QTreeWidgetWorkaround *widgetW = static_cast<QTreeWidgetWorkaround *>(widget);
for (int i = start; i <= end; ++i) {
if (QTreeWidgetItem *item = widgetW->itemFromIndex(model->index(i, 0, parentIndex))) {
bool newHidden = !q->itemMatches(item, q->text());
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
Q_EMIT q->hiddenChanged(item, newHidden);
}
}
}
}
void KTreeWidgetSearchLinePrivate::_k_treeWidgetDeleted(QObject *object)
{
treeWidgets.removeAll(static_cast<QTreeWidget *>(object));
q->setEnabled(treeWidgets.isEmpty());
}
void KTreeWidgetSearchLinePrivate::_k_slotColumnActivated(QAction *action)
{
if (!action) {
return;
}
bool ok;
int column = action->data().toInt(&ok);
if (!ok) {
return;
}
if (action->isChecked()) {
if (!searchColumns.isEmpty()) {
if (!searchColumns.contains(column)) {
searchColumns.append(column);
}
if (searchColumns.count() == treeWidgets.first()->header()->count() - treeWidgets.first()->header()->hiddenSectionCount()) {
searchColumns.clear();
}
} else {
searchColumns.append(column);
}
} else {
if (searchColumns.isEmpty()) {
QHeaderView *const header = treeWidgets.first()->header();
for (int i = 0; i < header->count(); i++) {
if (i != column && !header->isSectionHidden(i)) {
searchColumns.append(i);
}
}
} else if (searchColumns.contains(column)) {
searchColumns.removeAll(column);
}
}
q->updateSearch();
}
void KTreeWidgetSearchLinePrivate::_k_slotAllVisibleColumns()
{
if (searchColumns.isEmpty()) {
searchColumns.append(0);
} else {
searchColumns.clear();
}
q->updateSearch();
}
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLinePrivate::checkColumns()
{
canChooseColumns = q->canChooseColumnsCheck();
}
void KTreeWidgetSearchLinePrivate::checkItemParentsNotVisible(QTreeWidget *treeWidget)
{
for (QTreeWidgetItemIterator it(treeWidget); *it; ++it) {
QTreeWidgetItem *item = *it;
bool newHidden = !q->itemMatches(item, search);
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
Q_EMIT q->hiddenChanged(item, newHidden);
}
}
}
/** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary.
*
* \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or
* the first child of the list view.
* \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function
* returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown.
*/
bool KTreeWidgetSearchLinePrivate::checkItemParentsVisible(QTreeWidgetItem *item)
{
bool childMatch = false;
for (int i = 0; i < item->childCount(); ++i) {
childMatch |= checkItemParentsVisible(item->child(i));
}
// Should this item be shown? It should if any children should be, or if it matches.
bool newHidden = !childMatch && !q->itemMatches(item, search);
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
Q_EMIT q->hiddenChanged(item, newHidden);
}
return !newHidden;
}
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
KTreeWidgetSearchLine::KTreeWidgetSearchLine(QWidget *q, QTreeWidget *treeWidget)
: QLineEdit(q)
, d(new KTreeWidgetSearchLinePrivate(this))
{
connect(this, SIGNAL(textChanged(QString)), this, SLOT(_k_queueSearch(QString)));
setClearButtonEnabled(true);
setPlaceholderText(tr("Search…", "@info:placeholder"));
setTreeWidget(treeWidget);
if (!treeWidget) {
setEnabled(false);
}
}
KTreeWidgetSearchLine::KTreeWidgetSearchLine(QWidget *q, const QList<QTreeWidget *> &treeWidgets)
: QLineEdit(q)
, d(new KTreeWidgetSearchLinePrivate(this))
{
connect(this, SIGNAL(textChanged(QString)), this, SLOT(_k_queueSearch(QString)));
setClearButtonEnabled(true);
setTreeWidgets(treeWidgets);
}
KTreeWidgetSearchLine::~KTreeWidgetSearchLine() = default;
Qt::CaseSensitivity KTreeWidgetSearchLine::caseSensitivity() const
{
return d->caseSensitive;
}
QList<int> KTreeWidgetSearchLine::searchColumns() const
{
if (d->canChooseColumns) {
return d->searchColumns;
} else {
return QList<int>();
}
}
bool KTreeWidgetSearchLine::keepParentsVisible() const
{
return d->keepParentsVisible;
}
QTreeWidget *KTreeWidgetSearchLine::treeWidget() const
{
if (d->treeWidgets.count() == 1) {
return d->treeWidgets.first();
} else {
return nullptr;
}
}
QList<QTreeWidget *> KTreeWidgetSearchLine::treeWidgets() const
{
return d->treeWidgets;
}
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLine::addTreeWidget(QTreeWidget *treeWidget)
{
if (treeWidget) {
connectTreeWidget(treeWidget);
d->treeWidgets.append(treeWidget);
setEnabled(!d->treeWidgets.isEmpty());
d->checkColumns();
}
}
void KTreeWidgetSearchLine::removeTreeWidget(QTreeWidget *treeWidget)
{
if (treeWidget) {
int index = d->treeWidgets.indexOf(treeWidget);
if (index != -1) {
d->treeWidgets.removeAt(index);
d->checkColumns();
disconnectTreeWidget(treeWidget);
setEnabled(!d->treeWidgets.isEmpty());
}
}
}
void KTreeWidgetSearchLine::updateSearch(const QString &pattern)
{
d->search = pattern.isNull() ? text() : pattern;
for (QTreeWidget *treeWidget : std::as_const(d->treeWidgets)) {
updateSearch(treeWidget);
}
}
void KTreeWidgetSearchLine::updateSearch(QTreeWidget *treeWidget)
{
if (!treeWidget || !treeWidget->topLevelItemCount()) {
return;
}
// If there's a selected item that is visible, make sure that it's visible
// when the search changes too (assuming that it still matches).
QTreeWidgetItem *currentItem = treeWidget->currentItem();
if (d->keepParentsVisible) {
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
d->checkItemParentsVisible(treeWidget->topLevelItem(i));
}
} else {
d->checkItemParentsNotVisible(treeWidget);
}
if (currentItem) {
treeWidget->scrollToItem(currentItem);
}
Q_EMIT searchUpdated(d->search);
}
void KTreeWidgetSearchLine::setCaseSensitivity(Qt::CaseSensitivity caseSensitive)
{
if (d->caseSensitive != caseSensitive) {
d->caseSensitive = caseSensitive;
Q_EMIT caseSensitivityChanged(d->caseSensitive);
updateSearch();
}
}
void KTreeWidgetSearchLine::setKeepParentsVisible(bool visible)
{
if (d->keepParentsVisible != visible) {
d->keepParentsVisible = visible;
Q_EMIT keepParentsVisibleChanged(d->keepParentsVisible);
updateSearch();
}
}
void KTreeWidgetSearchLine::setSearchColumns(const QList<int> &columns)
{
if (d->canChooseColumns) {
d->searchColumns = columns;
}
}
void KTreeWidgetSearchLine::setTreeWidget(QTreeWidget *treeWidget)
{
setTreeWidgets(QList<QTreeWidget *>());
addTreeWidget(treeWidget);
}
void KTreeWidgetSearchLine::setTreeWidgets(const QList<QTreeWidget *> &treeWidgets)
{
for (QTreeWidget *treeWidget : std::as_const(d->treeWidgets)) {
disconnectTreeWidget(treeWidget);
}
d->treeWidgets = treeWidgets;
for (QTreeWidget *treeWidget : std::as_const(d->treeWidgets)) {
connectTreeWidget(treeWidget);
}
d->checkColumns();
setEnabled(!d->treeWidgets.isEmpty());
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
bool KTreeWidgetSearchLine::itemMatches(const QTreeWidgetItem *item, const QString &pattern) const
{
if (pattern.isEmpty()) {
return true;
}
// If the search column list is populated, search just the columns
// specified. If it is empty default to searching all of the columns.
if (!d->searchColumns.isEmpty()) {
QList<int>::ConstIterator it = d->searchColumns.constBegin();
for (; it != d->searchColumns.constEnd(); ++it) {
if (*it < item->treeWidget()->columnCount() //
&& item->text(*it).indexOf(pattern, 0, d->caseSensitive) >= 0) {
return true;
}
}
} else {
for (int i = 0; i < item->treeWidget()->columnCount(); i++) {
if (item->treeWidget()->columnWidth(i) > 0 //
&& item->text(i).indexOf(pattern, 0, d->caseSensitive) >= 0) {
return true;
}
}
}
return false;
}
void KTreeWidgetSearchLine::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *popup = QLineEdit::createStandardContextMenu();
if (d->canChooseColumns) {
popup->addSeparator();
QMenu *subMenu = popup->addMenu(tr("Search Columns", "@title:menu"));
QAction *allVisibleColumnsAction = subMenu->addAction(tr("All Visible Columns", "@optipn:check"), this, SLOT(_k_slotAllVisibleColumns()));
allVisibleColumnsAction->setCheckable(true);
allVisibleColumnsAction->setChecked(d->searchColumns.isEmpty());
subMenu->addSeparator();
bool allColumnsAreSearchColumns = true;
QActionGroup *group = new QActionGroup(popup);
group->setExclusive(false);
connect(group, SIGNAL(triggered(QAction *)), SLOT(_k_slotColumnActivated(QAction *)));
QHeaderView *const header = d->treeWidgets.first()->header();
for (int j = 0; j < header->count(); j++) {
int i = header->logicalIndex(j);
if (header->isSectionHidden(i)) {
continue;
}
QString columnText = d->treeWidgets.first()->headerItem()->text(i);
QAction *columnAction = subMenu->addAction(d->treeWidgets.first()->headerItem()->icon(i), columnText);
columnAction->setCheckable(true);
columnAction->setChecked(d->searchColumns.isEmpty() || d->searchColumns.contains(i));
columnAction->setData(i);
columnAction->setActionGroup(group);
if (d->searchColumns.isEmpty() || d->searchColumns.indexOf(i) != -1) {
columnAction->setChecked(true);
} else {
allColumnsAreSearchColumns = false;
}
}
allVisibleColumnsAction->setChecked(allColumnsAreSearchColumns);
// searchColumnsMenuActivated() relies on one possible "all" representation
if (allColumnsAreSearchColumns && !d->searchColumns.isEmpty()) {
d->searchColumns.clear();
}
}
popup->exec(event->globalPos());
delete popup;
}
void KTreeWidgetSearchLine::connectTreeWidget(QTreeWidget *treeWidget)
{
connect(treeWidget, SIGNAL(destroyed(QObject *)), this, SLOT(_k_treeWidgetDeleted(QObject *)));
connect(treeWidget->model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(_k_rowsInserted(QModelIndex, int, int)));
}
void KTreeWidgetSearchLine::disconnectTreeWidget(QTreeWidget *treeWidget)
{
disconnect(treeWidget, SIGNAL(destroyed(QObject *)), this, SLOT(_k_treeWidgetDeleted(QObject *)));
disconnect(treeWidget->model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(_k_rowsInserted(QModelIndex, int, int)));
}
bool KTreeWidgetSearchLine::canChooseColumnsCheck()
{
// This is true if either of the following is true:
// there are no listviews connected
if (d->treeWidgets.isEmpty()) {
return false;
}
const QTreeWidget *first = d->treeWidgets.first();
const int numcols = first->columnCount();
// the listviews have only one column,
if (numcols < 2) {
return false;
}
QStringList headers;
headers.reserve(numcols);
for (int i = 0; i < numcols; ++i) {
headers.append(first->headerItem()->text(i));
}
QList<QTreeWidget *>::ConstIterator it = d->treeWidgets.constBegin();
for (++it /* skip the first one */; it != d->treeWidgets.constEnd(); ++it) {
// the listviews have different numbers of columns,
if ((*it)->columnCount() != numcols) {
return false;
}
// the listviews differ in column labels.
QStringList::ConstIterator jt;
int i;
for (i = 0, jt = headers.constBegin(); i < numcols; ++i, ++jt) {
Q_ASSERT(jt != headers.constEnd());
if ((*it)->headerItem()->text(i) != *jt) {
return false;
}
}
}
return true;
}
bool KTreeWidgetSearchLine::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine)
|| keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine)
|| keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)
|| keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage) || keyEvent->key() == Qt::Key_Enter
|| keyEvent->key() == Qt::Key_Return) {
QTreeWidget *first = d->treeWidgets.first();
if (first) {
QApplication::sendEvent(first, event);
return true;
}
}
}
return QLineEdit::event(event);
}
////////////////////////////////////////////////////////////////////////////////
// protected slots
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLinePrivate::_k_queueSearch(const QString &_search)
{
queuedSearches++;
search = _search;
QTimer::singleShot(200, q, SLOT(_k_activateSearch()));
}
void KTreeWidgetSearchLinePrivate::_k_activateSearch()
{
--queuedSearches;
if (queuedSearches == 0) {
q->updateSearch(search);
}
}
#include "moc_ktreewidgetsearchline.cpp"
@@ -0,0 +1,258 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTREEWIDGETSEARCHLINE_H
#define KTREEWIDGETSEARCHLINE_H
#include <QLineEdit>
#include <kitemviews_export.h>
#include <memory>
class QModelIndex;
class QTreeWidget;
class QTreeWidgetItem;
/**
* @class KTreeWidgetSearchLine ktreewidgetsearchline.h KTreeWidgetSearchLine
*
* This class makes it easy to add a search line for filtering the items in
* listviews based on a simple text search.
*
* No changes to the application other than instantiating this class with
* appropriate QTreeWidgets should be needed.
*/
class KITEMVIEWS_EXPORT KTreeWidgetSearchLine : public QLineEdit
{
Q_OBJECT
Q_PROPERTY(Qt::CaseSensitivity caseSensitity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
Q_PROPERTY(bool keepParentsVisible READ keepParentsVisible WRITE setKeepParentsVisible NOTIFY keepParentsVisibleChanged)
public:
/**
* Constructs a KTreeWidgetSearchLine with \a treeWidget being the QTreeWidget to
* be filtered.
*
* If \a treeWidget is null then the widget will be disabled until listviews
* are set with setTreeWidget(), setTreeWidgets() or added with addTreeWidget().
*/
explicit KTreeWidgetSearchLine(QWidget *parent = nullptr, QTreeWidget *treeWidget = nullptr);
/**
* Constructs a KTreeWidgetSearchLine with \a treeWidgets being the list of
* pointers to QTreeWidgets to be filtered.
*
* If \a treeWidgets is empty then the widget will be disabled until listviews
* are set with setTreeWidget(), setTreeWidgets() or added with addTreeWidget().
*/
KTreeWidgetSearchLine(QWidget *parent, const QList<QTreeWidget *> &treeWidgets);
/**
* Destroys the KTreeWidgetSearchLine.
*/
~KTreeWidgetSearchLine() override;
/**
* Returns true if the search is case sensitive. This defaults to false.
*
* @see setCaseSensitive()
*/
Qt::CaseSensitivity caseSensitivity() const;
/**
* Returns the current list of columns that will be searched. If the
* returned list is empty all visible columns will be searched.
*
* @see setSearchColumns
*/
QList<int> searchColumns() const;
/**
* If this is true (the default) then the parents of matched items will also
* be shown.
*
* @see setKeepParentsVisible()
*/
bool keepParentsVisible() const;
/**
* Returns the listview that is currently filtered by the search.
* If there are multiple listviews filtered, it returns 0.
*
* @see setTreeWidget(), treeWidgets()
*/
QTreeWidget *treeWidget() const;
/**
* Returns the list of pointers to listviews that are currently filtered by
* the search.
*
* @see setTreeWidgets(), addTreeWidget(), treeWidget()
*/
QList<QTreeWidget *> treeWidgets() const;
Q_SIGNALS:
/**
* This signal is emitted whenever an item gets hidden or unhidden due
* to it not matching or matching the search string.
*/
void hiddenChanged(QTreeWidgetItem *, bool);
/**
* This signal is emitted when user finished entering filter text or
* when he made a pause long enough, after the QTreeWidget items got filtered
* @param searchString is the text currently entered by the user
* @since 5.0
*/
void searchUpdated(const QString &searchString);
void caseSensitivityChanged(Qt::CaseSensitivity caseSensitivity);
void keepParentsVisibleChanged(bool keepParentsVisible);
public Q_SLOTS:
/**
* Adds a QTreeWidget to the list of listviews filtered by this search line.
* If \a treeWidget is null then the widget will be disabled.
*
* @see treeWidget(), setTreeWidgets(), removeTreeWidget()
*/
void addTreeWidget(QTreeWidget *treeWidget);
/**
* Removes a QTreeWidget from the list of listviews filtered by this search
* line. Does nothing if \a treeWidget is 0 or is not filtered by the quick search
* line.
*
* @see listVew(), setTreeWidgets(), addTreeWidget()
*/
void removeTreeWidget(QTreeWidget *treeWidget);
/**
* Updates search to only make visible the items that match \a pattern. If
* \a s is null then the line edit's text will be used.
*/
virtual void updateSearch(const QString &pattern = QString());
/**
* Make the search case sensitive or case insensitive.
*
* @see caseSenstivity()
*/
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
/**
* When a search is active on a list that's organized into a tree view if
* a parent or ancesestor of an item is does not match the search then it
* will be hidden and as such so too will any children that match.
*
* If this is set to true (the default) then the parents of matching items
* will be shown.
*
* \warning setKeepParentsVisible(true) does not have the expected effect
* on items being added to or removed from the view while a search is active.
* When a new search starts afterwards the behavior will be normal.
*
* @see keepParentsVisible
*/
void setKeepParentsVisible(bool value);
/**
* Sets the list of columns to be searched. The default is to search all,
* visible columns which can be restored by passing \a columns as an empty
* list.
* If listviews to be filtered have different numbers or labels of columns
* this method has no effect.
*
* @see searchColumns
*/
void setSearchColumns(const QList<int> &columns);
/**
* Sets the QTreeWidget that is filtered by this search line, replacing any
* previously filtered listviews. If \a treeWidget is null then the widget will be
* disabled.
*
* @see treeWidget(), setTreeWidgets()
*/
void setTreeWidget(QTreeWidget *treeWidget);
/**
* Sets QTreeWidgets that are filtered by this search line, replacing any
* previously filtered listviews. If \a treeWidgets is empty then the widget will
* be disabled.
*
* @see treeWidgets(), addTreeWidget(), setTreeWidget()
*/
void setTreeWidgets(const QList<QTreeWidget *> &treeWidgets);
protected:
/**
* Returns true if \a item matches the search \a pattern. This will be evaluated
* based on the value of caseSensitive(). This can be overridden in
* subclasses to implement more complicated matching schemes.
*/
virtual bool itemMatches(const QTreeWidgetItem *item, const QString &pattern) const;
/**
* Re-implemented for internal reasons. API not affected.
*/
void contextMenuEvent(QContextMenuEvent *) override;
/**
* Updates search to only make visible appropriate items in \a treeWidget. If
* \a treeWidget is null then nothing is done.
*/
virtual void updateSearch(QTreeWidget *treeWidget);
/**
* Connects signals of this listview to the appropriate slots of the search
* line.
*/
virtual void connectTreeWidget(QTreeWidget *);
/**
* Disconnects signals of a listviews from the search line.
*/
virtual void disconnectTreeWidget(QTreeWidget *);
/**
* Checks columns in all listviews and decides whether choosing columns to
* filter on makes any sense.
*
* Returns false if either of the following is true:
* * there are no listviews connected,
* * the listviews have different numbers of columns,
* * the listviews have only one column,
* * the listviews differ in column labels.
*
* Otherwise it returns true.
*
* @see setSearchColumns()
*/
virtual bool canChooseColumnsCheck();
/**
* Re-implemented for internal reasons. API not affected.
*/
bool event(QEvent *event) override;
private:
friend class KTreeWidgetSearchLinePrivate;
std::unique_ptr<class KTreeWidgetSearchLinePrivate> const d;
Q_PRIVATE_SLOT(d, void _k_rowsInserted(const QModelIndex &, int, int) const)
Q_PRIVATE_SLOT(d, void _k_treeWidgetDeleted(QObject *))
Q_PRIVATE_SLOT(d, void _k_slotColumnActivated(QAction *))
Q_PRIVATE_SLOT(d, void _k_slotAllVisibleColumns())
Q_PRIVATE_SLOT(d, void _k_queueSearch(const QString &))
Q_PRIVATE_SLOT(d, void _k_activateSearch())
};
#endif
@@ -0,0 +1,60 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktreewidgetsearchlinewidget.h"
#include "ktreewidgetsearchline.h"
#include <QHBoxLayout>
#include <QTreeWidget>
class KTreeWidgetSearchLineWidgetPrivate
{
public:
QTreeWidget *treeWidget = nullptr;
KTreeWidgetSearchLine *searchLine = nullptr;
};
KTreeWidgetSearchLineWidget::KTreeWidgetSearchLineWidget(QWidget *parent, QTreeWidget *treeWidget)
: QWidget(parent)
, d(new KTreeWidgetSearchLineWidgetPrivate)
{
d->treeWidget = treeWidget;
// can't call createWidgets directly because it calls virtual functions
// that might not work if called directly from here due to how inheritance works
QMetaObject::invokeMethod(this, "createWidgets", Qt::QueuedConnection);
}
KTreeWidgetSearchLineWidget::~KTreeWidgetSearchLineWidget() = default;
KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::createSearchLine(QTreeWidget *treeWidget) const
{
return new KTreeWidgetSearchLine(const_cast<KTreeWidgetSearchLineWidget *>(this), treeWidget);
}
void KTreeWidgetSearchLineWidget::createWidgets()
{
searchLine()->show();
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(d->searchLine);
setFocusProxy(searchLine());
}
KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::searchLine() const
{
if (!d->searchLine) {
d->searchLine = createSearchLine(d->treeWidget);
}
return d->searchLine;
}
#include "moc_ktreewidgetsearchlinewidget.cpp"
@@ -0,0 +1,71 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTREEWIDGETSEARCHLINEWIDGET_H
#define KTREEWIDGETSEARCHLINEWIDGET_H
#include <QWidget>
#include <kitemviews_export.h>
#include <memory>
class QModelIndex;
class QTreeWidget;
class KTreeWidgetSearchLine;
/**
* @class KTreeWidgetSearchLineWidget ktreewidgetsearchlinewidget.h KTreeWidgetSearchLineWidget
*
* Creates a widget featuring a KTreeWidgetSearchLine, a label with the text
* "Search" and a button to clear the search.
*/
class KITEMVIEWS_EXPORT KTreeWidgetSearchLineWidget : public QWidget
{
Q_OBJECT
public:
/**
* Creates a KTreeWidgetSearchLineWidget for \a treeWidget with \a parent as the
* parent.
*/
explicit KTreeWidgetSearchLineWidget(QWidget *parent = nullptr, QTreeWidget *treeWidget = nullptr);
/**
* Destroys the KTreeWidgetSearchLineWidget
*/
~KTreeWidgetSearchLineWidget() override;
/**
* Returns a pointer to the search line.
*/
KTreeWidgetSearchLine *searchLine() const;
protected Q_SLOTS:
/**
* Creates the widgets inside of the widget. This is called from the
* constructor via a single shot timer so that it it guaranteed to run
* after construction is complete. This makes it suitable for overriding in
* subclasses.
*/
virtual void createWidgets();
protected:
/**
* Creates the search line. This can be useful to reimplement in cases where
* a KTreeWidgetSearchLine subclass is used.
*
* It is const because it is be called from searchLine(), which to the user
* doesn't conceptually alter the widget.
*/
virtual KTreeWidgetSearchLine *createSearchLine(QTreeWidget *treeWidget) const;
private:
std::unique_ptr<class KTreeWidgetSearchLineWidgetPrivate> const d;
};
#endif
@@ -0,0 +1,283 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007-2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kwidgetitemdelegate.h"
#include "kwidgetitemdelegate_p.h"
#include <QAbstractItemView>
#include <QApplication>
#include <QCursor>
#include <QPainter>
#include <QStyleOption>
#include <QTimer>
#include <QTreeView>
#include "kwidgetitemdelegatepool_p.h"
Q_DECLARE_METATYPE(QList<QEvent::Type>)
/**
KWidgetItemDelegatePrivate class that helps to provide binary compatibility between releases.
@internal
*/
//@cond PRIVATE
KWidgetItemDelegatePrivate::KWidgetItemDelegatePrivate(KWidgetItemDelegate *q, QObject *parent)
: QObject(parent)
, widgetPool(new KWidgetItemDelegatePool(q))
, q(q)
{
}
KWidgetItemDelegatePrivate::~KWidgetItemDelegatePrivate()
{
if (!viewDestroyed) {
widgetPool->fullClear();
}
delete widgetPool;
}
void KWidgetItemDelegatePrivate::_k_slotRowsInserted(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(end);
// We need to update the rows behind the inserted row as well because the widgets need to be
// moved to their new position
updateRowRange(parent, start, model->rowCount(parent), false);
}
void KWidgetItemDelegatePrivate::_k_slotRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
updateRowRange(parent, start, end, true);
}
void KWidgetItemDelegatePrivate::_k_slotRowsRemoved(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(end);
// We need to update the rows that come behind the deleted rows because the widgets need to be
// moved to the new position
updateRowRange(parent, start, model->rowCount(parent), false);
}
void KWidgetItemDelegatePrivate::_k_slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
for (int j = topLeft.column(); j <= bottomRight.column(); ++j) {
const QModelIndex index = model->index(i, j, topLeft.parent());
widgetPool->findWidgets(index, optionView(index));
}
}
}
void KWidgetItemDelegatePrivate::_k_slotLayoutChanged()
{
const auto lst = widgetPool->invalidIndexesWidgets();
for (QWidget *widget : lst) {
widget->setVisible(false);
}
QTimer::singleShot(0, this, SLOT(initializeModel()));
}
void KWidgetItemDelegatePrivate::_k_slotModelReset()
{
widgetPool->fullClear();
QTimer::singleShot(0, this, SLOT(initializeModel()));
}
void KWidgetItemDelegatePrivate::_k_slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
const auto lstSelected = selected.indexes();
for (const QModelIndex &index : lstSelected) {
widgetPool->findWidgets(index, optionView(index));
}
const auto lstDeselected = deselected.indexes();
for (const QModelIndex &index : lstDeselected) {
widgetPool->findWidgets(index, optionView(index));
}
}
void KWidgetItemDelegatePrivate::updateRowRange(const QModelIndex &parent, int start, int end, bool isRemoving)
{
int i = start;
while (i <= end) {
for (int j = 0; j < model->columnCount(parent); ++j) {
const QModelIndex index = model->index(i, j, parent);
const QList<QWidget *> widgetList =
widgetPool->findWidgets(index,
optionView(index),
isRemoving ? KWidgetItemDelegatePool::NotUpdateWidgets : KWidgetItemDelegatePool::UpdateWidgets);
if (isRemoving) {
for (QWidget *widget : widgetList) {
const QModelIndex idx = widgetPool->d->widgetInIndex[widget];
widgetPool->d->usedWidgets.remove(idx);
widgetPool->d->widgetInIndex.remove(widget);
delete widget;
}
}
}
i++;
}
}
inline QStyleOptionViewItem KWidgetItemDelegatePrivate::optionView(const QModelIndex &index)
{
QStyleOptionViewItem optionView;
optionView.initFrom(itemView->viewport());
optionView.rect = itemView->visualRect(index);
optionView.decorationSize = itemView->iconSize();
return optionView;
}
void KWidgetItemDelegatePrivate::initializeModel(const QModelIndex &parent)
{
if (!model) {
return;
}
for (int i = 0; i < model->rowCount(parent); ++i) {
for (int j = 0; j < model->columnCount(parent); ++j) {
const QModelIndex index = model->index(i, j, parent);
if (index.isValid()) {
widgetPool->findWidgets(index, optionView(index));
}
}
// Check if we need to go recursively through the children of parent (if any) to initialize
// all possible indexes that are shown.
const QModelIndex index = model->index(i, 0, parent);
if (index.isValid() && model->hasChildren(index)) {
initializeModel(index);
}
}
}
//@endcond
KWidgetItemDelegate::KWidgetItemDelegate(QAbstractItemView *itemView, QObject *parent)
: QAbstractItemDelegate(parent)
, d(new KWidgetItemDelegatePrivate(this))
{
Q_ASSERT(itemView);
itemView->setMouseTracking(true);
itemView->viewport()->setAttribute(Qt::WA_Hover);
d->itemView = itemView;
itemView->viewport()->installEventFilter(d.get()); // mouse events
itemView->installEventFilter(d.get()); // keyboard events
if (qobject_cast<QTreeView *>(itemView)) {
connect(itemView, SIGNAL(collapsed(QModelIndex)), d.get(), SLOT(initializeModel()));
connect(itemView, SIGNAL(expanded(QModelIndex)), d.get(), SLOT(initializeModel()));
}
}
KWidgetItemDelegate::~KWidgetItemDelegate() = default;
QAbstractItemView *KWidgetItemDelegate::itemView() const
{
return d->itemView;
}
QPersistentModelIndex KWidgetItemDelegate::focusedIndex() const
{
const QPersistentModelIndex idx = d->widgetPool->d->widgetInIndex.value(QApplication::focusWidget());
if (idx.isValid()) {
return idx;
}
// Use the mouse position, if the widget refused to take keyboard focus.
const QPoint pos = d->itemView->viewport()->mapFromGlobal(QCursor::pos());
return d->itemView->indexAt(pos);
}
//@cond PRIVATE
bool KWidgetItemDelegatePrivate::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Destroy) {
// we care for the view since it deletes the widgets (parentage).
// if the view hasn't been deleted, it might be that just the
// delegate is removed from it, in which case we need to remove the widgets
// manually, otherwise they still get drawn.
if (watched == itemView) {
viewDestroyed = true;
}
return false;
}
Q_ASSERT(itemView);
// clang-format off
if (model != itemView->model()) {
if (model) {
disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
disconnect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
disconnect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
}
model = itemView->model();
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
connect(model, SIGNAL(layoutChanged()), q, SLOT(_k_slotLayoutChanged()));
connect(model, SIGNAL(modelReset()), q, SLOT(_k_slotModelReset()));
QTimer::singleShot(0, this, SLOT(initializeModel()));
}
if (selectionModel != itemView->selectionModel()) {
if (selectionModel) {
disconnect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
}
selectionModel = itemView->selectionModel();
connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
QTimer::singleShot(0, this, SLOT(initializeModel()));
}
// clang-format on
switch (event->type()) {
case QEvent::Polish:
case QEvent::Resize:
if (!qobject_cast<QAbstractItemView *>(watched)) {
QTimer::singleShot(0, this, SLOT(initializeModel()));
}
break;
case QEvent::FocusIn:
case QEvent::FocusOut:
if (qobject_cast<QAbstractItemView *>(watched)) {
const auto lst = selectionModel->selectedIndexes();
for (const QModelIndex &index : lst) {
if (index.isValid()) {
widgetPool->findWidgets(index, optionView(index));
}
}
}
break;
default:
break;
}
return QObject::eventFilter(watched, event);
}
//@endcond
void KWidgetItemDelegate::setBlockedEventTypes(QWidget *widget, const QList<QEvent::Type> &types) const
{
widget->setProperty("goya:blockedEventTypes", QVariant::fromValue(types));
}
QList<QEvent::Type> KWidgetItemDelegate::blockedEventTypes(QWidget *widget) const
{
return widget->property("goya:blockedEventTypes").value<QList<QEvent::Type>>();
}
void KWidgetItemDelegate::resetModel()
{
d->_k_slotModelReset();
}
#include "moc_kwidgetitemdelegate.cpp"
#include "moc_kwidgetitemdelegate_p.cpp"
@@ -0,0 +1,151 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007-2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KWIDGETITEMDELEGATE_H
#define KWIDGETITEMDELEGATE_H
#include <QAbstractItemDelegate>
#include <QEvent>
#include <QList>
#include <QPersistentModelIndex>
#include <memory>
#include <kitemviews_export.h>
class QObject;
class QPainter;
class QStyleOption;
class QStyleOptionViewItem;
class QAbstractItemView;
class QItemSelection;
class KWidgetItemDelegatePrivate;
class KWidgetItemDelegatePool;
/**
* @class KWidgetItemDelegate kwidgetitemdelegate.h KWidgetItemDelegate
*
* This class allows to create item delegates embedding simple widgets to interact
* with items. For instance you can add push buttons, line edits, etc. to your delegate
* and use them to modify the state of your model.
*
* Porting from KF5 to KF6:
*
* The signature of the virtual method
* KWidgetItemDelegate::updateItemWidgets(const QList<QWidget *>, const QStyleOptionViewItem &, const QPersistentModelIndex &) const
* was changed to ;
* KWidgetItemDelegate::updateItemWidgets(const QList<QWidget *> &, const QStyleOptionViewItem &, const QPersistentModelIndex &) const.
*
* @since 4.1
*/
class KITEMVIEWS_EXPORT KWidgetItemDelegate : public QAbstractItemDelegate
{
Q_OBJECT
public:
/**
* Creates a new ItemDelegate to be used with a given itemview.
*
* @param itemView the item view the new delegate will monitor
* @param parent the parent of this delegate
*/
explicit KWidgetItemDelegate(QAbstractItemView *itemView, QObject *parent = nullptr);
/**
* Destroys an ItemDelegate.
*/
~KWidgetItemDelegate() override;
/**
* Retrieves the item view this delegate is monitoring.
*
* @return the item view this delegate is monitoring
*/
QAbstractItemView *itemView() const;
/**
* Retrieves the currently focused index. An invalid index if none is focused.
*
* @return the current focused index, or QPersistentModelIndex() if none is focused.
*/
QPersistentModelIndex focusedIndex() const;
/**
* trigger a modelReset
*/
void resetModel();
protected:
/**
* Creates the list of widgets needed for an item.
*
* @note No initialization of the widgets is supposed to happen here.
* The widgets will be initialized based on needs for a given item.
*
* @note If you want to connect some widget signals to any slot, you should
* do it here.
*
* @param index the index to create widgets for
*
* @return the list of newly created widgets which will be used to interact with an item.
* @see updateItemWidgets()
*/
virtual QList<QWidget *> createItemWidgets(const QModelIndex &index) const = 0;
/**
* Updates a list of widgets for its use inside of the delegate (painting or
* event handling).
*
* @note All the positioning and sizing should be done in item coordinates.
*
* @warning Do not make widget connections in here, since this method will
* be called very regularly.
*
* @param widgets the widgets to update
* @param option the current set of style options for the view.
* @param index the model index of the item currently manipulated.
*/
virtual void updateItemWidgets(const QList<QWidget *> &widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const = 0;
/**
* Sets the list of event @p types that a @p widget will block.
*
* Blocked events are not passed to the view. This way you can prevent an item
* from being selected when a button is clicked for instance.
*
* @param widget the widget which must block events
* @param types the list of event types the widget must block
*/
void setBlockedEventTypes(QWidget *widget, const QList<QEvent::Type> &types) const;
/**
* Retrieves the list of blocked event types for the given widget.
*
* @param widget the specified widget.
*
* @return the list of blocked event types, can be empty if no events are blocked.
*/
QList<QEvent::Type> blockedEventTypes(QWidget *widget) const;
private:
//@cond PRIVATE
friend class KWidgetItemDelegatePool;
friend class KWidgetItemDelegateEventListener;
std::unique_ptr<class KWidgetItemDelegatePrivate> const d;
Q_PRIVATE_SLOT(d, void _k_slotRowsInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d, void _k_slotRowsAboutToBeRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d, void _k_slotRowsRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d, void _k_slotDataChanged(const QModelIndex &, const QModelIndex &))
Q_PRIVATE_SLOT(d, void _k_slotLayoutChanged())
Q_PRIVATE_SLOT(d, void _k_slotModelReset())
Q_PRIVATE_SLOT(d, void _k_slotSelectionChanged(const QItemSelection &, const QItemSelection &))
//@endcond
};
#endif
@@ -0,0 +1,57 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/**
* This class is necessary to be installed because of the templated method.
* It is private in the sense of having clean the public header.
* Do not forget that this file _has_ to be installed.
*/
#ifndef KWIDGETITEMDELEGATE_P_H
#define KWIDGETITEMDELEGATE_P_H
#include <QItemSelectionModel>
class KWidgetItemDelegate;
class KWidgetItemDelegatePrivate : public QObject
{
Q_OBJECT
public:
explicit KWidgetItemDelegatePrivate(KWidgetItemDelegate *q, QObject *parent = nullptr);
~KWidgetItemDelegatePrivate() override;
void _k_slotRowsInserted(const QModelIndex &parent, int start, int end);
void _k_slotRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
void _k_slotRowsRemoved(const QModelIndex &parent, int start, int end);
void _k_slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void _k_slotLayoutChanged();
void _k_slotModelReset();
void _k_slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void updateRowRange(const QModelIndex &parent, int start, int end, bool isRemoving);
QStyleOptionViewItem optionView(const QModelIndex &index);
public Q_SLOTS:
void initializeModel(const QModelIndex &parent = QModelIndex());
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
public:
QAbstractItemView *itemView = nullptr;
KWidgetItemDelegatePool *const widgetPool;
QAbstractItemModel *model = nullptr;
QItemSelectionModel *selectionModel = nullptr;
bool viewDestroyed = false;
KWidgetItemDelegate *const q;
};
#endif
@@ -0,0 +1,208 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kwidgetitemdelegatepool_p.h"
#include <QAbstractItemView>
#include <QAbstractProxyModel>
#include <QApplication>
#include <QHash>
#include <QInputEvent>
#include <QList>
#include <QMetaMethod>
#include <QWidget>
#include <qobjectdefs.h>
#include "kwidgetitemdelegate.h"
#include "kwidgetitemdelegate_p.h"
#include <kitemviews_debug.h>
/**
Private class that helps to provide binary compatibility between releases.
@internal
*/
//@cond PRIVATE
class KWidgetItemDelegateEventListener : public QObject
{
public:
KWidgetItemDelegateEventListener(KWidgetItemDelegatePoolPrivate *poolPrivate, QObject *parent = nullptr)
: QObject(parent)
, poolPrivate(poolPrivate)
{
}
bool eventFilter(QObject *watched, QEvent *event) override;
private:
KWidgetItemDelegatePoolPrivate *const poolPrivate;
};
KWidgetItemDelegatePoolPrivate::KWidgetItemDelegatePoolPrivate(KWidgetItemDelegate *d)
: delegate(d)
, eventListener(new KWidgetItemDelegateEventListener(this))
{
}
KWidgetItemDelegatePool::KWidgetItemDelegatePool(KWidgetItemDelegate *delegate)
: d(new KWidgetItemDelegatePoolPrivate(delegate))
{
}
KWidgetItemDelegatePool::~KWidgetItemDelegatePool()
{
delete d->eventListener;
delete d;
}
QList<QWidget *>
KWidgetItemDelegatePool::findWidgets(const QPersistentModelIndex &idx, const QStyleOptionViewItem &option, UpdateWidgetsEnum updateWidgets) const
{
QList<QWidget *> result;
if (!idx.isValid()) {
return result;
}
QModelIndex index;
if (const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel *>(idx.model())) {
index = proxyModel->mapToSource(idx);
} else {
index = idx;
}
if (!index.isValid()) {
return result;
}
if (d->usedWidgets.contains(index)) {
result = d->usedWidgets[index];
} else {
result = d->delegate->createItemWidgets(index);
d->usedWidgets[index] = result;
for (QWidget *widget : std::as_const(result)) {
d->widgetInIndex[widget] = index;
widget->setParent(d->delegate->d->itemView->viewport());
widget->installEventFilter(d->eventListener);
widget->setVisible(true);
}
}
if (updateWidgets == UpdateWidgets) {
for (QWidget *widget : std::as_const(result)) {
widget->setVisible(true);
}
d->delegate->updateItemWidgets(result, option, idx);
for (QWidget *widget : std::as_const(result)) {
widget->move(widget->x() + option.rect.left(), widget->y() + option.rect.top());
}
}
return result;
}
QList<QWidget *> KWidgetItemDelegatePool::invalidIndexesWidgets() const
{
QList<QWidget *> result;
QHashIterator<QWidget *, QPersistentModelIndex> i(d->widgetInIndex);
while (i.hasNext()) {
i.next();
const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel *>(d->delegate->d->model);
QModelIndex index;
if (proxyModel) {
index = proxyModel->mapFromSource(i.value());
} else {
index = i.value();
}
if (!index.isValid()) {
result << i.key();
}
}
return result;
}
void KWidgetItemDelegatePool::fullClear()
{
d->clearing = true;
qDeleteAll(d->widgetInIndex.keys());
d->clearing = false;
d->usedWidgets.clear();
d->widgetInIndex.clear();
}
bool KWidgetItemDelegateEventListener::eventFilter(QObject *watched, QEvent *event)
{
QWidget *widget = static_cast<QWidget *>(watched);
if (event->type() == QEvent::Destroy && !poolPrivate->clearing) {
qCWarning(KITEMVIEWS_LOG) << "User of KWidgetItemDelegate should not delete widgets created by createItemWidgets!";
// assume the application has kept a list of widgets and tries to delete them manually
// they have been reparented to the view in any case, so no leaking occurs
poolPrivate->widgetInIndex.remove(widget);
}
if (dynamic_cast<QInputEvent *>(event) && !poolPrivate->delegate->blockedEventTypes(widget).contains(event->type())) {
QWidget *viewport = poolPrivate->delegate->d->itemView->viewport();
switch (event->type()) {
case QEvent::MouseMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
QMouseEvent evt(event->type(),
mouseEvent->position(),
viewport->mapFromGlobal(mouseEvent->globalPosition()),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers());
QApplication::sendEvent(viewport, &evt);
} break;
case QEvent::Wheel: {
QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
QWheelEvent evt(viewport->mapFromGlobal(wheelEvent->position().toPoint()),
viewport->mapFromGlobal(wheelEvent->globalPosition().toPoint()),
wheelEvent->pixelDelta(),
wheelEvent->angleDelta(),
wheelEvent->buttons(),
wheelEvent->modifiers(),
wheelEvent->phase(),
wheelEvent->inverted(),
wheelEvent->source());
QApplication::sendEvent(viewport, &evt);
} break;
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity: {
QTabletEvent *tabletEvent = static_cast<QTabletEvent *>(event);
QTabletEvent evt(event->type(),
tabletEvent->pointingDevice(),
viewport->mapFromGlobal(tabletEvent->globalPosition()),
tabletEvent->globalPosition(),
tabletEvent->pressure(),
tabletEvent->xTilt(),
tabletEvent->yTilt(),
tabletEvent->tangentialPressure(),
tabletEvent->rotation(),
tabletEvent->z(),
tabletEvent->modifiers(),
tabletEvent->button(),
tabletEvent->buttons());
QApplication::sendEvent(viewport, &evt);
break;
}
default:
QApplication::sendEvent(viewport, event);
break;
}
}
return QObject::eventFilter(watched, event);
}
//@endcond
@@ -0,0 +1,92 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KWIDGETITEMDELEGATEPOOL_P_H
#define KWIDGETITEMDELEGATEPOOL_P_H
#include <QHash>
#include <QList>
#include <QPersistentModelIndex>
class QWidget;
class QStyleOptionViewItem;
class KWidgetItemDelegate;
class KWidgetItemDelegatePoolPrivate;
/**
* @internal
*/
class KWidgetItemDelegatePool
{
public:
enum UpdateWidgetsEnum {
UpdateWidgets = 0,
NotUpdateWidgets,
};
/**
* Creates a new ItemDelegatePool.
*
* @param delegate the ItemDelegate for this pool.
*/
KWidgetItemDelegatePool(KWidgetItemDelegate *delegate);
/**
* Destroys an ItemDelegatePool.
*/
~KWidgetItemDelegatePool();
KWidgetItemDelegatePool(const KWidgetItemDelegatePool &) = delete;
KWidgetItemDelegatePool &operator=(const KWidgetItemDelegatePool &) = delete;
/**
* @brief Returns the widget associated to @p index and @p widget
* @param index The index to search into.
* @param option a QStyleOptionViewItem.
* @return A QList of the pointers to the widgets found.
* @internal
*/
QList<QWidget *> findWidgets(const QPersistentModelIndex &index, const QStyleOptionViewItem &option, UpdateWidgetsEnum updateWidgets = UpdateWidgets) const;
/**
* @internal
*/
QList<QWidget *> invalidIndexesWidgets() const;
/**
* @internal
*/
void fullClear();
private:
friend class KWidgetItemDelegate;
friend class KWidgetItemDelegatePrivate;
KWidgetItemDelegatePoolPrivate *const d;
};
class KWidgetItemDelegateEventListener;
/**
* @internal
*/
class KWidgetItemDelegatePoolPrivate
{
public:
KWidgetItemDelegatePoolPrivate(KWidgetItemDelegate *d);
KWidgetItemDelegate *delegate;
KWidgetItemDelegateEventListener *eventListener;
QHash<QPersistentModelIndex, QList<QWidget *>> usedWidgets;
QHash<QWidget *, QPersistentModelIndex> widgetInIndex;
bool clearing = false;
};
#endif