Advance Wayland and KDE package bring-up

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,10 @@
add_subdirectory(core)
if (TARGET Qt6::Qml)
# add_subdirectory(qml)
endif()
ecm_qt_install_logging_categories(
EXPORT KITEMMODELS
FILE kitemmodels.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
@@ -0,0 +1,108 @@
add_library(KF6ItemModels)
add_library(KF6::ItemModels ALIAS KF6ItemModels)
# needed for the QML module
qt_extract_metatypes(KF6ItemModels)
set_target_properties(KF6ItemModels PROPERTIES
VERSION ${KITEMMODELS_VERSION}
SOVERSION ${KITEMMODELS_SOVERSION}
EXPORT_NAME ItemModels
)
target_sources(KF6ItemModels PRIVATE
kbihash_p.h
kbreadcrumbselectionmodel.cpp
kbreadcrumbselectionmodel.h
kcheckableproxymodel.cpp
kcheckableproxymodel.h
kcolumnheadersmodel.cpp
kcolumnheadersmodel.h
kdescendantsproxymodel.cpp
kdescendantsproxymodel.h
kextracolumnsproxymodel.cpp
kextracolumnsproxymodel.h
klinkitemselectionmodel.cpp
klinkitemselectionmodel.h
kmodelindexproxymapper.cpp
kmodelindexproxymapper.h
knumbermodel.cpp
knumbermodel.h
krearrangecolumnsproxymodel.cpp
krearrangecolumnsproxymodel.h
kselectionproxymodel.cpp
kselectionproxymodel.h
kvoidpointerfactory_p.h
)
ecm_qt_declare_logging_category(KF6ItemModels
HEADER kitemmodels_debug.h
IDENTIFIER KITEMMODELS_LOG
CATEGORY_NAME kf.itemmodels.core
OLD_CATEGORY_NAMES kf5.kitemmodels
DESCRIPTION "KItemModels (Core)"
EXPORT KITEMMODELS
)
ecm_generate_export_header(KF6ItemModels
BASE_NAME KItemModels
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(KF6ItemModels INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KItemModels>")
target_link_libraries(KF6ItemModels PUBLIC Qt6::Core)
ecm_generate_headers(KItemModels_HEADERS
HEADER_NAMES
KBreadcrumbSelectionModel
KCheckableProxyModel
KColumnHeadersModel
KDescendantsProxyModel
KExtraColumnsProxyModel
KLinkItemSelectionModel
KModelIndexProxyMapper
KNumberModel
KRearrangeColumnsProxyModel
KSelectionProxyModel
REQUIRED_HEADERS KItemModels_HEADERS
)
install(TARGETS KF6ItemModels EXPORT KF6ItemModelsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kitemmodels_export.h
${KItemModels_HEADERS}
${KItemModels_Legacy_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KItemModels
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KF6ItemModels_QCH
NAME KItemModels
BASE_NAME KF6ItemModels
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KItemModels_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KITEMMODELS_EXPORT
"KITEMMODELS_DEPRECATED_VERSION(x, y, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,618 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KBIHASH_P_H
#define KBIHASH_P_H
#include <QHash>
#include <QMap>
#include <QDebug>
template<typename LeftContainer, typename RightContainer>
class KBiAssociativeContainer;
template<typename LeftContainer, typename RightContainer>
QDebug operator<<(QDebug out, const KBiAssociativeContainer<LeftContainer, RightContainer> &container);
template<typename LeftContainer, typename RightContainer>
QDataStream &operator<<(QDataStream &out, const KBiAssociativeContainer<LeftContainer, RightContainer> &container);
template<typename LeftContainer, typename RightContainer>
QDataStream &operator>>(QDataStream &in, KBiAssociativeContainer<LeftContainer, RightContainer> &container);
template<typename LeftContainer, typename RightContainer>
class KBiAssociativeContainer
{
// We need to convert from a QHash::iterator or QMap::iterator
// to a KBiAssociativeContainer::iterator (left or right)
// Do do that we use this implicit ctor. We partially specialize
// it for QHash and QMap types.
// Our iterator inherits from this struct to get the implicit ctor,
// and this struct must inherit from the QHash or QMap iterator.
template<typename Container, typename T, typename U>
struct _iterator_impl_ctor : public Container::iterator {
_iterator_impl_ctor(typename Container::iterator it);
};
template<typename T, typename U>
struct _iterator_impl_ctor<QHash<T, U>, T, U> : public QHash<T, U>::iterator {
/* implicit */ _iterator_impl_ctor(const typename QHash<T, U>::iterator it)
: QHash<T, U>::iterator(it)
{
}
};
template<typename T, typename U>
struct _iterator_impl_ctor<QMap<T, U>, T, U> : public QMap<T, U>::iterator {
/* implicit */ _iterator_impl_ctor(const typename QMap<T, U>::iterator it)
: QMap<T, U>::iterator(it)
{
}
};
public:
typedef typename RightContainer::mapped_type left_type;
typedef typename LeftContainer::mapped_type right_type;
template<typename Container>
class _iterator : public _iterator_impl_ctor<Container, typename Container::key_type, typename Container::mapped_type>
{
public:
explicit inline _iterator(void *data)
: Container::iterator(data)
{
}
/* implicit */ _iterator(const typename Container::iterator it)
: _iterator_impl_ctor<Container, typename Container::key_type, typename Container::mapped_type>(it)
{
}
inline const typename Container::mapped_type &value() const
{
return Container::iterator::value();
}
inline const typename Container::mapped_type &operator*() const
{
return Container::iterator::operator*();
}
inline const typename Container::mapped_type *operator->() const
{
return Container::iterator::operator->();
}
private:
#ifndef Q_CC_MSVC
using Container::iterator::operator*;
using Container::iterator::operator->;
using Container::iterator::value;
#endif
};
typedef _iterator<LeftContainer> left_iterator;
typedef typename LeftContainer::const_iterator left_const_iterator;
typedef _iterator<RightContainer> right_iterator;
typedef typename RightContainer::const_iterator right_const_iterator;
inline KBiAssociativeContainer()
{
}
inline KBiAssociativeContainer(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
*this = other;
}
const KBiAssociativeContainer<LeftContainer, RightContainer> &operator=(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
_leftToRight = other._leftToRight;
_rightToLeft = other._rightToLeft;
return *this;
}
inline bool removeLeft(left_type t)
{
const right_type u = _leftToRight.take(t);
return _rightToLeft.remove(u) != 0;
}
inline bool removeRight(right_type u)
{
const left_type t = _rightToLeft.take(u);
return _leftToRight.remove(t) != 0;
}
inline right_type takeLeft(left_type t)
{
const right_type u = _leftToRight.take(t);
_rightToLeft.remove(u);
return u;
}
inline left_type takeRight(right_type u)
{
const left_type t = _rightToLeft.take(u);
_leftToRight.remove(t);
return t;
}
inline left_type rightToLeft(right_type u) const
{
return _rightToLeft.value(u);
}
inline right_type leftToRight(left_type t) const
{
return _leftToRight.value(t);
}
inline bool leftContains(left_type t) const
{
return _leftToRight.contains(t);
}
inline bool rightContains(right_type u) const
{
return _rightToLeft.contains(u);
}
inline int size() const
{
return _leftToRight.size();
}
inline int count() const
{
return _leftToRight.count();
}
inline int capacity() const
{
return _leftToRight.capacity();
}
void reserve(int size)
{
_leftToRight.reserve(size);
_rightToLeft.reserve(size);
}
inline void squeeze()
{
_leftToRight.squeeze();
_rightToLeft.squeeze();
}
inline void detach()
{
_leftToRight.detach();
_rightToLeft.detach();
}
inline bool isDetached() const
{
return _leftToRight.isDetached();
}
inline void setSharable(bool sharable)
{
_leftToRight.setSharable(sharable);
_rightToLeft.setSharable(sharable);
}
inline bool isSharedWith(const KBiAssociativeContainer<RightContainer, LeftContainer> &other) const
{
return _leftToRight.isSharedWith(other._leftToRight) && _rightToLeft.isSharedWith(other._leftToRight);
}
void clear()
{
_leftToRight.clear();
_rightToLeft.clear();
}
QList<left_type> leftValues() const
{
return _leftToRight.keys();
}
QList<right_type> rightValues() const
{
return _rightToLeft.keys();
}
right_iterator eraseRight(right_iterator it)
{
Q_ASSERT(it != rightEnd());
_leftToRight.remove(it.value());
return _rightToLeft.erase(it);
}
left_iterator eraseLeft(left_iterator it)
{
Q_ASSERT(it != leftEnd());
_rightToLeft.remove(it.value());
return _leftToRight.erase(it);
}
left_iterator findLeft(left_type t)
{
return _leftToRight.find(t);
}
left_const_iterator findLeft(left_type t) const
{
return _leftToRight.find(t);
}
left_const_iterator constFindLeft(left_type t) const
{
return _leftToRight.constFind(t);
}
right_iterator findRight(right_type u)
{
return _rightToLeft.find(u);
}
right_const_iterator findRight(right_type u) const
{
return _rightToLeft.find(u);
}
right_const_iterator constFindRight(right_type u) const
{
return _rightToLeft.find(u);
}
left_iterator insert(left_type t, right_type u)
{
// biHash.insert(5, 7); // creates 5->7 in _leftToRight and 7->5 in _rightToLeft
// biHash.insert(5, 9); // replaces 5->7 with 5->9 in _leftToRight and inserts 9->5 in _rightToLeft.
// The 7->5 in _rightToLeft would be dangling, so we remove it before insertion.
// This means we need to hash u and t up to twice each. Could probably be done better using QHashNode.
if (_leftToRight.contains(t)) {
_rightToLeft.remove(_leftToRight.take(t));
}
if (_rightToLeft.contains(u)) {
_leftToRight.remove(_rightToLeft.take(u));
}
_rightToLeft.insert(u, t);
return _leftToRight.insert(t, u);
}
KBiAssociativeContainer<LeftContainer, RightContainer> &intersect(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
typename KBiAssociativeContainer<RightContainer, LeftContainer>::left_iterator it = leftBegin();
while (it != leftEnd()) {
if (!other.leftContains(it.key())) {
it = eraseLeft(it);
} else {
++it;
}
}
return *this;
}
KBiAssociativeContainer<LeftContainer, RightContainer> &subtract(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
typename KBiAssociativeContainer<RightContainer, LeftContainer>::left_iterator it = leftBegin();
while (it != leftEnd()) {
if (other._leftToRight.contains(it.key())) {
it = eraseLeft(it);
} else {
++it;
}
}
return *this;
}
KBiAssociativeContainer<LeftContainer, RightContainer> &unite(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
typename LeftContainer::const_iterator it = other._leftToRight.constBegin();
const typename LeftContainer::const_iterator end = other._leftToRight.constEnd();
while (it != end) {
const left_type key = it.key();
if (!_leftToRight.contains(key)) {
insert(key, it.value());
}
++it;
}
return *this;
}
void updateRight(left_iterator it, right_type u)
{
Q_ASSERT(it != leftEnd());
const left_type key = it.key();
_rightToLeft.remove(_leftToRight.value(key));
_leftToRight[key] = u;
_rightToLeft[u] = key;
}
void updateLeft(right_iterator it, left_type t)
{
Q_ASSERT(it != rightEnd());
const right_type key = it.key();
_leftToRight.remove(_rightToLeft.value(key));
_rightToLeft[key] = t;
_leftToRight[t] = key;
}
inline bool isEmpty() const
{
return _leftToRight.isEmpty();
}
const right_type operator[](const left_type &t) const
{
return _leftToRight.operator[](t);
}
bool operator==(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
return _leftToRight == other._leftToRight;
}
bool operator!=(const KBiAssociativeContainer<LeftContainer, RightContainer> &other)
{
return _leftToRight != other._leftToRight;
}
left_iterator toLeftIterator(right_iterator it) const
{
Q_ASSERT(it != rightEnd());
return _leftToRight.find(it.value());
}
right_iterator toRightIterator(left_iterator it) const
{
Q_ASSERT(it != leftEnd());
return _rightToLeft.find(it.value());
}
inline left_iterator leftBegin()
{
return _leftToRight.begin();
}
inline left_iterator leftEnd()
{
return _leftToRight.end();
}
inline left_const_iterator leftBegin() const
{
return _leftToRight.begin();
}
inline left_const_iterator leftEnd() const
{
return _leftToRight.end();
}
inline left_const_iterator leftConstBegin() const
{
return _leftToRight.constBegin();
}
inline left_const_iterator leftConstEnd() const
{
return _leftToRight.constEnd();
}
inline right_iterator rightBegin()
{
return _rightToLeft.begin();
}
inline right_iterator rightEnd()
{
return _rightToLeft.end();
}
inline right_const_iterator rightBegin() const
{
return _rightToLeft.begin();
}
inline right_const_iterator rightEnd() const
{
return _rightToLeft.end();
}
inline right_const_iterator rightConstBegin() const
{
return _rightToLeft.constBegin();
}
inline right_const_iterator rightConstEnd() const
{
return _rightToLeft.constEnd();
}
static KBiAssociativeContainer<LeftContainer, RightContainer> fromHash(const QHash<left_type, right_type> &hash)
{
KBiAssociativeContainer<LeftContainer, RightContainer> container;
typename QHash<left_type, right_type>::const_iterator it = hash.constBegin();
const typename QHash<left_type, right_type>::const_iterator end = hash.constEnd();
for (; it != end; ++it) {
container.insert(it.key(), it.value());
}
return container;
}
static KBiAssociativeContainer<LeftContainer, RightContainer> fromMap(const QMap<left_type, right_type> &hash)
{
KBiAssociativeContainer<LeftContainer, RightContainer> container;
typename QMap<left_type, right_type>::const_iterator it = hash.constBegin();
const typename QMap<left_type, right_type>::const_iterator end = hash.constEnd();
for (; it != end; ++it) {
container.insert(it.key(), it.value());
}
return container;
}
friend QDataStream &operator<<<LeftContainer, RightContainer>(QDataStream &out, const KBiAssociativeContainer<LeftContainer, RightContainer> &bihash);
friend QDataStream &operator>><LeftContainer, RightContainer>(QDataStream &in, KBiAssociativeContainer<LeftContainer, RightContainer> &biHash);
friend QDebug operator<<<LeftContainer, RightContainer>(QDebug out, const KBiAssociativeContainer<LeftContainer, RightContainer> &biHash);
protected:
LeftContainer _leftToRight;
RightContainer _rightToLeft;
};
template<typename LeftContainer, typename RightContainer>
QDataStream &operator<<(QDataStream &out, const KBiAssociativeContainer<LeftContainer, RightContainer> &container)
{
return out << container._leftToRight;
}
template<typename LeftContainer, typename RightContainer>
QDataStream &operator>>(QDataStream &in, KBiAssociativeContainer<LeftContainer, RightContainer> &container)
{
LeftContainer leftToRight;
in >> leftToRight;
typename LeftContainer::const_iterator it = leftToRight.constBegin();
const typename LeftContainer::const_iterator end = leftToRight.constEnd();
for (; it != end; ++it) {
container.insert(it.key(), it.value());
}
return in;
}
template<typename Container, typename T, typename U>
struct _containerType {
operator const char *();
};
template<typename T, typename U>
struct _containerType<QHash<T, U>, T, U> {
operator const char *()
{
return "QHash";
}
};
template<typename T, typename U>
struct _containerType<QMap<T, U>, T, U> {
operator const char *()
{
return "QMap";
}
};
template<typename Container>
static const char *containerType()
{
return _containerType<Container, typename Container::key_type, typename Container::mapped_type>();
}
template<typename LeftContainer, typename RightContainer>
QDebug operator<<(QDebug out, const KBiAssociativeContainer<LeftContainer, RightContainer> &container)
{
typename KBiAssociativeContainer<LeftContainer, RightContainer>::left_const_iterator it = container.leftConstBegin();
const typename KBiAssociativeContainer<LeftContainer, RightContainer>::left_const_iterator end = container.leftConstEnd();
out.nospace() << "KBiAssociativeContainer<" << containerType<LeftContainer>() << ", " << containerType<RightContainer>() << ">"
<< "(";
for (; it != end; ++it) {
out << "(" << it.key() << " <=> " << it.value() << ") ";
}
out << ")";
return out;
}
/**
* @brief KBiHash provides a bi-directional hash container
*
* @note This class is designed to make mapping easier in proxy model implementations.
*
* @todo Figure out whether to discard this and use boost::bimap instead, submit it Qt or keep it here and make more direct use of QHashNode.
*/
template<typename T, typename U>
struct KBiHash : public KBiAssociativeContainer<QHash<T, U>, QHash<U, T>> {
KBiHash()
: KBiAssociativeContainer<QHash<T, U>, QHash<U, T>>()
{
}
KBiHash(const KBiAssociativeContainer<QHash<T, U>, QHash<U, T>> &container)
: KBiAssociativeContainer<QHash<T, U>, QHash<U, T>>(container)
{
}
};
template<typename T, typename U>
QDebug operator<<(QDebug out, const KBiHash<T, U> &biHash)
{
typename KBiHash<T, U>::left_const_iterator it = biHash.leftConstBegin();
const typename KBiHash<T, U>::left_const_iterator end = biHash.leftConstEnd();
out.nospace() << "KBiHash(";
for (; it != end; ++it) {
out << "(" << it.key() << " <=> " << it.value() << ") ";
}
out << ")";
return out;
}
template<typename T, typename U>
struct KHash2Map : public KBiAssociativeContainer<QHash<T, U>, QMap<U, T>> {
KHash2Map()
: KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>()
{
}
KHash2Map(const KBiAssociativeContainer<QHash<T, U>, QMap<U, T>> &container)
: KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>(container)
{
}
typename KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>::right_iterator rightLowerBound(const U &key)
{
return this->_rightToLeft.lowerBound(key);
}
typename KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>::right_const_iterator rightLowerBound(const U &key) const
{
return this->_rightToLeft.lowerBound(key);
}
typename KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>::right_iterator rightUpperBound(const U &key)
{
return this->_rightToLeft.upperBound(key);
}
typename KBiAssociativeContainer<QHash<T, U>, QMap<U, T>>::right_const_iterator rightUpperBound(const U &key) const
{
return this->_rightToLeft.upperBound(key);
}
};
template<typename T, typename U>
QDebug operator<<(QDebug out, const KHash2Map<T, U> &container)
{
typename KHash2Map<T, U>::left_const_iterator it = container.leftConstBegin();
const typename KHash2Map<T, U>::left_const_iterator end = container.leftConstEnd();
out.nospace() << "KHash2Map(";
for (; it != end; ++it) {
out << "(" << it.key() << " <=> " << it.value() << ") ";
}
out << ")";
return out;
}
#endif
@@ -0,0 +1,222 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kbreadcrumbselectionmodel.h"
class KBreadcrumbSelectionModelPrivate
{
Q_DECLARE_PUBLIC(KBreadcrumbSelectionModel)
KBreadcrumbSelectionModel *const q_ptr;
public:
KBreadcrumbSelectionModelPrivate(KBreadcrumbSelectionModel *breadcrumbSelector,
QItemSelectionModel *selectionModel,
KBreadcrumbSelectionModel::BreadcrumbTarget direction);
/**
Returns a selection containing the breadcrumbs for @p index
*/
QItemSelection getBreadcrumbSelection(const QModelIndex &index);
/**
Returns a selection containing the breadcrumbs for @p selection
*/
QItemSelection getBreadcrumbSelection(const QItemSelection &selection);
void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void syncBreadcrumbs();
bool m_includeActualSelection = true;
bool m_showHiddenAscendantData = false;
bool m_ignoreCurrentChanged = false;
int m_selectionDepth = -1;
KBreadcrumbSelectionModel::BreadcrumbTarget m_direction = KBreadcrumbSelectionModel::MakeBreadcrumbSelectionInSelf;
QItemSelectionModel *m_selectionModel = nullptr;
};
KBreadcrumbSelectionModelPrivate::KBreadcrumbSelectionModelPrivate(KBreadcrumbSelectionModel *breadcrumbSelector,
QItemSelectionModel *selectionModel,
KBreadcrumbSelectionModel::BreadcrumbTarget direction)
: q_ptr(breadcrumbSelector)
, m_direction(direction)
, m_selectionModel(selectionModel)
{
Q_Q(KBreadcrumbSelectionModel);
if (direction != KBreadcrumbSelectionModel::MakeBreadcrumbSelectionInSelf) {
q->connect(selectionModel, &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &selected, const QItemSelection &deselected) {
sourceSelectionChanged(selected, deselected);
});
}
q->connect(m_selectionModel->model(), &QAbstractItemModel::layoutChanged, q, [this]() {
syncBreadcrumbs();
});
q->connect(m_selectionModel->model(), &QAbstractItemModel::modelReset, q, [this]() {
syncBreadcrumbs();
});
q->connect(m_selectionModel->model(), &QAbstractItemModel::rowsMoved, q, [this]() {
syncBreadcrumbs();
});
// Don't need to handle insert & remove because they can't change the breadcrumbs on their own.
}
KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, QObject *parent)
: QItemSelectionModel(const_cast<QAbstractItemModel *>(selectionModel->model()), parent)
, d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, MakeBreadcrumbSelectionInSelf))
{
}
KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, BreadcrumbTarget direction, QObject *parent)
: QItemSelectionModel(const_cast<QAbstractItemModel *>(selectionModel->model()), parent)
, d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, direction))
{
}
KBreadcrumbSelectionModel::~KBreadcrumbSelectionModel() = default;
bool KBreadcrumbSelectionModel::isActualSelectionIncluded() const
{
Q_D(const KBreadcrumbSelectionModel);
return d->m_includeActualSelection;
}
void KBreadcrumbSelectionModel::setActualSelectionIncluded(bool includeActualSelection)
{
Q_D(KBreadcrumbSelectionModel);
d->m_includeActualSelection = includeActualSelection;
}
int KBreadcrumbSelectionModel::breadcrumbLength() const
{
Q_D(const KBreadcrumbSelectionModel);
return d->m_selectionDepth;
}
void KBreadcrumbSelectionModel::setBreadcrumbLength(int breadcrumbLength)
{
Q_D(KBreadcrumbSelectionModel);
d->m_selectionDepth = breadcrumbLength;
}
QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QModelIndex &index)
{
QItemSelection breadcrumbSelection;
if (m_includeActualSelection) {
breadcrumbSelection.append(QItemSelectionRange(index));
}
QModelIndex parent = index.parent();
int sumBreadcrumbs = 0;
bool includeAll = m_selectionDepth < 0;
while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) {
breadcrumbSelection.append(QItemSelectionRange(parent));
parent = parent.parent();
}
return breadcrumbSelection;
}
QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QItemSelection &selection)
{
QItemSelection breadcrumbSelection;
if (m_includeActualSelection) {
breadcrumbSelection = selection;
}
QItemSelection::const_iterator it = selection.constBegin();
const QItemSelection::const_iterator end = selection.constEnd();
for (; it != end; ++it) {
QModelIndex parent = it->parent();
if (breadcrumbSelection.contains(parent)) {
continue;
}
int sumBreadcrumbs = 0;
bool includeAll = m_selectionDepth < 0;
while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) {
breadcrumbSelection.append(QItemSelectionRange(parent));
parent = parent.parent();
if (breadcrumbSelection.contains(parent)) {
break;
}
++sumBreadcrumbs;
}
}
return breadcrumbSelection;
}
void KBreadcrumbSelectionModelPrivate::sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_Q(KBreadcrumbSelectionModel);
const QItemSelection deselectedCrumbs = getBreadcrumbSelection(deselected);
const QItemSelection selectedCrumbs = getBreadcrumbSelection(selected);
QItemSelection removed = deselectedCrumbs;
for (const QItemSelectionRange &range : selectedCrumbs) {
removed.removeAll(range);
}
QItemSelection added = selectedCrumbs;
for (const QItemSelectionRange &range : deselectedCrumbs) {
added.removeAll(range);
}
if (!removed.isEmpty()) {
q->QItemSelectionModel::select(removed, QItemSelectionModel::Deselect);
}
if (!added.isEmpty()) {
q->QItemSelectionModel::select(added, QItemSelectionModel::Select);
}
}
void KBreadcrumbSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
{
Q_D(KBreadcrumbSelectionModel);
// When an item is removed, the current index is set to the top index in the model.
// That causes a selectionChanged signal with a selection which we do not want.
if (d->m_ignoreCurrentChanged) {
d->m_ignoreCurrentChanged = false;
return;
}
if (d->m_direction == MakeBreadcrumbSelectionInOther) {
d->m_selectionModel->select(d->getBreadcrumbSelection(index), command);
QItemSelectionModel::select(index, command);
} else {
d->m_selectionModel->select(index, command);
QItemSelectionModel::select(d->getBreadcrumbSelection(index), command);
}
}
void KBreadcrumbSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
{
Q_D(KBreadcrumbSelectionModel);
QItemSelection bcc = d->getBreadcrumbSelection(selection);
if (d->m_direction == MakeBreadcrumbSelectionInOther) {
d->m_selectionModel->select(selection, command);
QItemSelectionModel::select(bcc, command);
} else {
d->m_selectionModel->select(bcc, command);
QItemSelectionModel::select(selection, command);
}
}
void KBreadcrumbSelectionModelPrivate::syncBreadcrumbs()
{
Q_Q(KBreadcrumbSelectionModel);
q->select(m_selectionModel->selection(), QItemSelectionModel::ClearAndSelect);
}
#include "moc_kbreadcrumbselectionmodel.cpp"
@@ -0,0 +1,147 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KBREADCRUMBSELECTIONMODEL_H
#define KBREADCRUMBSELECTIONMODEL_H
#include <QItemSelectionModel>
#include "kitemmodels_export.h"
#include <memory>
class KBreadcrumbSelectionModelPrivate;
/**
@class KBreadcrumbSelectionModel kbreadcrumbselectionmodel.h KBreadcrumbSelectionModel
@brief Selects the parents of selected items to create breadcrumbs
For example, if the tree is
@verbatim
- A
- B
- - C
- - D
- - - E
- - - - F
@endverbatim
and E is selected, the selection can contain
@verbatim
- B
- D
@endverbatim
or
@verbatim
- B
- D
- E
@endverbatim
if isActualSelectionIncluded is true.
The depth of the selection may also be set. For example if the breadcrumbLength is 1:
@verbatim
- D
- E
@endverbatim
And if breadcrumbLength is 2:
@verbatim
- B
- D
- E
@endverbatim
A KBreadcrumbsSelectionModel with a breadcrumbLength of 0 and including the actual selection is
the same as a KSelectionProxyModel in the KSelectionProxyModel::ExactSelection configuration.
@code
view1->setModel(rootModel);
QItemSelectionModel *breadcrumbSelectionModel = new QItemSelectionModel(rootModel, this);
KBreadcrumbSelectionModel *breadcrumbProxySelector = new KBreadcrumbSelectionModel(breadcrumbSelectionModel, rootModel, this);
view1->setSelectionModel(breadcrumbProxySelector);
KSelectionProxyModel *breadcrumbSelectionProxyModel = new KSelectionProxyModel( breadcrumbSelectionModel, this);
breadcrumbSelectionProxyModel->setSourceModel( rootModel );
breadcrumbSelectionProxyModel->setFilterBehavior( KSelectionProxyModel::ExactSelection );
view2->setModel(breadcrumbSelectionProxyModel);
@endcode
@image html kbreadcrumbselectionmodel.png "KBreadcrumbSelectionModel in several configurations"
This can work in two directions. One option is for a single selection in the KBreadcrumbSelectionModel to invoke
the breadcrumb selection in its constructor argument.
The other is for a selection in the itemselectionmodel in the constructor argument to cause a breadcrumb selection
in @p this.
@since 4.5
*/
class KITEMMODELS_EXPORT KBreadcrumbSelectionModel : public QItemSelectionModel
{
Q_OBJECT
public:
enum BreadcrumbTarget {
MakeBreadcrumbSelectionInOther,
MakeBreadcrumbSelectionInSelf,
};
explicit KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, QObject *parent = nullptr);
KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, BreadcrumbTarget target, QObject *parent = nullptr);
~KBreadcrumbSelectionModel() override;
/**
Returns whether the actual selection in included in the proxy.
The default is true.
*/
bool isActualSelectionIncluded() const;
/**
Set whether the actual selection in included in the proxy to @p isActualSelectionIncluded.
*/
void setActualSelectionIncluded(bool isActualSelectionIncluded);
/**
Returns the depth that the breadcrumb selection should go to.
*/
int breadcrumbLength() const;
/**
Sets the depth that the breadcrumb selection should go to.
If the @p breadcrumbLength is -1, all breadcrumbs are selected.
The default is -1
*/
void setBreadcrumbLength(int breadcrumbLength);
void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override;
void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
protected:
std::unique_ptr<KBreadcrumbSelectionModelPrivate> const d_ptr;
private:
//@cond PRIVATE
Q_DECLARE_PRIVATE(KBreadcrumbSelectionModel)
//@cond PRIVATE
};
#endif
@@ -0,0 +1,128 @@
/*
SPDX-FileCopyrightText: 2010 Stephen Kelly <steveire@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcheckableproxymodel.h"
#include <QItemSelectionModel>
class KCheckableProxyModelPrivate
{
Q_DECLARE_PUBLIC(KCheckableProxyModel)
KCheckableProxyModel *q_ptr;
KCheckableProxyModelPrivate(KCheckableProxyModel *checkableModel)
: q_ptr(checkableModel)
{
}
QItemSelectionModel *m_itemSelectionModel = nullptr;
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
};
KCheckableProxyModel::KCheckableProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
, d_ptr(new KCheckableProxyModelPrivate(this))
{
}
KCheckableProxyModel::~KCheckableProxyModel() = default;
void KCheckableProxyModel::setSelectionModel(QItemSelectionModel *itemSelectionModel)
{
Q_D(KCheckableProxyModel);
d->m_itemSelectionModel = itemSelectionModel;
Q_ASSERT(sourceModel() ? d->m_itemSelectionModel->model() == sourceModel() : true);
connect(itemSelectionModel, &QItemSelectionModel::selectionChanged, this, [d](const QItemSelection &selected, const QItemSelection &deselected) {
d->selectionChanged(selected, deselected);
});
}
QItemSelectionModel *KCheckableProxyModel::selectionModel() const
{
Q_D(const KCheckableProxyModel);
return d->m_itemSelectionModel;
}
Qt::ItemFlags KCheckableProxyModel::flags(const QModelIndex &index) const
{
if (!index.isValid() || index.column() != 0) {
return QIdentityProxyModel::flags(index);
}
return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant KCheckableProxyModel::data(const QModelIndex &index, int role) const
{
Q_D(const KCheckableProxyModel);
if (role == Qt::CheckStateRole) {
if (index.column() != 0) {
return QVariant();
}
if (!d->m_itemSelectionModel) {
return Qt::Unchecked;
}
return d->m_itemSelectionModel->selection().contains(mapToSource(index)) ? Qt::Checked : Qt::Unchecked;
}
return QIdentityProxyModel::data(index, role);
}
bool KCheckableProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Q_D(KCheckableProxyModel);
if (role == Qt::CheckStateRole) {
if (index.column() != 0) {
return false;
}
if (!d->m_itemSelectionModel) {
return false;
}
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
const QModelIndex srcIndex = mapToSource(index);
bool result = select(QItemSelection(srcIndex, srcIndex), state == Qt::Checked ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
Q_EMIT dataChanged(index, index);
return result;
}
return QIdentityProxyModel::setData(index, value, role);
}
void KCheckableProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
{
QIdentityProxyModel::setSourceModel(sourceModel);
Q_ASSERT(d_ptr->m_itemSelectionModel ? d_ptr->m_itemSelectionModel->model() == sourceModel : true);
}
void KCheckableProxyModelPrivate::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_Q(KCheckableProxyModel);
const auto lstSelected = q->mapSelectionFromSource(selected);
for (const QItemSelectionRange &range : lstSelected) {
Q_EMIT q->dataChanged(range.topLeft(), range.bottomRight());
}
const auto lstDeselected = q->mapSelectionFromSource(deselected);
for (const QItemSelectionRange &range : lstDeselected) {
Q_EMIT q->dataChanged(range.topLeft(), range.bottomRight());
}
}
bool KCheckableProxyModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
{
Q_D(KCheckableProxyModel);
d->m_itemSelectionModel->select(selection, command);
return true;
}
QHash<int, QByteArray> KCheckableProxyModel::roleNames() const
{
auto roles = QIdentityProxyModel::roleNames();
roles[Qt::CheckStateRole] = QByteArrayLiteral("checkState");
return roles;
}
#include "moc_kcheckableproxymodel.cpp"
@@ -0,0 +1,87 @@
/*
SPDX-FileCopyrightText: 2010 Stephen Kelly <steveire@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCHECKABLEPROXYMODEL_H
#define KCHECKABLEPROXYMODEL_H
#include "kitemmodels_export.h"
#include <QIdentityProxyModel>
#include <QItemSelection>
#include <memory>
class KCheckableProxyModelPrivate;
/**
* @class KCheckableProxyModel kcheckableproxymodel.h KCheckableProxyModel
*
* @brief Adds a checkable capability to a source model
*
* Items is standard Qt views such as QTreeView and QListView can have a
* checkable capability and draw checkboxes. Adding such a capability
* requires specific implementations of the data() and flags() virtual methods.
* This class implements those methods generically so that it is not necessary to
* implement them in your model.
*
* This can be combined with a KSelectionProxyModel showing the items currently selected
*
* @code
*
* QItemSelectionModel *checkModel = new QItemSelectionModel(rootModel, this);
* KCheckableProxyModel *checkable = new KCheckableProxyModel(this);
* checkable->setSourceModel(rootModel);
* checkable->setSelectionModel(checkModel);
*
* QTreeView *tree1 = new QTreeView(vSplitter);
* tree1->setModel(checkable);
* tree1->expandAll();
*
* KSelectionProxyModel *selectionProxy = new KSelectionProxyModel(checkModel, this);
* selectionProxy->setFilterBehavior(KSelectionProxyModel::ExactSelection);
* selectionProxy->setSourceModel(rootModel);
*
* QTreeView *tree2 = new QTreeView(vSplitter);
* tree2->setModel(selectionProxy);
* @endcode
*
* @image html kcheckableproxymodel.png "A KCheckableProxyModel and KSelectionProxyModel showing checked items"
*
* @since 4.6
* @author Stephen Kelly <steveire@gmail.com>
*/
class KITEMMODELS_EXPORT KCheckableProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
explicit KCheckableProxyModel(QObject *parent = nullptr);
~KCheckableProxyModel() override;
void setSelectionModel(QItemSelectionModel *itemSelectionModel);
QItemSelectionModel *selectionModel() const;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
void setSourceModel(QAbstractItemModel *sourceModel) override;
/// Expose following role: "checkState" => Qt::CheckStateRole
QHash<int, QByteArray> roleNames() const override;
protected:
virtual bool select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command);
private:
Q_DECLARE_PRIVATE(KCheckableProxyModel)
std::unique_ptr<KCheckableProxyModelPrivate> const d_ptr;
Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection &, const QItemSelection &))
};
#endif
@@ -0,0 +1,165 @@
/*
SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolumnheadersmodel.h"
class KColumnHeadersModelPrivate
{
public:
QAbstractItemModel *sourceModel = nullptr;
int sortColumn = -1;
Qt::SortOrder sortOrder = Qt::AscendingOrder;
};
KColumnHeadersModel::KColumnHeadersModel(QObject *parent)
: QAbstractListModel(parent)
, d(new KColumnHeadersModelPrivate)
{
}
KColumnHeadersModel::~KColumnHeadersModel()
{
}
int KColumnHeadersModel::rowCount(const QModelIndex &parent) const
{
if (!d->sourceModel || parent.isValid()) {
return 0;
}
return d->sourceModel->columnCount();
}
QVariant KColumnHeadersModel::data(const QModelIndex &index, int role) const
{
if (!d->sourceModel || !index.isValid()) {
return QVariant{};
}
if (role == SortRole) {
if (index.row() == d->sortColumn) {
return d->sortOrder;
} else {
return QVariant{};
}
}
return sourceModel()->headerData(index.row(), Qt::Horizontal, role);
}
QHash<int, QByteArray> KColumnHeadersModel::roleNames() const
{
if (!d->sourceModel) {
return QHash<int, QByteArray>{};
}
auto names = d->sourceModel->roleNames();
names.insert(SortRole, "sort");
return names;
}
QAbstractItemModel *KColumnHeadersModel::sourceModel() const
{
return d->sourceModel;
}
void KColumnHeadersModel::setSourceModel(QAbstractItemModel *newSourceModel)
{
if (newSourceModel == d->sourceModel) {
return;
}
if (d->sourceModel) {
d->sourceModel->disconnect(this);
}
beginResetModel();
d->sourceModel = newSourceModel;
endResetModel();
if (newSourceModel) {
connect(newSourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this, [this](const QModelIndex &, int first, int last) {
beginInsertRows(QModelIndex{}, first, last);
});
connect(newSourceModel, &QAbstractItemModel::columnsInserted, this, [this]() {
endInsertRows();
});
connect(newSourceModel,
&QAbstractItemModel::columnsAboutToBeMoved,
this,
[this](const QModelIndex &, int start, int end, const QModelIndex &, int destination) {
beginMoveRows(QModelIndex{}, start, end, QModelIndex{}, destination);
});
connect(newSourceModel, &QAbstractItemModel::columnsMoved, this, [this]() {
endMoveRows();
});
connect(newSourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this, [this](const QModelIndex &, int first, int last) {
beginRemoveRows(QModelIndex{}, first, last);
});
connect(newSourceModel, &QAbstractItemModel::columnsRemoved, this, [this]() {
endRemoveRows();
});
connect(newSourceModel, &QAbstractItemModel::headerDataChanged, this, [this](Qt::Orientation orientation, int first, int last) {
if (orientation == Qt::Horizontal) {
Q_EMIT dataChanged(index(first, 0), index(last, 0));
}
});
connect(newSourceModel, &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
beginResetModel();
});
connect(newSourceModel, &QAbstractItemModel::modelReset, this, [this]() {
endResetModel();
});
}
}
int KColumnHeadersModel::sortColumn() const
{
return d->sortColumn;
}
void KColumnHeadersModel::setSortColumn(int newSortColumn)
{
if (newSortColumn == d->sortColumn) {
return;
}
auto previousSortColumn = d->sortColumn;
d->sortColumn = newSortColumn;
if (previousSortColumn >= 0) {
Q_EMIT dataChanged(index(previousSortColumn), index(previousSortColumn), {SortRole});
}
if (newSortColumn >= 0) {
Q_EMIT dataChanged(index(newSortColumn), index(newSortColumn), {SortRole});
}
Q_EMIT sortColumnChanged();
}
Qt::SortOrder KColumnHeadersModel::sortOrder() const
{
return d->sortOrder;
}
void KColumnHeadersModel::setSortOrder(Qt::SortOrder newSortOrder)
{
if (newSortOrder == d->sortOrder) {
return;
}
d->sortOrder = newSortOrder;
if (d->sortColumn >= 0) {
Q_EMIT dataChanged(index(d->sortColumn), index(d->sortColumn), {SortRole});
}
Q_EMIT sortOrderChanged();
}
#include "moc_kcolumnheadersmodel.cpp"
@@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLUMNHEADERSMODEL_H
#define KCOLUMNHEADERSMODEL_H
#include "kitemmodels_export.h"
#include <QAbstractListModel>
#include <memory>
class KColumnHeadersModelPrivate;
/**
* A model that converts a model's headers into a list model.
*
* This model will expose the source model's headers as a simple list. This is
* mostly useful as a helper for QML applications that want to display a model's
* headers.
*
* Each columns's header will be presented as a row in this model. Roles are
* forwarded directly to the source model's headerData() method.
*
* @since 5.66
*/
class KITEMMODELS_EXPORT KColumnHeadersModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
Q_PROPERTY(int sortColumn READ sortColumn WRITE setSortColumn NOTIFY sortColumnChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
public:
enum ExtraRoles { SortRole = 0x011D910E };
Q_ENUM(ExtraRoles)
explicit KColumnHeadersModel(QObject *parent = nullptr);
~KColumnHeadersModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
QAbstractItemModel *sourceModel() const;
void setSourceModel(QAbstractItemModel *newSourceModel);
int sortColumn() const;
void setSortColumn(int newSortColumn);
Qt::SortOrder sortOrder() const;
void setSortOrder(Qt::SortOrder newSortOrder);
Q_SIGNALS:
void sourceModelChanged();
void sortColumnChanged();
void sortOrderChanged();
private:
void onLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
const std::unique_ptr<KColumnHeadersModelPrivate> d;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,270 @@
/*
SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KDESCENDANTSPROXYMODEL_P_H
#define KDESCENDANTSPROXYMODEL_P_H
#include <QAbstractProxyModel>
#include "kitemmodels_export.h"
#include <memory>
class KDescendantsProxyModelPrivate;
/**
@class KDescendantsProxyModel kdescendantsproxymodel.h KDescendantsProxyModel
@brief Proxy Model for restructuring a Tree into a list.
A KDescendantsProxyModel may be used to alter how the items in the tree are presented.
Given a model which is represented as a tree:
\image html entitytreemodel.png "A plain EntityTreeModel in a view"
The KDescendantsProxyModel restructures the sourceModel to represent it as a flat list.
@code
// ... Create an entityTreeModel
KDescendantsProxyModel *descProxy = new KDescendantsProxyModel(this);
descProxy->setSourceModel(entityTree);
view->setModel(descProxy);
@endcode
\image html descendantentitiesproxymodel.png "A KDescendantsProxyModel."
KDescendantEntitiesProxyModel can also display the ancestors of the index in the source model as part of its display.
@code
// ... Create an entityTreeModel
KDescendantsProxyModel *descProxy = new KDescendantsProxyModel(this);
descProxy->setSourceModel(entityTree);
// #### This is new
descProxy->setDisplayAncestorData(true);
descProxy->setAncestorSeparator(QString(" / "));
view->setModel(descProxy);
@endcode
\image html descendantentitiesproxymodel-withansecnames.png "A KDescendantsProxyModel with ancestor names."
@since 4.6
@author Stephen Kelly <steveire@gmail.com>
*/
class KITEMMODELS_EXPORT KDescendantsProxyModel : public QAbstractProxyModel
{
Q_OBJECT
/**
* @since 5.62
*/
Q_PROPERTY(QAbstractItemModel *model READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
/**
* @since 5.62
*/
Q_PROPERTY(bool displayAncestorData READ displayAncestorData WRITE setDisplayAncestorData NOTIFY displayAncestorDataChanged)
/**
* @since 5.62
*/
Q_PROPERTY(QString ancestorSeparator READ ancestorSeparator WRITE setAncestorSeparator NOTIFY ancestorSeparatorChanged)
/**
* If true, all the nodes in the whole tree will be expanded upon loading and all items
* of the source model will be shown in the proxy.
* The default value is true.
* @since 5.74
*/
Q_PROPERTY(bool expandsByDefault READ expandsByDefault WRITE setExpandsByDefault NOTIFY expandsByDefaultChanged)
public:
enum AdditionalRoles {
// Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
// to define additional roles.
LevelRole = 0x14823F9A,
ExpandableRole = 0x1CA894AD,
ExpandedRole = 0x1E413DA4,
HasSiblingsRole = 0x1633CE0C,
};
/**
* Creates a new descendant entities proxy model.
*
* @param parent The parent object.
*/
explicit KDescendantsProxyModel(QObject *parent = nullptr);
/**
* Destroys the descendant entities proxy model.
*/
~KDescendantsProxyModel() override;
/**
* Sets the source @p model of the proxy.
*/
void setSourceModel(QAbstractItemModel *model) override;
/**
* Set whether to show ancestor data in the model. If @p display is true, then
* a source model which is displayed as
*
* @code
* -> "Item 0-0" (this is row-depth)
* -> -> "Item 0-1"
* -> -> "Item 1-1"
* -> -> -> "Item 0-2"
* -> -> -> "Item 1-2"
* -> "Item 1-0"
* @endcode
*
* will be displayed as
*
* @code
* -> *Item 0-0"
* -> "Item 0-0 / Item 0-1"
* -> "Item 0-0 / Item 1-1"
* -> "Item 0-0 / Item 1-1 / Item 0-2"
* -> "Item 0-0 / Item 1-1 / Item 1-2"
* -> "Item 1-0"
* @endcode
*
* If @p display is false, the proxy will show
*
* @code
* -> *Item 0-0"
* -> "Item 0-1"
* -> "Item 1-1"
* -> "Item 0-2"
* -> "Item 1-2"
* -> "Item 1-0"
* @endcode
*
* Default is false.
*/
void setDisplayAncestorData(bool display);
/**
* Whether ancestor data will be displayed.
*/
bool displayAncestorData() const;
/**
* Sets the ancestor @p separator used between data of ancestors.
*/
void setAncestorSeparator(const QString &separator);
/**
* Separator used between data of ancestors.
*/
QString ancestorSeparator() const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(int, int, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override;
int columnCount(const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
/**
* If true, all the nodes in the whole tree will be expanded upon loading (default)
* @param expand whether we want everything expanded upon load
* @since 5.74
*/
void setExpandsByDefault(bool expand);
/**
* @returns true if all the tree nodes are expanded by default upon loading
* @since 5.74
*/
bool expandsByDefault() const;
/**
* @returns true if the source index is mapped in the proxy as expanded, therefore it will show its children
* @since 5.74
*/
bool isSourceIndexExpanded(const QModelIndex &sourceIndex) const;
/**
* @returns true if the source index is visible in the proxy, meaning all its parent hierarchy is expanded.
* @since 5.74
*/
bool isSourceIndexVisible(const QModelIndex &sourceIndex) const;
/**
* Maps a source index as expanded in the proxy, all its children will become visible.
* @param sourceIndex an idex of the source model.
* @since 5.74
*/
void expandSourceIndex(const QModelIndex &sourceIndex);
/**
* Maps a source index as collapsed in the proxy, all its children will be hidden.
* @param sourceIndex an idex of the source model.
* @since 5.74
*/
void collapseSourceIndex(const QModelIndex &sourceIndex);
Qt::DropActions supportedDropActions() const override;
/**
Reimplemented to match all descendants.
*/
virtual QModelIndexList match(const QModelIndex &start,
int role,
const QVariant &value,
int hits = 1,
Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override;
Q_SIGNALS:
void sourceModelChanged();
void displayAncestorDataChanged();
void ancestorSeparatorChanged();
void expandsByDefaultChanged(bool expands);
void sourceIndexExpanded(const QModelIndex &sourceIndex);
void sourceIndexCollapsed(const QModelIndex &sourceIndex);
private:
Q_DECLARE_PRIVATE(KDescendantsProxyModel)
//@cond PRIVATE
std::unique_ptr<KDescendantsProxyModelPrivate> const d_ptr;
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))
Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset())
Q_PRIVATE_SLOT(d_func(), void sourceModelReset())
Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged())
Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged())
Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &))
Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed())
Q_PRIVATE_SLOT(d_func(), void processPendingParents())
// Make these private, they shouldn't be called by applications
// virtual bool insertRows(int , int, const QModelIndex & = QModelIndex());
// virtual bool insertColumns(int, int, const QModelIndex & = QModelIndex());
// virtual bool removeRows(int, int, const QModelIndex & = QModelIndex());
// virtual bool removeColumns(int, int, const QModelIndex & = QModelIndex());
//@endcond
};
#endif
@@ -0,0 +1,351 @@
/*
SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-FileContributor: David Faure <david.faure@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kextracolumnsproxymodel.h"
#include "kitemmodels_debug.h"
#include <QItemSelection>
class KExtraColumnsProxyModelPrivate
{
Q_DECLARE_PUBLIC(KExtraColumnsProxyModel)
KExtraColumnsProxyModel *const q_ptr;
public:
KExtraColumnsProxyModelPrivate(KExtraColumnsProxyModel *model)
: q_ptr(model)
{
}
void _ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
void _ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
// Configuration (doesn't change once source model is plugged in)
QList<QString> m_extraHeaders;
// for layoutAboutToBeChanged/layoutChanged
QList<QPersistentModelIndex> layoutChangePersistentIndexes;
QList<int> layoutChangeProxyColumns;
QModelIndexList proxyIndexes;
};
KExtraColumnsProxyModel::KExtraColumnsProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
, d_ptr(new KExtraColumnsProxyModelPrivate(this))
{
// The handling of persistent model indexes assumes mapToSource can be called for any index
// This breaks for the extra column, so we'll have to do it ourselves
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
setHandleSourceLayoutChanges(false);
#endif
}
KExtraColumnsProxyModel::~KExtraColumnsProxyModel()
{
}
void KExtraColumnsProxyModel::appendColumn(const QString &header)
{
Q_D(KExtraColumnsProxyModel);
d->m_extraHeaders.append(header);
}
void KExtraColumnsProxyModel::removeExtraColumn(int idx)
{
Q_D(KExtraColumnsProxyModel);
d->m_extraHeaders.remove(idx);
}
bool KExtraColumnsProxyModel::setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role)
{
Q_UNUSED(parent);
Q_UNUSED(row);
Q_UNUSED(extraColumn);
Q_UNUSED(data);
Q_UNUSED(role);
return false;
}
void KExtraColumnsProxyModel::extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QList<int> &roles)
{
const QModelIndex idx = index(row, proxyColumnForExtraColumn(extraColumn), parent);
Q_EMIT dataChanged(idx, idx, roles);
}
void KExtraColumnsProxyModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel()) {
disconnect(sourceModel(),
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
disconnect(sourceModel(),
SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
}
QIdentityProxyModel::setSourceModel(model);
if (model) {
// The handling of persistent model indexes assumes mapToSource can be called for any index
// This breaks for the extra column, so we'll have to do it ourselves
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
disconnect(model,
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_q_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
disconnect(model,
SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_q_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
#endif
connect(model,
SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_ec_sourceLayoutAboutToBeChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
connect(model,
SIGNAL(layoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)),
this,
SLOT(_ec_sourceLayoutChanged(QList<QPersistentModelIndex>, QAbstractItemModel::LayoutChangeHint)));
}
}
QModelIndex KExtraColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid()) { // happens in e.g. rowCount(mapToSource(parent))
return QModelIndex();
}
const int column = proxyIndex.column();
if (column >= sourceModel()->columnCount()) {
qCDebug(KITEMMODELS_LOG) << "Returning invalid index in mapToSource";
return QModelIndex();
}
return QIdentityProxyModel::mapToSource(proxyIndex);
}
QModelIndex KExtraColumnsProxyModel::buddy(const QModelIndex &proxyIndex) const
{
if (sourceModel()) {
const int column = proxyIndex.column();
if (column >= sourceModel()->columnCount()) {
return proxyIndex;
}
}
return QIdentityProxyModel::buddy(proxyIndex);
}
QModelIndex KExtraColumnsProxyModel::sibling(int row, int column, const QModelIndex &idx) const
{
if (row == idx.row() && column == idx.column()) {
return idx;
}
return index(row, column, parent(idx));
}
QItemSelection KExtraColumnsProxyModel::mapSelectionToSource(const QItemSelection &selection) const
{
QItemSelection sourceSelection;
if (!sourceModel()) {
return sourceSelection;
}
// mapToSource will give invalid index for our additional columns, so truncate the selection
// to the columns known by the source model
const int sourceColumnCount = sourceModel()->columnCount();
QItemSelection::const_iterator it = selection.constBegin();
const QItemSelection::const_iterator end = selection.constEnd();
for (; it != end; ++it) {
Q_ASSERT(it->model() == this);
QModelIndex topLeft = it->topLeft();
Q_ASSERT(topLeft.isValid());
Q_ASSERT(topLeft.model() == this);
topLeft = topLeft.sibling(topLeft.row(), 0);
QModelIndex bottomRight = it->bottomRight();
Q_ASSERT(bottomRight.isValid());
Q_ASSERT(bottomRight.model() == this);
if (bottomRight.column() >= sourceColumnCount) {
bottomRight = bottomRight.sibling(bottomRight.row(), sourceColumnCount - 1);
}
// This can lead to duplicate source indexes, so use merge().
const QItemSelectionRange range(mapToSource(topLeft), mapToSource(bottomRight));
QItemSelection newSelection;
newSelection << range;
sourceSelection.merge(newSelection, QItemSelectionModel::Select);
}
return sourceSelection;
}
int KExtraColumnsProxyModel::columnCount(const QModelIndex &parent) const
{
Q_D(const KExtraColumnsProxyModel);
return QIdentityProxyModel::columnCount(parent) + d->m_extraHeaders.count();
}
QVariant KExtraColumnsProxyModel::data(const QModelIndex &index, int role) const
{
Q_D(const KExtraColumnsProxyModel);
const int extraCol = extraColumnForProxyColumn(index.column());
if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
return extraColumnData(index.parent(), index.row(), extraCol, role);
}
return sourceModel()->data(mapToSource(index), role);
}
bool KExtraColumnsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Q_D(const KExtraColumnsProxyModel);
const int extraCol = extraColumnForProxyColumn(index.column());
if (extraCol >= 0 && !d->m_extraHeaders.isEmpty()) {
return setExtraColumnData(index.parent(), index.row(), extraCol, value, role);
}
return sourceModel()->setData(mapToSource(index), value, role);
}
Qt::ItemFlags KExtraColumnsProxyModel::flags(const QModelIndex &index) const
{
const int extraCol = extraColumnForProxyColumn(index.column());
if (extraCol >= 0) {
// extra columns are readonly
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
return sourceModel() != nullptr ? sourceModel()->flags(mapToSource(index)) : Qt::NoItemFlags;
}
bool KExtraColumnsProxyModel::hasChildren(const QModelIndex &index) const
{
if (index.column() > 0) {
return false;
}
return QIdentityProxyModel::hasChildren(index);
}
QVariant KExtraColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_D(const KExtraColumnsProxyModel);
if (orientation == Qt::Horizontal) {
const int extraCol = extraColumnForProxyColumn(section);
if (extraCol >= 0) {
// Only text is supported, in headers for extra columns
if (role == Qt::DisplayRole) {
return d->m_extraHeaders.at(extraCol);
}
return QVariant();
}
}
return QIdentityProxyModel::headerData(section, orientation, role);
}
QModelIndex KExtraColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const
{
const int extraCol = extraColumnForProxyColumn(column);
if (extraCol >= 0) {
// We store the internal pointer of the index for column 0 in the proxy index for extra columns.
// This will be useful in the parent method.
return createIndex(row, column, QIdentityProxyModel::index(row, 0, parent).internalPointer());
}
return QIdentityProxyModel::index(row, column, parent);
}
QModelIndex KExtraColumnsProxyModel::parent(const QModelIndex &child) const
{
const int extraCol = extraColumnForProxyColumn(child.column());
if (extraCol >= 0) {
// Create an index for column 0 and use that to get the parent.
const QModelIndex proxySibling = createIndex(child.row(), 0, child.internalPointer());
return QIdentityProxyModel::parent(proxySibling);
}
return QIdentityProxyModel::parent(child);
}
int KExtraColumnsProxyModel::extraColumnForProxyColumn(int proxyColumn) const
{
if (sourceModel() != nullptr) {
const int sourceColumnCount = sourceModel()->columnCount();
if (proxyColumn >= sourceColumnCount) {
return proxyColumn - sourceColumnCount;
}
}
return -1;
}
int KExtraColumnsProxyModel::proxyColumnForExtraColumn(int extraColumn) const
{
return sourceModel()->columnCount() + extraColumn;
}
void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents,
QAbstractItemModel::LayoutChangeHint hint)
{
Q_Q(KExtraColumnsProxyModel);
QList<QPersistentModelIndex> parents;
parents.reserve(sourceParents.size());
for (const QPersistentModelIndex &parent : sourceParents) {
if (!parent.isValid()) {
parents << QPersistentModelIndex();
continue;
}
const QModelIndex mappedParent = q->mapFromSource(parent);
Q_ASSERT(mappedParent.isValid());
parents << mappedParent;
}
Q_EMIT q->layoutAboutToBeChanged(parents, hint);
const QModelIndexList persistentIndexList = q->persistentIndexList();
layoutChangePersistentIndexes.reserve(persistentIndexList.size());
layoutChangeProxyColumns.reserve(persistentIndexList.size());
for (QModelIndex proxyPersistentIndex : persistentIndexList) {
proxyIndexes << proxyPersistentIndex;
Q_ASSERT(proxyPersistentIndex.isValid());
const int column = proxyPersistentIndex.column();
layoutChangeProxyColumns << column;
if (column >= q->sourceModel()->columnCount()) {
proxyPersistentIndex = proxyPersistentIndex.sibling(proxyPersistentIndex.row(), 0);
}
const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
Q_ASSERT(srcPersistentIndex.isValid());
layoutChangePersistentIndexes << srcPersistentIndex;
}
}
void KExtraColumnsProxyModelPrivate::_ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
{
Q_Q(KExtraColumnsProxyModel);
for (int i = 0; i < proxyIndexes.size(); ++i) {
const QModelIndex proxyIdx = proxyIndexes.at(i);
QModelIndex newProxyIdx = q->mapFromSource(layoutChangePersistentIndexes.at(i));
if (proxyIdx.column() >= q->sourceModel()->columnCount()) {
newProxyIdx = newProxyIdx.sibling(newProxyIdx.row(), layoutChangeProxyColumns.at(i));
}
q->changePersistentIndex(proxyIdx, newProxyIdx);
}
layoutChangePersistentIndexes.clear();
layoutChangeProxyColumns.clear();
proxyIndexes.clear();
QList<QPersistentModelIndex> parents;
parents.reserve(sourceParents.size());
for (const QPersistentModelIndex &parent : sourceParents) {
if (!parent.isValid()) {
parents << QPersistentModelIndex();
continue;
}
const QModelIndex mappedParent = q->mapFromSource(parent);
Q_ASSERT(mappedParent.isValid());
parents << mappedParent;
}
Q_EMIT q->layoutChanged(parents, hint);
}
#include "moc_kextracolumnsproxymodel.cpp"
@@ -0,0 +1,144 @@
/*
SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-FileContributor: David Faure <david.faure@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KEXTRACOLUMNSPROXYMODEL_H
#define KEXTRACOLUMNSPROXYMODEL_H
#include "kitemmodels_export.h"
#include <QIdentityProxyModel>
#include <memory>
class KExtraColumnsProxyModelPrivate;
/**
* @class KExtraColumnsProxyModel kextracolumnsproxymodel.h KExtraColumnsProxyModel
*
* This proxy appends extra columns (after all existing columns).
*
* The proxy supports source models that have a tree structure.
* It also supports editing, and propagating changes from the source model.
* Row insertion/removal, column insertion/removal in the source model are supported.
*
* Not supported: adding/removing extra columns at runtime; having a different number of columns in subtrees;
* drag-n-drop support in the extra columns; moving columns.
*
* Derive from KExtraColumnsProxyModel, call appendColumn (typically in the constructor) for each extra column,
* and reimplement extraColumnData() to allow KExtraColumnsProxyModel to retrieve the data to show in the extra columns.
*
* If you want your new column(s) to be somewhere else than at the right of the existing columns, you can
* use a KRearrangeColumnsProxyModel on top.
*
* Author: David Faure, KDAB
* @since 5.13
*/
class KITEMMODELS_EXPORT KExtraColumnsProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
/**
* Base class constructor.
* Remember to call setSourceModel afterwards, and appendColumn.
*/
explicit KExtraColumnsProxyModel(QObject *parent = nullptr);
/**
* Destructor.
*/
~KExtraColumnsProxyModel() override;
// API
/**
* Appends an extra column.
* @param header an optional text for the horizontal header
* This does not emit any signals - do it in the initial setup phase
*/
void appendColumn(const QString &header = QString());
/**
* Removes an extra column.
* @param idx index of the extra column (starting from 0).
* This does not emit any signals - do it in the initial setup phase
* @since 5.24
*/
void removeExtraColumn(int idx);
/**
* This method is called by data() for extra columns.
* Reimplement this method to return the data for the extra columns.
*
* @param parent the parent model index in the proxy model (only useful in tree models)
* @param row the row number for which the proxy model is querying for data (child of @p parent, if set)
* @param extraColumn the number of the extra column, starting at 0 (this doesn't require knowing how many columns the source model has)
* @param role the role being queried
* @return the data at @p row and @p extraColumn
*/
virtual QVariant extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role = Qt::DisplayRole) const = 0;
// KF6 TODO: add extraColumnFlags() virtual method
/**
* This method is called by setData() for extra columns.
* Reimplement this method to set the data for the extra columns, if editing is supported.
* Remember to call extraColumnDataChanged() after changing the data storage.
* The default implementation returns false.
*/
virtual bool setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role = Qt::EditRole);
/**
* This method can be called by your derived class when the data in an extra column has changed.
* The use case is data that changes "by itself", unrelated to setData.
*/
void extraColumnDataChanged(const QModelIndex &parent, int row, int extraColumn, const QList<int> &roles);
/**
* Returns the extra column number (0, 1, ...) for a given column number of the proxymodel.
* This basically means subtracting the amount of columns in the source model.
*/
int extraColumnForProxyColumn(int proxyColumn) const;
/**
* Returns the proxy column number for a given extra column number (starting at 0).
* This basically means adding the amount of columns in the source model.
*/
int proxyColumnForExtraColumn(int extraColumn) const;
// Implementation
/// @reimp
void setSourceModel(QAbstractItemModel *model) override;
/// @reimp
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
/// @reimp
QItemSelection mapSelectionToSource(const QItemSelection &selection) const override;
/// @reimp
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/// @reimp
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
/// @reimp
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
/// @reimp
QModelIndex buddy(const QModelIndex &index) const override;
/// @reimp
Qt::ItemFlags flags(const QModelIndex &index) const override;
/// @reimp
bool hasChildren(const QModelIndex &index) const override;
/// @reimp
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
/// @reimp
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
QModelIndex parent(const QModelIndex &child) const override;
private:
Q_DECLARE_PRIVATE(KExtraColumnsProxyModel)
Q_PRIVATE_SLOT(d_func(), void _ec_sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint))
Q_PRIVATE_SLOT(d_func(), void _ec_sourceLayoutChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint))
std::unique_ptr<KExtraColumnsProxyModelPrivate> const d_ptr;
};
#endif
@@ -0,0 +1,193 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "klinkitemselectionmodel.h"
#include "kitemmodels_debug.h"
#include "kmodelindexproxymapper.h"
#include <QItemSelection>
class KLinkItemSelectionModelPrivate
{
public:
KLinkItemSelectionModelPrivate(KLinkItemSelectionModel *proxySelectionModel)
: q_ptr(proxySelectionModel)
{
QObject::connect(q_ptr, &QItemSelectionModel::currentChanged, q_ptr, [this](const QModelIndex &idx) {
slotCurrentChanged(idx);
});
QObject::connect(q_ptr, &QItemSelectionModel::modelChanged, q_ptr, [this] {
reinitializeIndexMapper();
});
}
Q_DECLARE_PUBLIC(KLinkItemSelectionModel)
KLinkItemSelectionModel *const q_ptr;
bool assertSelectionValid(const QItemSelection &selection) const
{
for (const QItemSelectionRange &range : selection) {
if (!range.isValid()) {
qCDebug(KITEMMODELS_LOG) << selection;
}
Q_ASSERT(range.isValid());
}
return true;
}
void reinitializeIndexMapper()
{
delete m_indexMapper;
m_indexMapper = nullptr;
if (!q_ptr->model() || !m_linkedItemSelectionModel || !m_linkedItemSelectionModel->model()) {
return;
}
m_indexMapper = new KModelIndexProxyMapper(q_ptr->model(), m_linkedItemSelectionModel->model(), q_ptr);
const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(m_linkedItemSelectionModel->selection());
q_ptr->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::ClearAndSelect);
}
void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void sourceCurrentChanged(const QModelIndex &current);
void slotCurrentChanged(const QModelIndex &current);
QItemSelectionModel *m_linkedItemSelectionModel = nullptr;
bool m_ignoreCurrentChanged = false;
KModelIndexProxyMapper *m_indexMapper = nullptr;
};
KLinkItemSelectionModel::KLinkItemSelectionModel(QAbstractItemModel *model, QItemSelectionModel *proxySelector, QObject *parent)
: QItemSelectionModel(model, parent)
, d_ptr(new KLinkItemSelectionModelPrivate(this))
{
setLinkedItemSelectionModel(proxySelector);
}
KLinkItemSelectionModel::KLinkItemSelectionModel(QObject *parent)
: QItemSelectionModel(nullptr, parent)
, d_ptr(new KLinkItemSelectionModelPrivate(this))
{
}
KLinkItemSelectionModel::~KLinkItemSelectionModel() = default;
QItemSelectionModel *KLinkItemSelectionModel::linkedItemSelectionModel() const
{
Q_D(const KLinkItemSelectionModel);
return d->m_linkedItemSelectionModel;
}
void KLinkItemSelectionModel::setLinkedItemSelectionModel(QItemSelectionModel *selectionModel)
{
Q_D(KLinkItemSelectionModel);
if (d->m_linkedItemSelectionModel != selectionModel) {
if (d->m_linkedItemSelectionModel) {
disconnect(d->m_linkedItemSelectionModel);
}
d->m_linkedItemSelectionModel = selectionModel;
if (d->m_linkedItemSelectionModel) {
connect(d->m_linkedItemSelectionModel,
&QItemSelectionModel::selectionChanged,
this,
[d](const QItemSelection &selected, const QItemSelection &deselected) {
d->sourceSelectionChanged(selected, deselected);
});
connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::currentChanged, this, [d](const QModelIndex &current) {
d->sourceCurrentChanged(current);
});
connect(d->m_linkedItemSelectionModel, &QItemSelectionModel::modelChanged, this, [this] {
d_ptr->reinitializeIndexMapper();
});
}
d->reinitializeIndexMapper();
Q_EMIT linkedItemSelectionModelChanged();
}
}
void KLinkItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
{
Q_D(KLinkItemSelectionModel);
// When an item is removed, the current index is set to the top index in the model.
// That causes a selectionChanged signal with a selection which we do not want.
if (d->m_ignoreCurrentChanged) {
return;
}
// Do *not* replace next line with: QItemSelectionModel::select(index, command)
//
// Doing so would end up calling KLinkItemSelectionModel::select(QItemSelection, QItemSelectionModel::SelectionFlags)
//
// This is because the code for QItemSelectionModel::select(QModelIndex, QItemSelectionModel::SelectionFlags) looks like this:
// {
// QItemSelection selection(index, index);
// select(selection, command);
// }
// So it calls KLinkItemSelectionModel overload of
// select(QItemSelection, QItemSelectionModel::SelectionFlags)
//
// When this happens and the selection flags include Toggle, it causes the
// selection to be toggled twice.
QItemSelectionModel::select(QItemSelection(index, index), command);
if (index.isValid()) {
d->m_linkedItemSelectionModel->select(d->m_indexMapper->mapSelectionLeftToRight(QItemSelection(index, index)), command);
} else {
d->m_linkedItemSelectionModel->clearSelection();
}
}
void KLinkItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
{
Q_D(KLinkItemSelectionModel);
d->m_ignoreCurrentChanged = true;
QItemSelection _selection = selection;
QItemSelectionModel::select(_selection, command);
Q_ASSERT(d->assertSelectionValid(_selection));
QItemSelection mappedSelection = d->m_indexMapper->mapSelectionLeftToRight(_selection);
Q_ASSERT(d->assertSelectionValid(mappedSelection));
d->m_linkedItemSelectionModel->select(mappedSelection, command);
d->m_ignoreCurrentChanged = false;
}
void KLinkItemSelectionModelPrivate::slotCurrentChanged(const QModelIndex &current)
{
const QModelIndex mappedCurrent = m_indexMapper->mapLeftToRight(current);
if (!mappedCurrent.isValid()) {
return;
}
m_linkedItemSelectionModel->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate);
}
void KLinkItemSelectionModelPrivate::sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_Q(KLinkItemSelectionModel);
QItemSelection _selected = selected;
QItemSelection _deselected = deselected;
Q_ASSERT(assertSelectionValid(_selected));
Q_ASSERT(assertSelectionValid(_deselected));
const QItemSelection mappedDeselection = m_indexMapper->mapSelectionRightToLeft(_deselected);
const QItemSelection mappedSelection = m_indexMapper->mapSelectionRightToLeft(_selected);
q->QItemSelectionModel::select(mappedDeselection, QItemSelectionModel::Deselect);
q->QItemSelectionModel::select(mappedSelection, QItemSelectionModel::Select);
}
void KLinkItemSelectionModelPrivate::sourceCurrentChanged(const QModelIndex &current)
{
Q_Q(KLinkItemSelectionModel);
const QModelIndex mappedCurrent = m_indexMapper->mapRightToLeft(current);
if (!mappedCurrent.isValid()) {
return;
}
q->setCurrentIndex(mappedCurrent, QItemSelectionModel::NoUpdate);
}
#include "moc_klinkitemselectionmodel.cpp"
@@ -0,0 +1,122 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLINKITEMSELECTIONMODEL_H
#define KLINKITEMSELECTIONMODEL_H
#include <QItemSelectionModel>
#include "kitemmodels_export.h"
#include <memory>
class KLinkItemSelectionModelPrivate;
/**
@class KLinkItemSelectionModel klinkitemselectionmodel.h KLinkItemSelectionModel
@brief Makes it possible to share a selection in multiple views which do not have the same source model
Although <a href="https://doc.qt.io/qt-5/model-view-programming.html#handling-selections-of-items">multiple views can share the same QItemSelectionModel</a>,
the views then need to have the same source model.
If there is a proxy model between the model and one of the views, or different proxy models in each, this class makes
it possible to share the selection between the views.
@image html kproxyitemselectionmodel-simple.png "Sharing a QItemSelectionModel between views on the same model is trivial"
@image html kproxyitemselectionmodel-error.png "If a proxy model is used, it is no longer possible to share the QItemSelectionModel directly"
@image html kproxyitemselectionmodel-solution.png "A KLinkItemSelectionModel can be used to map the selection through the proxy model"
@code
QAbstractItemModel *model = getModel();
QSortFilterProxyModel *proxy = new QSortFilterProxyModel();
proxy->setSourceModel(model);
QTreeView *view1 = new QTreeView(splitter);
view1->setModel(model);
KLinkItemSelectionModel *view2SelectionModel = new KLinkItemSelectionModel( proxy, view1->selectionModel());
QTreeView *view2 = new QTreeView(splitter);
// Note that the QAbstractItemModel passed to KLinkItemSelectionModel must be the same as what is used in the view
view2->setModel(proxy);
view2->setSelectionModel( view2SelectionModel );
@endcode
@image html kproxyitemselectionmodel-complex.png "Arbitrarily complex proxy configurations on the same root model can be used"
@code
QAbstractItemModel *model = getModel();
QSortFilterProxyModel *proxy1 = new QSortFilterProxyModel();
proxy1->setSourceModel(model);
QSortFilterProxyModel *proxy2 = new QSortFilterProxyModel();
proxy2->setSourceModel(proxy1);
QSortFilterProxyModel *proxy3 = new QSortFilterProxyModel();
proxy3->setSourceModel(proxy2);
QTreeView *view1 = new QTreeView(splitter);
view1->setModel(proxy3);
QSortFilterProxyModel *proxy4 = new QSortFilterProxyModel();
proxy4->setSourceModel(model);
QSortFilterProxyModel *proxy5 = new QSortFilterProxyModel();
proxy5->setSourceModel(proxy4);
KLinkItemSelectionModel *view2SelectionModel = new KLinkItemSelectionModel( proxy5, view1->selectionModel());
QTreeView *view2 = new QTreeView(splitter);
// Note that the QAbstractItemModel passed to KLinkItemSelectionModel must be the same as what is used in the view
view2->setModel(proxy5);
view2->setSelectionModel( view2SelectionModel );
@endcode
See also <a href="https://commits.kde.org/kitemmodels?path=tests/proxymodeltestapp/proxyitemselectionwidget.cpp">kitemmodels:
tests/proxymodeltestapp/proxyitemselectionwidget.cpp</a>.
@since 4.5
@author Stephen Kelly <steveire@gmail.com>
*/
class KITEMMODELS_EXPORT KLinkItemSelectionModel : public QItemSelectionModel
{
Q_OBJECT
Q_PROPERTY(
QItemSelectionModel *linkedItemSelectionModel READ linkedItemSelectionModel WRITE setLinkedItemSelectionModel NOTIFY linkedItemSelectionModelChanged)
public:
/**
Constructor.
*/
KLinkItemSelectionModel(QAbstractItemModel *targetModel, QItemSelectionModel *linkedItemSelectionModel, QObject *parent = nullptr);
explicit KLinkItemSelectionModel(QObject *parent = nullptr);
~KLinkItemSelectionModel() override;
QItemSelectionModel *linkedItemSelectionModel() const;
void setLinkedItemSelectionModel(QItemSelectionModel *selectionModel);
void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) override;
void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override;
Q_SIGNALS:
void linkedItemSelectionModelChanged();
protected:
std::unique_ptr<KLinkItemSelectionModelPrivate> const d_ptr;
private:
Q_DECLARE_PRIVATE(KLinkItemSelectionModel)
Q_PRIVATE_SLOT(d_func(), void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected))
Q_PRIVATE_SLOT(d_func(), void sourceCurrentChanged(const QModelIndex &current))
Q_PRIVATE_SLOT(d_func(), void slotCurrentChanged(const QModelIndex &current))
};
#endif
@@ -0,0 +1,287 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmodelindexproxymapper.h"
#include "kitemmodels_debug.h"
#include <QAbstractItemModel>
#include <QAbstractProxyModel>
#include <QItemSelectionModel>
#include <QPointer>
class KModelIndexProxyMapperPrivate
{
KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
: q_ptr(qq)
, m_leftModel(leftModel)
, m_rightModel(rightModel)
, mConnected(false)
{
createProxyChain();
}
void createProxyChain();
void checkConnected();
void setConnected(bool connected);
bool assertSelectionValid(const QItemSelection &selection) const
{
for (const QItemSelectionRange &range : selection) {
if (!range.isValid()) {
qCDebug(KITEMMODELS_LOG) << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp;
}
Q_ASSERT(range.isValid());
}
return true;
}
Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
KModelIndexProxyMapper *const q_ptr;
QList<QPointer<const QAbstractProxyModel>> m_proxyChainUp;
QList<QPointer<const QAbstractProxyModel>> m_proxyChainDown;
QPointer<const QAbstractItemModel> m_leftModel;
QPointer<const QAbstractItemModel> m_rightModel;
bool mConnected;
};
/*
The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the
proxy chain. We need to build up to two chains of proxy models to create mappings between them.
Example 1:
Root model
|
/ \
Proxy 1 Proxy 3
| |
Proxy 2 Proxy 4
Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
Example 2:
Root model
|
Proxy 1
|
Proxy 2
/ \
Proxy 3 Proxy 6
| |
Proxy 4 Proxy 7
|
Proxy 5
We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is
already in the first chain.
Stephen Kelly, 30 March 2010.
*/
void KModelIndexProxyMapperPrivate::createProxyChain()
{
for (auto p : std::as_const(m_proxyChainUp)) {
p->disconnect(q_ptr);
}
for (auto p : std::as_const(m_proxyChainDown)) {
p->disconnect(q_ptr);
}
m_proxyChainUp.clear();
m_proxyChainDown.clear();
QPointer<const QAbstractItemModel> targetModel = m_rightModel;
QList<QPointer<const QAbstractProxyModel>> proxyChainDown;
QPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(targetModel);
while (selectionTargetProxyModel) {
proxyChainDown.prepend(selectionTargetProxyModel);
QObject::connect(selectionTargetProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
createProxyChain();
});
selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(selectionTargetProxyModel->sourceModel());
if (selectionTargetProxyModel == m_leftModel) {
m_proxyChainDown = proxyChainDown;
checkConnected();
return;
}
}
QPointer<const QAbstractItemModel> sourceModel = m_leftModel;
QPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceModel);
while (sourceProxyModel) {
m_proxyChainUp.append(sourceProxyModel);
QObject::connect(sourceProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
createProxyChain();
});
sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceProxyModel->sourceModel());
const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
if (targetIndex != -1) {
m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size());
checkConnected();
return;
}
}
m_proxyChainDown = proxyChainDown;
checkConnected();
}
void KModelIndexProxyMapperPrivate::checkConnected()
{
auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel();
auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel();
setConnected(konamiLeft && (konamiLeft == konamiRight));
}
void KModelIndexProxyMapperPrivate::setConnected(bool connected)
{
if (mConnected != connected) {
Q_Q(KModelIndexProxyMapper);
mConnected = connected;
Q_EMIT q->isConnectedChanged();
}
}
KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent)
: QObject(parent)
, d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this))
{
}
KModelIndexProxyMapper::~KModelIndexProxyMapper() = default;
QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex &index) const
{
const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
if (selection.isEmpty()) {
return QModelIndex();
}
return selection.indexes().first();
}
QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex &index) const
{
const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
if (selection.isEmpty()) {
return QModelIndex();
}
return selection.indexes().first();
}
QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection &selection) const
{
Q_D(const KModelIndexProxyMapper);
if (selection.isEmpty() || !d->mConnected) {
return QItemSelection();
}
if (selection.first().model() != d->m_leftModel) {
qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
}
Q_ASSERT(selection.first().model() == d->m_leftModel);
QItemSelection seekSelection = selection;
Q_ASSERT(d->assertSelectionValid(seekSelection));
QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
while (iUp.hasNext()) {
const QPointer<const QAbstractProxyModel> proxy = iUp.next();
if (!proxy) {
return QItemSelection();
}
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
seekSelection = proxy->mapSelectionToSource(seekSelection);
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
Q_ASSERT(d->assertSelectionValid(seekSelection));
}
QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
while (iDown.hasNext()) {
const QPointer<const QAbstractProxyModel> proxy = iDown.next();
if (!proxy) {
return QItemSelection();
}
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
seekSelection = proxy->mapSelectionFromSource(seekSelection);
Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
Q_ASSERT(d->assertSelectionValid(seekSelection));
}
Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel) || true);
return seekSelection;
}
QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection &selection) const
{
Q_D(const KModelIndexProxyMapper);
if (selection.isEmpty() || !d->mConnected) {
return QItemSelection();
}
if (selection.first().model() != d->m_rightModel) {
qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
}
Q_ASSERT(selection.first().model() == d->m_rightModel);
QItemSelection seekSelection = selection;
Q_ASSERT(d->assertSelectionValid(seekSelection));
QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
iDown.toBack();
while (iDown.hasPrevious()) {
const QPointer<const QAbstractProxyModel> proxy = iDown.previous();
if (!proxy) {
return QItemSelection();
}
seekSelection = proxy->mapSelectionToSource(seekSelection);
Q_ASSERT(d->assertSelectionValid(seekSelection));
}
QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
iUp.toBack();
while (iUp.hasPrevious()) {
const QPointer<const QAbstractProxyModel> proxy = iUp.previous();
if (!proxy) {
return QItemSelection();
}
seekSelection = proxy->mapSelectionFromSource(seekSelection);
Q_ASSERT(d->assertSelectionValid(seekSelection));
}
Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel) || true);
return seekSelection;
}
bool KModelIndexProxyMapper::isConnected() const
{
Q_D(const KModelIndexProxyMapper);
return d->mConnected;
}
#include "moc_kmodelindexproxymapper.cpp"
@@ -0,0 +1,124 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMODELINDEXPROXYMAPPER_H
#define KMODELINDEXPROXYMAPPER_H
#include <QObject>
#include "kitemmodels_export.h"
#include <memory>
class QAbstractItemModel;
class QModelIndex;
class QItemSelection;
class KModelIndexProxyMapperPrivate;
/**
* @class KModelIndexProxyMapper kmodelindexproxymapper.h KModelIndexProxyMapper
*
* @brief This class facilitates easy mapping of indexes and selections through proxy models.
*
* In a complex system of proxy models there can be a need to map indexes and selections between them,
* and sometimes to do so without knowledge of the path from one model to another.
*
* For example,
*
* @verbatim
* Root model
* |
* / \
* Proxy 1 Proxy 3
* | |
* Proxy 2 Proxy 4
* @endverbatim
*
* If there is a need to map indexes between proxy 2 and proxy 4, a KModelIndexProxyMapper can be created
* to facilitate mapping of indexes between them.
*
* @code
* m_indexMapper = new KModelIndexProxyMapper(proxy2, proxy4, this);
*
* ...
*
* const QModelIndex proxy4Index = m_mapLeftToRight(proxy2->index(0, 0));
* Q_ASSERT(proxy4Index.model() == proxy4);
* @endcode
*
* Note that the aim is to achieve black box connections so that there is no need for application code to
* know the structure of proxy models in the path between left and right and attempt to manually map them.
*
* @verbatim
* Root model
* |
* ---------------
* | Black Box |
* ---------------
* | |
* Proxy 2 Proxy 4
* @endverbatim
*
* The isConnected property indicates whether there is a
* path from the left side to the right side.
*
* @author Stephen Kelly <steveire@gmail.com>
*
*/
class KITEMMODELS_EXPORT KModelIndexProxyMapper : public QObject
{
Q_OBJECT
/**
* Indicates whether there is a chain that can be followed from leftModel to rightModel.
*
* This value can change if the sourceModel of an intermediate proxy is changed.
*/
Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged)
public:
/**
* Constructor
*/
KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent = nullptr);
~KModelIndexProxyMapper() override;
/**
* Maps the @p index from the left model to the right model.
*/
QModelIndex mapLeftToRight(const QModelIndex &index) const;
/**
* Maps the @p index from the right model to the left model.
*/
QModelIndex mapRightToLeft(const QModelIndex &index) const;
/**
* Maps the @p selection from the left model to the right model.
*/
QItemSelection mapSelectionLeftToRight(const QItemSelection &selection) const;
/**
* Maps the @p selection from the right model to the left model.
*/
QItemSelection mapSelectionRightToLeft(const QItemSelection &selection) const;
bool isConnected() const;
Q_SIGNALS:
void isConnectedChanged();
private:
//@cond PRIVATE
Q_DECLARE_PRIVATE(KModelIndexProxyMapper)
std::unique_ptr<KModelIndexProxyMapperPrivate> const d_ptr;
//@endcond
};
#endif
@@ -0,0 +1,138 @@
/*
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knumbermodel.h"
#include <QtMath>
#include <cmath>
class KNumberModelPrivate
{
public:
qreal minimumValue = 0.0;
qreal maximumValue = 0.0;
qreal stepSize = 1.0;
QLocale::NumberOptions formattingOptions = QLocale::DefaultNumberOptions;
};
KNumberModel::KNumberModel(QObject *parent)
: QAbstractListModel(parent)
, d(new KNumberModelPrivate)
{
}
KNumberModel::~KNumberModel()
{
}
void KNumberModel::setMinimumValue(qreal minimumValue)
{
if (minimumValue == d->minimumValue) {
return;
}
beginResetModel();
d->minimumValue = minimumValue;
endResetModel();
Q_EMIT minimumValueChanged();
}
qreal KNumberModel::minimumValue() const
{
return d->minimumValue;
}
void KNumberModel::setMaximumValue(qreal maximumValue)
{
if (maximumValue == d->maximumValue) {
return;
}
beginResetModel();
d->maximumValue = maximumValue;
endResetModel();
Q_EMIT maximumValueChanged();
}
qreal KNumberModel::maximumValue() const
{
return d->maximumValue;
}
void KNumberModel::setStepSize(qreal stepSize)
{
if (stepSize == d->stepSize) {
return;
}
beginResetModel();
d->stepSize = stepSize;
endResetModel();
Q_EMIT stepSizeChanged();
}
qreal KNumberModel::stepSize() const
{
return d->stepSize;
}
void KNumberModel::setFormattingOptions(QLocale::NumberOptions formattingOptions)
{
if (d->formattingOptions == formattingOptions) {
return;
}
d->formattingOptions = formattingOptions;
if (rowCount() == 0) {
return;
}
dataChanged(index(0, 0, QModelIndex()), index(rowCount(), 0, QModelIndex()), QList<int>{DisplayRole});
Q_EMIT formattingOptionsChanged();
}
QLocale::NumberOptions KNumberModel::formattingOptions() const
{
return d->formattingOptions;
}
qreal KNumberModel::value(const QModelIndex &index) const
{
if (!index.isValid()) {
return 0.0;
}
return d->minimumValue + d->stepSize * index.row();
}
int KNumberModel::rowCount(const QModelIndex &index) const
{
if (index.parent().isValid()) {
return 0;
}
if (d->stepSize == 0) {
return 1;
}
// 1 initial entry (the minimumValue) + the number of valid steps afterwards
return 1 + std::max(0, qFloor((d->maximumValue - d->minimumValue) / d->stepSize));
}
QVariant KNumberModel::data(const QModelIndex &index, int role) const
{
switch (role) {
case KNumberModel::DisplayRole: {
auto locale = QLocale();
locale.setNumberOptions(d->formattingOptions);
return QVariant(locale.toString(value(index)));
}
case KNumberModel::ValueRole:
return QVariant(value(index));
}
return QVariant();
}
QHash<int, QByteArray> KNumberModel::roleNames() const
{
return {{KNumberModel::DisplayRole, QByteArrayLiteral("display")}, {KNumberModel::ValueRole, QByteArrayLiteral("value")}};
}
#include "moc_knumbermodel.cpp"
@@ -0,0 +1,104 @@
/*
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KNUMBERMODEL_H
#define KNUMBERMODEL_H
#include <QAbstractListModel>
#include <QLocale>
#include "kitemmodels_export.h"
#include <memory>
class KNumberModelPrivate;
/**
* @class KNumberModel knumbermodel.h KNumberModel
*
* Creates a model of entries from N to M with rows at a given interval
*
* The model contains two roles:
* @li display - the number represented as a string
* @li value - the actual value as a number
*
* @since 5.65
*/
class KITEMMODELS_EXPORT KNumberModel : public QAbstractListModel
{
Q_OBJECT
/**
* The minimum value for the model
*
* The default value is @c 1.0.
*/
Q_PROPERTY(qreal minimumValue READ minimumValue WRITE setMinimumValue NOTIFY minimumValueChanged)
/**
* The maximum value for the model
*
* The default value is @c 1.0.
*
* @note If @c maximumValue is a multiple of @c stepSize added to @c minimumValue
* it will be included. Otherwise it will not be reached.
* E.g. in a model with a @c minimumValue of 0.0, a @c maximumValue of 1.0 and a @c stepSize of 0.3, the final row will be 0.9.
*/
Q_PROPERTY(qreal maximumValue READ maximumValue WRITE setMaximumValue NOTIFY maximumValueChanged)
/**
* Step between listed entries
*
* The default value is @c 1.0.
*/
Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged)
/**
* Defines the string representation of the number,
* e.g. "1,000" or "1000".
*
* Default is @c QLocale::Default.
*/
Q_PROPERTY(QLocale::NumberOptions formattingOptions READ formattingOptions WRITE setFormattingOptions NOTIFY formattingOptionsChanged)
public:
explicit KNumberModel(QObject *parent = nullptr);
~KNumberModel() override;
enum Roles {
DisplayRole = Qt::DisplayRole,
ValueRole = Qt::UserRole,
};
void setMinimumValue(qreal minimumValue);
qreal minimumValue() const;
void setMaximumValue(qreal maximumValue);
qreal maximumValue() const;
void setStepSize(qreal stepSize);
qreal stepSize() const;
void setFormattingOptions(QLocale::NumberOptions options);
QLocale::NumberOptions formattingOptions() const;
/**
* Returns the value represented at the given index.
*/
qreal value(const QModelIndex &index) const;
int rowCount(const QModelIndex &index = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void minimumValueChanged();
void maximumValueChanged();
void stepSizeChanged();
void formattingOptionsChanged();
private:
std::unique_ptr<KNumberModelPrivate> const d;
};
#endif
@@ -0,0 +1,167 @@
/*
SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-FileContributor: David Faure <david.faure@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "krearrangecolumnsproxymodel.h"
class KRearrangeColumnsProxyModelPrivate
{
public:
QList<int> m_sourceColumns;
};
KRearrangeColumnsProxyModel::KRearrangeColumnsProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
, d_ptr(new KRearrangeColumnsProxyModelPrivate)
{
}
KRearrangeColumnsProxyModel::~KRearrangeColumnsProxyModel()
{
}
void KRearrangeColumnsProxyModel::setSourceColumns(const QList<int> &columns)
{
// We could use layoutChanged() here, but we would have to map persistent
// indexes from the old to the new location...
beginResetModel();
d_ptr->m_sourceColumns = columns;
endResetModel();
}
int KRearrangeColumnsProxyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (!sourceModel()) {
return 0;
}
return d_ptr->m_sourceColumns.count();
}
int KRearrangeColumnsProxyModel::rowCount(const QModelIndex &parent) const
{
Q_ASSERT(parent.isValid() ? parent.model() == this : true);
if (!sourceModel()) {
return 0;
}
if (parent.column() > 0) {
return 0;
}
// The parent in the source model is on column 0, whatever swapping we are doing
const QModelIndex sourceParent = mapToSource(parent).sibling(parent.row(), 0);
return sourceModel()->rowCount(sourceParent);
}
bool KRearrangeColumnsProxyModel::hasChildren(const QModelIndex &parent) const
{
Q_ASSERT(parent.isValid() ? parent.model() == this : true);
if (!sourceModel()) {
return false;
}
if (d_ptr->m_sourceColumns.isEmpty()) { // no columns configured yet
return false;
}
if (parent.column() > 0) {
return false;
}
const QModelIndex sourceParent = mapToSource(parent).sibling(parent.row(), 0);
return sourceModel()->rowCount(sourceParent) > 0;
}
QModelIndex KRearrangeColumnsProxyModel::index(int row, int column, const QModelIndex &parent) const
{
Q_ASSERT(parent.isValid() ? parent.model() == this : true);
Q_ASSERT(row >= 0);
Q_ASSERT(column >= 0);
// Only first column has children
if (parent.column() > 0) {
return {};
}
if (!sourceModel()) {
return {};
}
if (d_ptr->m_sourceColumns.isEmpty()) {
return {};
}
// The parent in the source model is on column 0, whatever swapping we are doing
const QModelIndex sourceParent = mapToSource(parent).sibling(parent.row(), 0);
// Find the child in the source model, we need its internal pointer
const QModelIndex sourceIndex = sourceModel()->index(row, sourceColumnForProxyColumn(column), sourceParent);
if (!sourceIndex.isValid()) {
return QModelIndex();
}
return createIndex(row, column, sourceIndex.internalPointer());
}
QModelIndex KRearrangeColumnsProxyModel::parent(const QModelIndex &child) const
{
Q_ASSERT(child.isValid() ? child.model() == this : true);
const QModelIndex sourceIndex = mapToSource(child);
const QModelIndex sourceParent = sourceIndex.parent();
if (!sourceParent.isValid()) {
return QModelIndex();
}
return createIndex(sourceParent.row(), 0, sourceParent.internalPointer());
}
QVariant KRearrangeColumnsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal) {
if (!sourceModel() || section >= d_ptr->m_sourceColumns.count()) {
return QVariant();
}
const int sourceCol = sourceColumnForProxyColumn(section);
return sourceModel()->headerData(sourceCol, orientation, role);
} else {
return QIdentityProxyModel::headerData(section, orientation, role);
}
}
QModelIndex KRearrangeColumnsProxyModel::sibling(int row, int column, const QModelIndex &idx) const
{
if (column >= d_ptr->m_sourceColumns.count()) {
return QModelIndex();
}
return index(row, column, idx.parent());
}
QModelIndex KRearrangeColumnsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid()) {
return QModelIndex();
}
Q_ASSERT(sourceIndex.model() == sourceModel());
const int proxyColumn = proxyColumnForSourceColumn(sourceIndex.column());
return createIndex(sourceIndex.row(), proxyColumn, sourceIndex.internalPointer());
}
QModelIndex KRearrangeColumnsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid()) {
return QModelIndex();
}
return createSourceIndex(proxyIndex.row(), sourceColumnForProxyColumn(proxyIndex.column()), proxyIndex.internalPointer());
}
int KRearrangeColumnsProxyModel::proxyColumnForSourceColumn(int sourceColumn) const
{
// If this is too slow, we could add a second QList with index=logical_source_column value=desired_pos_in_proxy.
return d_ptr->m_sourceColumns.indexOf(sourceColumn);
}
int KRearrangeColumnsProxyModel::sourceColumnForProxyColumn(int proxyColumn) const
{
Q_ASSERT(proxyColumn >= 0);
Q_ASSERT(proxyColumn < d_ptr->m_sourceColumns.size());
return d_ptr->m_sourceColumns.at(proxyColumn);
}
#include "moc_krearrangecolumnsproxymodel.cpp"
@@ -0,0 +1,102 @@
/*
SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
SPDX-FileContributor: David Faure <david.faure@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef REARRANGECOLUMNSPROXYMODEL_H
#define REARRANGECOLUMNSPROXYMODEL_H
#include "kitemmodels_export.h"
#include <QIdentityProxyModel>
#include <memory>
class KRearrangeColumnsProxyModelPrivate;
/**
* @class KRearrangeColumnsProxyModel krearrangecolumnsproxymodel.h KRearrangeColumnsProxyModel
*
* This proxy shows specific columns from the source model, in any order.
* This allows to reorder columns, as well as not showing all of them.
*
* The proxy supports source models that have a tree structure.
* It also supports editing, and propagating changes from the source model.
*
* Showing the same source column more than once is not supported.
*
* Author: David Faure, KDAB
* @since 5.12
*/
class KITEMMODELS_EXPORT KRearrangeColumnsProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
/**
* Creates a KRearrangeColumnsProxyModel proxy.
* Remember to call setSourceModel afterwards.
*/
explicit KRearrangeColumnsProxyModel(QObject *parent = nullptr);
/**
* Destructor.
*/
~KRearrangeColumnsProxyModel() override;
// API
/**
* Set the chosen source columns, in the desired order for the proxy columns
* columns[proxyColumn=0] is the source column to show in the first proxy column, etc.
*
* Example: QList<int>() << 2 << 1;
* This examples configures the proxy to hide column 0, show column 2 from the source model,
* then show column 1 from the source model.
*/
void setSourceColumns(const QList<int> &columns);
// Implementation
/// @reimp
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
QModelIndex parent(const QModelIndex &child) const override;
/// @reimp
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
/// @reimp
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
/// @reimp
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
/// @reimp
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
/// @reimp
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
/**
* Returns the proxy column for the given source column
* or -1 if the source column isn't shown in the proxy.
* @since 5.56
*/
int proxyColumnForSourceColumn(int sourceColumn) const;
/**
* Returns the source column for the given proxy column.
* @since 5.56
*/
int sourceColumnForProxyColumn(int proxyColumn) const;
private:
std::unique_ptr<KRearrangeColumnsProxyModelPrivate> const d_ptr;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,325 @@
/*
SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSELECTIONPROXYMODEL_H
#define KSELECTIONPROXYMODEL_H
#include <QAbstractProxyModel>
#include <QItemSelectionModel>
#include "kitemmodels_export.h"
#include <memory>
class KSelectionProxyModelPrivate;
/**
@class KSelectionProxyModel kselectionproxymodel.h KSelectionProxyModel
@brief A Proxy Model which presents a subset of its source model to observers.
The KSelectionProxyModel is most useful as a convenience for displaying the selection in one view in
another view. The selectionModel of the initial view is used to create a proxied model which is filtered
based on the configuration of this class.
For example, when a user clicks a mail folder in one view in an email application, the contained emails
should be displayed in another view.
This takes away the need for the developer to handle the selection between the views, including all the
mapToSource, mapFromSource and setRootIndex calls.
@code
MyModel *sourceModel = new MyModel(this);
QTreeView *leftView = new QTreeView(this);
leftView->setModel(sourceModel);
KSelectionProxyModel *selectionProxy = new KSelectionProxyModel(leftView->selectionModel(), this);
selectionProxy->setSourceModel(sourceModel);
QTreeView *rightView = new QTreeView(this);
rightView->setModel(selectionProxy);
@endcode
\image html selectionproxymodelsimpleselection.png "A Selection in one view creating a model for use with another view."
The KSelectionProxyModel can handle complex selections.
\image html selectionproxymodelmultipleselection.png "Non-contiguous selection creating a new simple model in a second view."
The contents of the secondary view depends on the selection in the primary view, and the configuration of the proxy model.
See KSelectionProxyModel::setFilterBehavior for the different possible configurations.
For example, if the filterBehavior is SubTrees, selecting another item in an already selected subtree has no effect.
\image html selectionproxymodelmultipleselection-withdescendant.png "Selecting an item and its descendant."
See the test application in tests/proxymodeltestapp to try out the valid configurations.
\image html kselectionproxymodel-testapp.png "KSelectionProxyModel test application"
Obviously, the KSelectionProxyModel may be used in a view, or further processed with other proxy models.
See KAddressBook and AkonadiConsole in kdepim for examples which use a further KDescendantsProxyModel
and QSortFilterProxyModel on top of a KSelectionProxyModel.
Additionally, this class can be used to programmatically choose some items from the source model to display in the view. For example,
this is how the Favourite Folder View in KMail works, and is also used in unit testing.
See also: https://doc.qt.io/qt-5/model-view-programming.html#proxy-models
@since 4.4
@author Stephen Kelly <steveire@gmail.com>
*/
class KITEMMODELS_EXPORT KSelectionProxyModel : public QAbstractProxyModel
{
Q_OBJECT
Q_PROPERTY(FilterBehavior filterBehavior READ filterBehavior WRITE setFilterBehavior NOTIFY filterBehaviorChanged)
Q_PROPERTY(QItemSelectionModel *selectionModel READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
public:
/**
ctor.
@p selectionModel The selection model used to filter what is presented by the proxy.
*/
// KF6: Remove the selectionModel from the constructor here.
explicit KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent = nullptr);
/**
Default constructor. Allow the creation of a KSelectionProxyModel in QML
code. QML will assign a parent after construction.
*/
// KF6: Remove in favor of the constructor above.
explicit KSelectionProxyModel();
/**
dtor
*/
~KSelectionProxyModel() override;
/**
reimp.
*/
void setSourceModel(QAbstractItemModel *sourceModel) override;
QItemSelectionModel *selectionModel() const;
void setSelectionModel(QItemSelectionModel *selectionModel);
enum FilterBehavior {
SubTrees,
SubTreeRoots,
SubTreesWithoutRoots,
ExactSelection,
ChildrenOfExactSelection,
InvalidBehavior,
};
Q_ENUM(FilterBehavior)
/**
Set the filter behaviors of this model.
The filter behaviors of the model govern the content of the model based on the selection of the contained QItemSelectionModel.
See kdeui/proxymodeltestapp to try out the different proxy model behaviors.
The most useful behaviors are SubTrees, ExactSelection and ChildrenOfExactSelection.
The default behavior is SubTrees. This means that this proxy model will contain the roots of the items in the source model.
Any descendants which are also selected have no additional effect.
For example if the source model is like:
@verbatim
(root)
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
@endverbatim
And A, B, C and D are selected, the proxy will contain:
@verbatim
(root)
- A
- B
- C
- D
- E
- F
- G
@endverbatim
That is, selecting 'D' or 'C' if 'B' is also selected has no effect. If 'B' is de-selected, then 'C' amd 'D' become top-level items:
@verbatim
(root)
- A
- C
- D
- E
- F
- G
@endverbatim
This is the behavior used by KJots when rendering books.
If the behavior is set to SubTreeRoots, then the children of selected indexes are not part of the model. If 'A', 'B' and 'D' are selected,
@verbatim
(root)
- A
- B
@endverbatim
Note that although 'D' is selected, it is not part of the proxy model, because its parent 'B' is already selected.
SubTreesWithoutRoots has the effect of not making the selected items part of the model, but making their children part of the model instead. If 'A', 'B'
and 'I' are selected:
@verbatim
(root)
- C
- D
- E
- F
- G
- J
- K
- L
@endverbatim
Note that 'A' has no children, so selecting it has no outward effect on the model.
ChildrenOfExactSelection causes the proxy model to contain the children of the selected indexes,but further descendants are omitted.
Additionally, if descendants of an already selected index are selected, their children are part of the proxy model.
For example, if 'A', 'B', 'D' and 'I' are selected:
@verbatim
(root)
- C
- D
- E
- G
- J
- K
- L
@endverbatim
This would be useful for example if showing containers (for example maildirs) in one view and their items in another. Sub-maildirs would still appear in
the proxy, but could be filtered out using a QSortfilterProxyModel.
The ExactSelection behavior causes the selected items to be part of the proxy model, even if their ancestors are already selected, but children of
selected items are not included.
Again, if 'A', 'B', 'D' and 'I' are selected:
@verbatim
(root)
- A
- B
- D
- I
@endverbatim
This is the behavior used by the Favourite Folder View in KMail.
*/
void setFilterBehavior(FilterBehavior behavior);
FilterBehavior filterBehavior() const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QItemSelection mapSelectionFromSource(const QItemSelection &selection) const override;
QItemSelection mapSelectionToSource(const QItemSelection &selection) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
Qt::DropActions supportedDropActions() const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override;
int columnCount(const QModelIndex & = QModelIndex()) const override;
virtual QModelIndexList match(const QModelIndex &start,
int role,
const QVariant &value,
int hits = 1,
Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override;
Q_SIGNALS:
/**
@internal
Emitted before @p removeRootIndex, an index in the sourceModel is removed from
the root selected indexes. This may be unrelated to rows removed from the model,
depending on configuration.
*/
void rootIndexAboutToBeRemoved(const QModelIndex &removeRootIndex, QPrivateSignal);
/**
@internal
Emitted when @p newIndex, an index in the sourceModel is added to the root selected
indexes. This may be unrelated to rows inserted to the model,
depending on configuration.
*/
void rootIndexAdded(const QModelIndex &newIndex, QPrivateSignal);
/**
@internal
Emitted before @p selection, a selection in the sourceModel, is removed from
the root selection.
*/
void rootSelectionAboutToBeRemoved(const QItemSelection &selection, QPrivateSignal);
/**
@internal
Emitted after @p selection, a selection in the sourceModel, is added to
the root selection.
*/
void rootSelectionAdded(const QItemSelection &selection, QPrivateSignal);
void selectionModelChanged(QPrivateSignal);
void filterBehaviorChanged(QPrivateSignal);
protected:
QList<QPersistentModelIndex> sourceRootIndexes() const;
private:
Q_DECLARE_PRIVATE(KSelectionProxyModel)
//@cond PRIVATE
std::unique_ptr<KSelectionProxyModelPrivate> const d_ptr;
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))
Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))
Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset())
Q_PRIVATE_SLOT(d_func(), void sourceModelReset())
Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged())
Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged())
Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &))
Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected))
Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed())
//@endcond
};
#endif
@@ -0,0 +1,105 @@
/*
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
SPDX-FileContributor: 2010 Stephen Kelly <stephen@kdab.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KVOIDPOINTERFACTORY_P_H
#define KVOIDPOINTERFACTORY_P_H
#include <cstddef>
#include <cstdlib>
#include <vector>
#define DEFAULT_BLOCK_SIZE 256
/**
* @brief A Class for managing void pointers for use in QModelIndexes.
*
* This class creates void pointers pointing to individual blocks of memory.
* The pointed-to memory contains zeros.
*
* Memory is allocated in blocks of size @p blockSize times sizeof(void*) at a time. The used
* memory is automatically freed and can be cleared manually.
*
* The void pointers should not be dereferenced, but only used as a unique
* identifier suitable for use with createIndex() and for comparison with other void pointers.
*/
template<std::size_t blockSize = DEFAULT_BLOCK_SIZE>
class KVoidPointerFactory
{
// a class with size 1.
class Bit
{
bool bit;
};
public:
KVoidPointerFactory()
: m_previousPointer(nullptr)
, m_finalPointer(nullptr)
{
}
KVoidPointerFactory(const KVoidPointerFactory<blockSize> &other)
{
*this = other;
}
KVoidPointerFactory<blockSize> &operator=(const KVoidPointerFactory<blockSize> &other)
{
m_previousPointer = other.m_previousPointer;
m_finalPointer = other.m_finalPointer;
m_blocks = other.m_blocks;
return *this;
}
~KVoidPointerFactory()
{
clear();
}
void clear()
{
typename std::vector<Bit *>::const_iterator it = m_blocks.begin();
const typename std::vector<Bit *>::const_iterator end = m_blocks.end();
for (; it != end; ++it) {
free(*it);
}
m_blocks.clear();
m_finalPointer = nullptr;
m_previousPointer = nullptr;
}
void *createPointer() const
{
if (m_previousPointer == m_finalPointer) {
static const std::size_t pointer_size = sizeof(Bit *);
Bit *const bit = static_cast<Bit *>(calloc(blockSize, pointer_size));
m_blocks.push_back(bit);
m_finalPointer = bit + (blockSize * pointer_size) - 1;
m_previousPointer = bit;
return bit;
}
return ++m_previousPointer;
}
private:
mutable std::vector<Bit *> m_blocks;
mutable Bit *m_previousPointer;
mutable Bit *m_finalPointer;
};
// Disable factory with 0 blockSize
template<>
class KVoidPointerFactory<0>
{
public:
KVoidPointerFactory();
void clear();
void *createPointer();
};
#endif
@@ -0,0 +1,32 @@
ecm_add_qml_module(itemmodelsplugin
URI "org.kde.kitemmodels"
VERSION 1.0
GENERATE_PLUGIN_SOURCE
DEPENDENCIES
QtCore
)
target_sources(itemmodelsplugin PRIVATE
kdescendantsproxymodel_qml.cpp
kdescendantsproxymodel_qml.h
krolenames.cpp
krolenames.h
ksortfilterproxymodel.cpp
ksortfilterproxymodel.h
types.h
)
ecm_qt_declare_logging_category(itemmodelsplugin
HEADER kitemmodels_debug.h
IDENTIFIER KITEMMODELS_LOG
CATEGORY_NAME kf.itemmodels.quick
DESCRIPTION "KItemModels (QtQuick)"
EXPORT KITEMMODELS
)
target_link_libraries(itemmodelsplugin PRIVATE
Qt6::Qml
KF6::ItemModels
)
ecm_finalize_qml_module(itemmodelsplugin DESTINATION ${KDE_INSTALL_QMLDIR} EXPORT KF6ItemModelsTargets)
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kdescendantsproxymodel_qml.h"
#include <QDebug>
KDescendantsProxyModelQml::KDescendantsProxyModelQml(QObject *parent)
: KDescendantsProxyModel(parent)
{
}
KDescendantsProxyModelQml::~KDescendantsProxyModelQml()
{
}
void KDescendantsProxyModelQml::expandChildren(int row)
{
QModelIndex idx = mapToSource(index(row, 0));
expandSourceIndex(idx);
}
void KDescendantsProxyModelQml::collapseChildren(int row)
{
QModelIndex idx = mapToSource(index(row, 0));
collapseSourceIndex(idx);
}
void KDescendantsProxyModelQml::toggleChildren(int row)
{
QModelIndex sourceIndex = mapToSource(index(row, 0));
if (!sourceModel()->hasChildren(sourceIndex)) {
return;
}
if (isSourceIndexExpanded(sourceIndex)) {
collapseSourceIndex(sourceIndex);
} else {
expandSourceIndex(sourceIndex);
}
}
#include "moc_kdescendantsproxymodel_qml.cpp"
@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// This class exposes KDescendantsProxyModel in a more QML friendly way
#pragma once
#include <KDescendantsProxyModel>
#include <QObject>
#include <QPointer>
#include <qqmlregistration.h>
class KDescendantsProxyModelQml : public KDescendantsProxyModel
{
Q_OBJECT
QML_NAMED_ELEMENT(KDescendantsProxyModel)
public:
explicit KDescendantsProxyModelQml(QObject *parent = nullptr);
~KDescendantsProxyModelQml() override;
Q_INVOKABLE void expandChildren(int row);
Q_INVOKABLE void collapseChildren(int row);
Q_INVOKABLE void toggleChildren(int row);
};
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "krolenames.h"
#include <QAbstractItemModel>
#include <QQmlInfo>
class KRoleNamesPrivate
{
KRoleNames *const q;
public:
explicit KRoleNamesPrivate(KRoleNames *qq)
: q(qq)
{
}
QHash<int, QByteArray> roleNames() const;
QAbstractItemModel *model() const;
};
KRoleNames::KRoleNames(QObject *parent)
: QObject(parent)
, d(new KRoleNamesPrivate(this))
{
Q_ASSERT(parent);
if (!d->model()) {
qmlWarning(parent) << "KRoleNames must be attached to a QAbstractItemModel";
return;
}
}
KRoleNames::~KRoleNames() = default;
QByteArray KRoleNames::roleName(int role) const
{
const auto map = d->roleNames();
return map.value(role, QByteArray());
}
int KRoleNames::role(const QByteArray &roleName) const
{
const auto map = d->roleNames();
return map.key(roleName, -1);
}
KRoleNames *KRoleNames::qmlAttachedProperties(QObject *object)
{
return new KRoleNames(object);
}
QHash<int, QByteArray> KRoleNamesPrivate::roleNames() const
{
if (const auto m = model()) {
return m->roleNames();
}
return {};
}
QAbstractItemModel *KRoleNamesPrivate::model() const
{
return qobject_cast<QAbstractItemModel *>(q->parent());
}
#include "moc_krolenames.cpp"
@@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KROLENAMES_H
#define KROLENAMES_H
#include <QObject>
#include <qqml.h>
#include <memory>
class QAbstractItemModel;
class KRoleNamesPrivate;
/**
* @class KRoleNames
*
* @brief A mapper between roles and role names of an attachee model.
*
* KRoleNames exposes runtime-invokable methods to map from roles to role names
* and vice-versa. It can be used to retrieve data from a model in an imperative
* fashion when enum with roles is not available at runtime (i.e. not exported
* via Q_ENUM macro) but role names are known; or just to maintain consistency
* with view delegates (which use role names as properties).
*
* @since 6.0
*/
class KRoleNames : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("KRoleNames can only be used as an attached property")
QML_ATTACHED(KRoleNames)
QML_ADDED_IN_MINOR_VERSION(1)
public:
explicit KRoleNames(QObject *parent = nullptr);
~KRoleNames() override;
/**
* Maps role number to role name.
*
* Returns an empty string if role is not found in attachee model's
* roleNames() hash map.
*
* @since 6.0
*/
Q_INVOKABLE QByteArray roleName(int role) const;
/**
* Maps role name to role number.
*
* Returns -1 if role name is not found in attachee model's
* roleNames() hash map.
*
* @since 6.0
*/
Q_INVOKABLE int role(const QByteArray &roleName) const;
static KRoleNames *qmlAttachedProperties(QObject *object);
private:
std::unique_ptr<KRoleNamesPrivate> const d;
};
QML_DECLARE_TYPEINFO(KRoleNames, QML_HAS_ATTACHED_PROPERTIES)
#endif
@@ -0,0 +1,314 @@
/*
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ksortfilterproxymodel.h"
#include <QQmlContext>
#include <QQmlEngine>
#include "kitemmodels_debug.h"
KSortFilterProxyModel::KSortFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
, m_componentCompleted(false)
, m_sortRoleSourceOfTruth(SourceOfTruthIsRoleID)
, m_filterRoleSourceOfTruth(SourceOfTruthIsRoleID)
, m_sortRoleGuard(false)
, m_filterRoleGuard(false)
{
setDynamicSortFilter(true);
connect(this, &KSortFilterProxyModel::modelReset, this, &KSortFilterProxyModel::rowCountChanged);
connect(this, &KSortFilterProxyModel::rowsInserted, this, &KSortFilterProxyModel::rowCountChanged);
connect(this, &KSortFilterProxyModel::rowsRemoved, this, &KSortFilterProxyModel::rowCountChanged);
connect(this, &KSortFilterProxyModel::sortRoleChanged, this, &KSortFilterProxyModel::syncSortRoleProperties);
connect(this, &KSortFilterProxyModel::filterRoleChanged, this, &KSortFilterProxyModel::syncFilterRoleProperties);
}
KSortFilterProxyModel::~KSortFilterProxyModel()
{
}
static void reverseStringIntHash(QHash<QString, int> &dst, const QHash<int, QByteArray> &src)
{
dst.clear();
dst.reserve(src.count());
for (auto i = src.constBegin(); i != src.constEnd(); ++i) {
dst[QString::fromUtf8(i.value())] = i.key();
}
}
void KSortFilterProxyModel::syncRoleNames()
{
if (!sourceModel()) {
return;
}
reverseStringIntHash(m_roleIds, roleNames());
m_sortRoleGuard = true;
syncSortRoleProperties();
m_sortRoleGuard = false;
m_filterRoleGuard = true;
syncFilterRoleProperties();
m_filterRoleGuard = false;
}
int KSortFilterProxyModel::roleNameToId(const QString &name) const
{
return m_roleIds.value(name, Qt::DisplayRole);
}
void KSortFilterProxyModel::setSourceModel(QAbstractItemModel *model)
{
const auto oldModel = sourceModel();
if (model == oldModel) {
return;
}
if (oldModel) {
for (const auto &connection : std::as_const(m_sourceModelConnections)) {
disconnect(connection);
}
}
QSortFilterProxyModel::setSourceModel(model);
// NOTE: some models actually fill their roleNames() only when they get some actual data, this works around the bad behavior
if (model) {
m_sourceModelConnections = {{
connect(model, &QAbstractItemModel::modelReset, this, &KSortFilterProxyModel::syncRoleNames),
connect(model, &QAbstractItemModel::rowsInserted, this, &KSortFilterProxyModel::syncRoleNames),
connect(model, &QAbstractItemModel::rowsRemoved, this, &KSortFilterProxyModel::syncRoleNames),
}};
}
if (m_componentCompleted) {
syncRoleNames();
}
}
bool KSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (m_filterRowCallback.isCallable()) {
QJSEngine *engine = qjsEngine(this);
QJSValueList args = {QJSValue(source_row), engine->toScriptValue(source_parent)};
QJSValue result = const_cast<KSortFilterProxyModel *>(this)->m_filterRowCallback.call(args);
if (result.isError()) {
qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:";
qCWarning(KITEMMODELS_LOG) << result.toString();
return true;
} else {
return result.toBool();
}
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
bool KSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
if (m_filterColumnCallback.isCallable()) {
QJSEngine *engine = qjsEngine(this);
QJSValueList args = {QJSValue(source_column), engine->toScriptValue(source_parent)};
QJSValue result = const_cast<KSortFilterProxyModel *>(this)->m_filterColumnCallback.call(args);
if (result.isError()) {
qCWarning(KITEMMODELS_LOG) << "Row filter callback produced an error:";
qCWarning(KITEMMODELS_LOG) << result.toString();
return true;
} else {
return result.toBool();
}
}
return QSortFilterProxyModel::filterAcceptsColumn(source_column, source_parent);
}
void KSortFilterProxyModel::setFilterString(const QString &filterString)
{
if (filterString == m_filterString) {
return;
}
m_filterString = filterString;
QSortFilterProxyModel::setFilterFixedString(filterString);
Q_EMIT filterStringChanged();
}
QString KSortFilterProxyModel::filterString() const
{
return m_filterString;
}
QJSValue KSortFilterProxyModel::filterRowCallback() const
{
return m_filterRowCallback;
}
void KSortFilterProxyModel::setFilterRowCallback(const QJSValue &callback)
{
if (m_filterRowCallback.strictlyEquals(callback)) {
return;
}
if (!callback.isNull() && !callback.isCallable()) {
return;
}
m_filterRowCallback = callback;
invalidateFilter();
Q_EMIT filterRowCallbackChanged(callback);
}
void KSortFilterProxyModel::setFilterColumnCallback(const QJSValue &callback)
{
if (m_filterColumnCallback.strictlyEquals(callback)) {
return;
}
if (!callback.isNull() && !callback.isCallable()) {
return;
}
m_filterColumnCallback = callback;
invalidateFilter();
Q_EMIT filterColumnCallbackChanged(callback);
}
QJSValue KSortFilterProxyModel::filterColumnCallback() const
{
return m_filterColumnCallback;
}
void KSortFilterProxyModel::syncSortRoleProperties()
{
if (!sourceModel()) {
return;
}
if (!m_sortRoleGuard) {
m_sortRoleSourceOfTruth = SourceOfTruthIsRoleID;
}
if (m_sortRoleSourceOfTruth == SourceOfTruthIsRoleName) {
if (m_sortRoleName.isEmpty()) {
QSortFilterProxyModel::setSortRole(Qt::DisplayRole);
sort(-1, Qt::AscendingOrder);
} else {
const auto role = roleNameToId(m_sortRoleName);
QSortFilterProxyModel::setSortRole(role);
sort(std::max(sortColumn(), 0), sortOrder());
}
} else {
const QString roleName = QString::fromUtf8(roleNames().value(sortRole()));
if (m_sortRoleName != roleName) {
m_sortRoleName = roleName;
Q_EMIT sortRoleNameChanged();
}
}
}
void KSortFilterProxyModel::syncFilterRoleProperties()
{
if (!sourceModel()) {
return;
}
if (!m_filterRoleGuard) {
m_filterRoleSourceOfTruth = SourceOfTruthIsRoleID;
}
if (m_filterRoleSourceOfTruth == SourceOfTruthIsRoleName) {
const auto role = roleNameToId(m_filterRoleName);
QSortFilterProxyModel::setFilterRole(role);
} else {
const QString roleName = QString::fromUtf8(roleNames().value(filterRole()));
if (m_filterRoleName != roleName) {
m_filterRoleName = roleName;
Q_EMIT filterRoleNameChanged();
}
}
}
void KSortFilterProxyModel::setFilterRoleName(const QString &roleName)
{
if (m_filterRoleSourceOfTruth == SourceOfTruthIsRoleName && m_filterRoleName == roleName) {
return;
}
m_filterRoleSourceOfTruth = SourceOfTruthIsRoleName;
m_filterRoleName = roleName;
m_filterRoleGuard = true;
syncFilterRoleProperties();
m_filterRoleGuard = false;
Q_EMIT filterRoleNameChanged();
}
QString KSortFilterProxyModel::filterRoleName() const
{
return m_filterRoleName;
}
void KSortFilterProxyModel::setSortRoleName(const QString &roleName)
{
if (m_sortRoleSourceOfTruth == SourceOfTruthIsRoleName && m_sortRoleName == roleName) {
return;
}
m_sortRoleSourceOfTruth = SourceOfTruthIsRoleName;
m_sortRoleName = roleName;
m_sortRoleGuard = true;
syncSortRoleProperties();
m_sortRoleGuard = false;
Q_EMIT sortRoleNameChanged();
}
QString KSortFilterProxyModel::sortRoleName() const
{
return m_sortRoleName;
}
void KSortFilterProxyModel::setSortOrder(const Qt::SortOrder order)
{
sort(std::max(sortColumn(), 0), order);
Q_EMIT sortOrderChanged();
}
void KSortFilterProxyModel::setSortColumn(int column)
{
if (column == sortColumn()) {
return;
}
sort(column, sortOrder());
Q_EMIT sortColumnChanged();
}
void KSortFilterProxyModel::classBegin()
{
}
void KSortFilterProxyModel::componentComplete()
{
m_componentCompleted = true;
syncRoleNames();
}
void KSortFilterProxyModel::invalidateFilter()
{
QSortFilterProxyModel::invalidateFilter();
}
#include "moc_ksortfilterproxymodel.cpp"
@@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSORTFILTERPROXYMODEL_H
#define KSORTFILTERPROXYMODEL_H
#include <QAbstractItemModel>
#include <QJSValue>
#include <QList>
#include <QQmlParserStatus>
#include <QSortFilterProxyModel>
#include <qqmlregistration.h>
#include <array>
/**
* @class KSortFilterProxyModel
* @short Filter and sort an existing QAbstractItemModel
*
* @since 5.67
*/
class KSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus
{
Q_OBJECT
QML_ELEMENT
Q_INTERFACES(QQmlParserStatus)
/**
* The string for the filter, only rows with their filterRole matching filterString will be displayed
*/
Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)
/**
* A JavaScript callable that can be used to perform advanced filters on a given row.
* The callback is passed the source row, and source parent for a given row as arguments
*
* The callable's return value is evaluated as boolean to determine
* whether the row is accepted (true) or filtered out (false). It overrides the default implementation
* that uses filterRegExp or filterString; while filterCallback is set those two properties are
* ignored. Attempts to write a non-callable to this property are silently ignored, but you can set
* it to null.
*
* @code
* filterRowCallback: function(source_row, source_parent) {
* return sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.DisplayRole) == "...";
* };
* @endcode
*/
Q_PROPERTY(QJSValue filterRowCallback READ filterRowCallback WRITE setFilterRowCallback NOTIFY filterRowCallbackChanged)
/**
* A JavaScript callable that can be used to perform advanced filters on a given column.
* The callback is passed the source column, and source parent for a given column as arguments.
*
* @see filterRowCallback
*/
Q_PROPERTY(QJSValue filterColumnCallback READ filterColumnCallback WRITE setFilterColumnCallback NOTIFY filterColumnCallbackChanged)
/**
* The role of the sourceModel on which the filter will be applied.
* This can either be the numerical role value or the role name as a string.
*/
Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
/**
* The role of the sourceModel that will be used for sorting. if empty the order will be left unaltered
* This can either be the numerical role value or the role name as a string.
*/
Q_PROPERTY(QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged)
/**
* One of Qt.AscendingOrder or Qt.DescendingOrder
*/
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
/**
* Specify which column should be used for sorting
* The default value is -1.
* If \a sortRole is set, the default value is 0.
*/
Q_PROPERTY(int sortColumn READ sortColumn WRITE setSortColumn NOTIFY sortColumnChanged)
/**
* The number of top level rows.
*/
Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
public:
explicit KSortFilterProxyModel(QObject *parent = nullptr);
~KSortFilterProxyModel() override;
void setSourceModel(QAbstractItemModel *sourceModel) override;
void setFilterRowCallback(const QJSValue &callback);
QJSValue filterRowCallback() const;
void setFilterString(const QString &filterString);
QString filterString() const;
void setFilterColumnCallback(const QJSValue &callback);
QJSValue filterColumnCallback() const;
void setFilterRoleName(const QString &roleName);
QString filterRoleName() const;
void setSortRoleName(const QString &roleName);
QString sortRoleName() const;
void setSortOrder(const Qt::SortOrder order);
void setSortColumn(int column);
void classBegin() override;
void componentComplete() override;
public Q_SLOTS:
/**
* Invalidates the current filtering.
*
* This function should be called if you are implementing custom filtering through
* filterRowCallback or filterColumnCallback, and your filter parameters have changed.
*
* @since 5.70
*/
void invalidateFilter();
Q_SIGNALS:
void filterStringChanged();
void filterRoleNameChanged();
void sortRoleNameChanged();
void sortOrderChanged();
void sortColumnChanged();
void filterRowCallbackChanged(const QJSValue &);
void filterColumnCallbackChanged(const QJSValue &);
void rowCountChanged();
protected:
int roleNameToId(const QString &name) const;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override;
protected Q_SLOTS:
// This method is called whenever we suspect that role names mapping might have gone stale.
// It must not alter the source of truth for sort/filter properties.
void syncRoleNames();
// These methods are dealing with individual pairs of properties. They are
// called on various occasions, and need to check whether the invocation
// has been caused by a standalone base type's property change
// (switching source of truth to role ID) or as a side-effect of a sync.
void syncSortRoleProperties();
void syncFilterRoleProperties();
private:
// conveniently, role ID is the default source of truth, turning it into
// zero-initialization.
enum SourceOfTruthForRoleProperty : bool {
SourceOfTruthIsRoleID = false,
SourceOfTruthIsRoleName = true,
};
bool m_componentCompleted : 1;
SourceOfTruthForRoleProperty m_sortRoleSourceOfTruth : 1;
SourceOfTruthForRoleProperty m_filterRoleSourceOfTruth : 1;
bool m_sortRoleGuard : 1;
bool m_filterRoleGuard : 1;
// default role name corresponds to the standard mapping of the default Qt::DisplayRole in QAbstractItemModel::roleNames
QString m_sortRoleName{QStringLiteral("display")};
QString m_filterRoleName{QStringLiteral("display")};
QString m_filterString;
QJSValue m_filterRowCallback;
QJSValue m_filterColumnCallback;
QHash<QString, int> m_roleIds;
std::array<QMetaObject::Connection, 3> m_sourceModelConnections;
};
#endif
@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <QQmlEngine>
#include <KColumnHeadersModel>
#include <KNumberModel>
struct KColumnHeadersModelForeign {
Q_GADGET
QML_NAMED_ELEMENT(KColumnHeadersModel)
QML_FOREIGN(KColumnHeadersModel)
};
struct KNumberModelForeign {
Q_GADGET
QML_NAMED_ELEMENT(KNumberModel)
QML_FOREIGN(KNumberModel)
};