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:
@@ -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}"
|
||||
)
|
||||
@@ -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 ¤t, 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
|
||||
Reference in New Issue
Block a user