1 | /* |
2 | SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> |
3 | SPDX-FileContributor: Stephen Kelly <stephen@kdab.com> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kbreadcrumbselectionmodel.h" |
9 | |
10 | class KBreadcrumbSelectionModelPrivate |
11 | { |
12 | Q_DECLARE_PUBLIC(KBreadcrumbSelectionModel) |
13 | KBreadcrumbSelectionModel *const q_ptr; |
14 | |
15 | public: |
16 | KBreadcrumbSelectionModelPrivate(KBreadcrumbSelectionModel *breadcrumbSelector, |
17 | QItemSelectionModel *selectionModel, |
18 | KBreadcrumbSelectionModel::BreadcrumbTarget direction); |
19 | |
20 | /** |
21 | Returns a selection containing the breadcrumbs for @p index |
22 | */ |
23 | QItemSelection getBreadcrumbSelection(const QModelIndex &index); |
24 | |
25 | /** |
26 | Returns a selection containing the breadcrumbs for @p selection |
27 | */ |
28 | QItemSelection getBreadcrumbSelection(const QItemSelection &selection); |
29 | |
30 | void sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); |
31 | |
32 | void syncBreadcrumbs(); |
33 | |
34 | bool m_includeActualSelection = true; |
35 | bool m_showHiddenAscendantData = false; |
36 | bool m_ignoreCurrentChanged = false; |
37 | int m_selectionDepth = -1; |
38 | KBreadcrumbSelectionModel::BreadcrumbTarget m_direction = KBreadcrumbSelectionModel::MakeBreadcrumbSelectionInSelf; |
39 | QItemSelectionModel *m_selectionModel = nullptr; |
40 | }; |
41 | |
42 | KBreadcrumbSelectionModelPrivate::KBreadcrumbSelectionModelPrivate(KBreadcrumbSelectionModel *breadcrumbSelector, |
43 | QItemSelectionModel *selectionModel, |
44 | KBreadcrumbSelectionModel::BreadcrumbTarget direction) |
45 | : q_ptr(breadcrumbSelector) |
46 | , m_direction(direction) |
47 | , m_selectionModel(selectionModel) |
48 | { |
49 | Q_Q(KBreadcrumbSelectionModel); |
50 | |
51 | if (direction != KBreadcrumbSelectionModel::MakeBreadcrumbSelectionInSelf) { |
52 | q->connect(sender: selectionModel, signal: &QItemSelectionModel::selectionChanged, context: q, slot: [this](const QItemSelection &selected, const QItemSelection &deselected) { |
53 | sourceSelectionChanged(selected, deselected); |
54 | }); |
55 | } |
56 | |
57 | q->connect(sender: m_selectionModel->model(), signal: &QAbstractItemModel::layoutChanged, context: q, slot: [this]() { |
58 | syncBreadcrumbs(); |
59 | }); |
60 | q->connect(sender: m_selectionModel->model(), signal: &QAbstractItemModel::modelReset, context: q, slot: [this]() { |
61 | syncBreadcrumbs(); |
62 | }); |
63 | q->connect(sender: m_selectionModel->model(), signal: &QAbstractItemModel::rowsMoved, context: q, slot: [this]() { |
64 | syncBreadcrumbs(); |
65 | }); |
66 | |
67 | // Don't need to handle insert & remove because they can't change the breadcrumbs on their own. |
68 | } |
69 | |
70 | KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, QObject *parent) |
71 | : QItemSelectionModel(const_cast<QAbstractItemModel *>(selectionModel->model()), parent) |
72 | , d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, MakeBreadcrumbSelectionInSelf)) |
73 | { |
74 | } |
75 | |
76 | KBreadcrumbSelectionModel::KBreadcrumbSelectionModel(QItemSelectionModel *selectionModel, BreadcrumbTarget direction, QObject *parent) |
77 | : QItemSelectionModel(const_cast<QAbstractItemModel *>(selectionModel->model()), parent) |
78 | , d_ptr(new KBreadcrumbSelectionModelPrivate(this, selectionModel, direction)) |
79 | { |
80 | } |
81 | |
82 | KBreadcrumbSelectionModel::~KBreadcrumbSelectionModel() = default; |
83 | |
84 | bool KBreadcrumbSelectionModel::isActualSelectionIncluded() const |
85 | { |
86 | Q_D(const KBreadcrumbSelectionModel); |
87 | return d->m_includeActualSelection; |
88 | } |
89 | |
90 | void KBreadcrumbSelectionModel::setActualSelectionIncluded(bool includeActualSelection) |
91 | { |
92 | Q_D(KBreadcrumbSelectionModel); |
93 | d->m_includeActualSelection = includeActualSelection; |
94 | } |
95 | |
96 | int KBreadcrumbSelectionModel::breadcrumbLength() const |
97 | { |
98 | Q_D(const KBreadcrumbSelectionModel); |
99 | return d->m_selectionDepth; |
100 | } |
101 | |
102 | void KBreadcrumbSelectionModel::setBreadcrumbLength(int breadcrumbLength) |
103 | { |
104 | Q_D(KBreadcrumbSelectionModel); |
105 | d->m_selectionDepth = breadcrumbLength; |
106 | } |
107 | |
108 | QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QModelIndex &index) |
109 | { |
110 | QItemSelection breadcrumbSelection; |
111 | |
112 | if (m_includeActualSelection) { |
113 | breadcrumbSelection.append(t: QItemSelectionRange(index)); |
114 | } |
115 | |
116 | QModelIndex parent = index.parent(); |
117 | int sumBreadcrumbs = 0; |
118 | bool includeAll = m_selectionDepth < 0; |
119 | while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) { |
120 | breadcrumbSelection.append(t: QItemSelectionRange(parent)); |
121 | parent = parent.parent(); |
122 | } |
123 | return breadcrumbSelection; |
124 | } |
125 | |
126 | QItemSelection KBreadcrumbSelectionModelPrivate::getBreadcrumbSelection(const QItemSelection &selection) |
127 | { |
128 | QItemSelection breadcrumbSelection; |
129 | |
130 | if (m_includeActualSelection) { |
131 | breadcrumbSelection = selection; |
132 | } |
133 | |
134 | QItemSelection::const_iterator it = selection.constBegin(); |
135 | const QItemSelection::const_iterator end = selection.constEnd(); |
136 | |
137 | for (; it != end; ++it) { |
138 | QModelIndex parent = it->parent(); |
139 | |
140 | if (breadcrumbSelection.contains(index: parent)) { |
141 | continue; |
142 | } |
143 | |
144 | int sumBreadcrumbs = 0; |
145 | bool includeAll = m_selectionDepth < 0; |
146 | |
147 | while (parent.isValid() && (includeAll || sumBreadcrumbs < m_selectionDepth)) { |
148 | breadcrumbSelection.append(t: QItemSelectionRange(parent)); |
149 | parent = parent.parent(); |
150 | |
151 | if (breadcrumbSelection.contains(index: parent)) { |
152 | break; |
153 | } |
154 | |
155 | ++sumBreadcrumbs; |
156 | } |
157 | } |
158 | return breadcrumbSelection; |
159 | } |
160 | |
161 | void KBreadcrumbSelectionModelPrivate::sourceSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) |
162 | { |
163 | Q_Q(KBreadcrumbSelectionModel); |
164 | const QItemSelection deselectedCrumbs = getBreadcrumbSelection(selection: deselected); |
165 | const QItemSelection selectedCrumbs = getBreadcrumbSelection(selection: selected); |
166 | |
167 | QItemSelection removed = deselectedCrumbs; |
168 | for (const QItemSelectionRange &range : selectedCrumbs) { |
169 | removed.removeAll(t: range); |
170 | } |
171 | |
172 | QItemSelection added = selectedCrumbs; |
173 | for (const QItemSelectionRange &range : deselectedCrumbs) { |
174 | added.removeAll(t: range); |
175 | } |
176 | |
177 | if (!removed.isEmpty()) { |
178 | q->QItemSelectionModel::select(selection: removed, command: QItemSelectionModel::Deselect); |
179 | } |
180 | if (!added.isEmpty()) { |
181 | q->QItemSelectionModel::select(selection: added, command: QItemSelectionModel::Select); |
182 | } |
183 | } |
184 | |
185 | void KBreadcrumbSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) |
186 | { |
187 | Q_D(KBreadcrumbSelectionModel); |
188 | // When an item is removed, the current index is set to the top index in the model. |
189 | // That causes a selectionChanged signal with a selection which we do not want. |
190 | if (d->m_ignoreCurrentChanged) { |
191 | d->m_ignoreCurrentChanged = false; |
192 | return; |
193 | } |
194 | if (d->m_direction == MakeBreadcrumbSelectionInOther) { |
195 | d->m_selectionModel->select(selection: d->getBreadcrumbSelection(index), command); |
196 | QItemSelectionModel::select(index, command); |
197 | } else { |
198 | d->m_selectionModel->select(index, command); |
199 | QItemSelectionModel::select(selection: d->getBreadcrumbSelection(index), command); |
200 | } |
201 | } |
202 | |
203 | void KBreadcrumbSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) |
204 | { |
205 | Q_D(KBreadcrumbSelectionModel); |
206 | QItemSelection bcc = d->getBreadcrumbSelection(selection); |
207 | if (d->m_direction == MakeBreadcrumbSelectionInOther) { |
208 | d->m_selectionModel->select(selection, command); |
209 | QItemSelectionModel::select(selection: bcc, command); |
210 | } else { |
211 | d->m_selectionModel->select(selection: bcc, command); |
212 | QItemSelectionModel::select(selection, command); |
213 | } |
214 | } |
215 | |
216 | void KBreadcrumbSelectionModelPrivate::syncBreadcrumbs() |
217 | { |
218 | Q_Q(KBreadcrumbSelectionModel); |
219 | q->select(selection: m_selectionModel->selection(), command: QItemSelectionModel::ClearAndSelect); |
220 | } |
221 | |
222 | #include "moc_kbreadcrumbselectionmodel.cpp" |
223 | |