Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,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 ¤t);
|
||||
void slotCurrentChanged(const QModelIndex ¤t);
|
||||
|
||||
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 ¤t) {
|
||||
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 ¤t)
|
||||
{
|
||||
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 ¤t)
|
||||
{
|
||||
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 ¤t))
|
||||
Q_PRIVATE_SLOT(d_func(), void slotCurrentChanged(const QModelIndex ¤t))
|
||||
};
|
||||
|
||||
#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)
|
||||
+46
@@ -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"
|
||||
+29
@@ -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)
|
||||
};
|
||||
Reference in New Issue
Block a user