Akonadi

entitytreeview.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
4 SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "entitytreeview.h"
10
11#include "dragdropmanager_p.h"
12
13#include <QApplication>
14#include <QDragMoveEvent>
15#include <QHeaderView>
16#include <QMenu>
17#include <QTimer>
18
19#include "collection.h"
20#include "controlgui.h"
21#include "entitytreemodel.h"
22#include "item.h"
23
24#include <KXMLGUIClient>
25#include <KXMLGUIFactory>
26
27#include "progressspinnerdelegate_p.h"
28
29using namespace Akonadi;
30
31/**
32 * @internal
33 */
34class Akonadi::EntityTreeViewPrivate
35{
36public:
37 explicit EntityTreeViewPrivate(EntityTreeView *parent)
38 : mParent(parent)
39#ifndef QT_NO_DRAGANDDROP
40 , mDragDropManager(new DragDropManager(mParent))
41#endif
42 , mDefaultPopupMenu(QStringLiteral("akonadi_collectionview_contextmenu"))
43 {
44 }
45
46 void init();
47 void itemClicked(const QModelIndex &index) const;
48 void itemDoubleClicked(const QModelIndex &index) const;
49 void itemCurrentChanged(const QModelIndex &index) const;
50
51 void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) const;
52
53 EntityTreeView *const mParent;
54 QBasicTimer mDragExpandTimer;
55 DragDropManager *mDragDropManager = nullptr;
56 KXMLGUIClient *mXmlGuiClient = nullptr;
57 QString mDefaultPopupMenu;
58};
59
60void EntityTreeViewPrivate::init()
61{
62 auto animator = new Akonadi::DelegateAnimator(mParent);
63 auto customDelegate = new Akonadi::ProgressSpinnerDelegate(animator, mParent);
64 mParent->setItemDelegate(customDelegate);
65
66 mParent->header()->setSectionsClickable(true);
67 mParent->header()->setStretchLastSection(false);
68 // mParent->setRootIsDecorated( false );
69
70 // QTreeView::autoExpandDelay has very strange behaviour. It toggles the collapse/expand state
71 // of the item the cursor is currently over when a timer event fires.
72 // The behaviour we want is to expand a collapsed row on drag-over, but not collapse it.
73 // mDragExpandTimer is used to achieve this.
74 // mParent->setAutoExpandDelay ( QApplication::startDragTime() );
75
76 mParent->setSortingEnabled(true);
79 mParent->setAcceptDrops(true);
80#ifndef QT_NO_DRAGANDDROP
81 mParent->setDropIndicatorShown(true);
83 mParent->setDragEnabled(true);
84#endif
85
86 mParent->connect(mParent, &QAbstractItemView::clicked, mParent, [this](const auto &index) {
87 itemClicked(index);
88 });
89 mParent->connect(mParent, &QAbstractItemView::doubleClicked, mParent, [this](const auto &index) {
90 itemDoubleClicked(index);
91 });
92
94}
95
96void EntityTreeViewPrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) const
97{
98 Q_UNUSED(deselected)
99 const int column = 0;
100 for (const QItemSelectionRange &range : selected) {
101 const QModelIndex index = range.topLeft();
102
103 if (index.column() > 0) {
104 continue;
105 }
106
107 for (int row = index.row(); row <= range.bottomRight().row(); ++row) {
108 // Don't use canFetchMore here. We need to bypass the check in
109 // the EntityFilterModel when it shows only collections.
110 mParent->model()->fetchMore(index.sibling(row, column));
111 }
112 }
113
114 if (selected.size() == 1) {
115 const QItemSelectionRange &range = selected.first();
116 if (range.topLeft().row() == range.bottomRight().row()) {
117 mParent->scrollTo(range.topLeft(), QTreeView::EnsureVisible);
118 }
119 }
120}
121
122void EntityTreeViewPrivate::itemClicked(const QModelIndex &index) const
123{
124 if (!index.isValid()) {
125 return;
126 }
127 QModelIndex idx = index.sibling(index.row(), 0);
128
129 const auto collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
130 if (collection.isValid()) {
131 Q_EMIT mParent->clicked(collection);
132 } else {
133 const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>();
134 if (item.isValid()) {
135 Q_EMIT mParent->clicked(item);
136 }
137 }
138}
139
140void EntityTreeViewPrivate::itemDoubleClicked(const QModelIndex &index) const
141{
142 if (!index.isValid()) {
143 return;
144 }
145 QModelIndex idx = index.sibling(index.row(), 0);
146 const auto collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
147 if (collection.isValid()) {
148 Q_EMIT mParent->doubleClicked(collection);
149 } else {
150 const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>();
151 if (item.isValid()) {
152 Q_EMIT mParent->doubleClicked(item);
153 }
154 }
155}
156
157void EntityTreeViewPrivate::itemCurrentChanged(const QModelIndex &index) const
158{
159 if (!index.isValid()) {
160 return;
161 }
162 QModelIndex idx = index.sibling(index.row(), 0);
163 const auto collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
164 if (collection.isValid()) {
165 Q_EMIT mParent->currentChanged(collection);
166 } else {
167 const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>();
168 if (item.isValid()) {
169 Q_EMIT mParent->currentChanged(item);
170 }
171 }
172}
173
175 : QTreeView(parent)
176 , d(new EntityTreeViewPrivate(this))
177{
179 d->init();
180}
181
183 : QTreeView(parent)
184 , d(new EntityTreeViewPrivate(this))
185{
186 d->mXmlGuiClient = xmlGuiClient;
187 d->init();
188}
189
191{
192 delete d->mDragDropManager;
193}
194
196{
197 if (selectionModel()) {
200 }
201
204
205 connect(selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const auto &index) {
206 d->itemCurrentChanged(index);
207 });
208 connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const auto &oldSel, const auto &newSel) {
209 d->slotSelectionChanged(oldSel, newSel);
210 });
211}
212
213void EntityTreeView::timerEvent(QTimerEvent *event)
214{
215 if (event->timerId() == d->mDragExpandTimer.timerId()) {
217 if (state() == QAbstractItemView::DraggingState && viewport()->rect().contains(pos)) {
218 setExpanded(indexAt(pos), true);
219 }
220 }
221
223}
224
225#ifndef QT_NO_DRAGANDDROP
226void EntityTreeView::dragMoveEvent(QDragMoveEvent *event)
227{
228 d->mDragExpandTimer.start(QApplication::startDragTime(), this);
229
230 if (d->mDragDropManager->dropAllowed(event)) {
231 // All urls are supported. process the event.
233 return;
234 }
235
236 event->setDropAction(Qt::IgnoreAction);
237}
238
239void EntityTreeView::dropEvent(QDropEvent *event)
240{
241 d->mDragExpandTimer.stop();
242 bool menuCanceled = false;
243 if (d->mDragDropManager->processDropEvent(event, menuCanceled, (dropIndicatorPosition() == QAbstractItemView::OnItem))) {
245 }
246}
247#endif
248
249#ifndef QT_NO_CONTEXTMENU
250void EntityTreeView::contextMenuEvent(QContextMenuEvent *event)
251{
252 if (!d->mXmlGuiClient || !model()) {
253 return;
254 }
255
256 const QModelIndex index = indexAt(event->pos());
257 QString popupName = d->mDefaultPopupMenu;
258
259 if (index.isValid()) { // popup not over empty space
260 // check whether the index under the cursor is a collection or item
261 const Item item = model()->data(index, EntityTreeModel::ItemRole).value<Item>();
262 popupName = (item.isValid() ? QStringLiteral("akonadi_itemview_contextmenu") : QStringLiteral("akonadi_collectionview_contextmenu"));
263 }
264
265 auto popup = static_cast<QMenu *>(d->mXmlGuiClient->factory()->container(popupName, d->mXmlGuiClient));
266 if (popup) {
267 popup->exec(event->globalPos());
268 }
269}
270#endif
271
273{
274 d->mXmlGuiClient = xmlGuiClient;
275}
276
278{
279 return d->mXmlGuiClient;
280}
281
282#ifndef QT_NO_DRAGANDDROP
283void EntityTreeView::startDrag(Qt::DropActions supportedActions)
284{
285 d->mDragDropManager->startDrag(supportedActions);
286}
287#endif
288
290{
291#ifndef QT_NO_DRAGANDDROP
292 d->mDragDropManager->setShowDropActionMenu(enabled);
293#endif
294}
295
297{
298#ifndef QT_NO_DRAGANDDROP
299 return d->mDragDropManager->showDropActionMenu();
300#else
301 return false;
302#endif
303}
304
306{
307#ifndef QT_NO_DRAGANDDROP
308 d->mDragDropManager->setManualSortingActive(active);
309#endif
310}
311
313{
314#ifndef QT_NO_DRAGANDDROP
315 return d->mDragDropManager->isManualSortingActive();
316#else
317 return false;
318#endif
319}
320
322{
323 d->mDefaultPopupMenu = name;
324}
325
326#include "moc_entitytreeview.cpp"
Represents a collection of PIM items.
Definition collection.h:62
static void widgetNeedsAkonadi(QWidget *widget)
Disable the given widget when Akonadi is not operational and show an error overlay (given enough spac...
@ CollectionRole
The collection.
A view to show an item/collection tree provided by an EntityTreeModel.
void doubleClicked(const Akonadi::Collection &collection)
This signal is emitted whenever the user has double clicked a collection in the view.
bool isManualSortingActive() const
Return true if we use an manual sorting Necessary to fix dnd menu We must show just move when we move...
KXMLGUIClient * xmlGuiClient() const
Return the XML GUI client which the view is used in.
EntityTreeView(QWidget *parent=nullptr)
Creates a new entity tree view.
void setModel(QAbstractItemModel *model) override
void setDropActionMenuEnabled(bool enabled)
Sets whether the drop action menu is enabled and will be shown on drop operation.
void clicked(const Akonadi::Collection &collection)
This signal is emitted whenever the user has clicked a collection in the view.
void setDefaultPopupMenu(const QString &name)
Set the name of the default popup menu (retrieved from the application's XMLGUI file).
void currentChanged(const Akonadi::Collection &collection)
This signal is emitted whenever the current collection in the view has changed.
~EntityTreeView() override
Destroys the entity tree view.
void setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
Sets the XML GUI client which the view is used in.
bool isDropActionMenuEnabled() const
Returns whether the drop action menu is enabled and will be shown on drop operation.
void setManualSortingActive(bool active)
Set true if we automatic sorting.
Helper integration between Akonadi and Qt.
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual void fetchMore(const QModelIndex &parent)
void clicked(const QModelIndex &index)
void doubleClicked(const QModelIndex &index)
void setDragDropMode(DragDropMode behavior)
void setDragEnabled(bool enable)
virtual void dropEvent(QDropEvent *event) override
DropIndicatorPosition dropIndicatorPosition() const const
void setEditTriggers(EditTriggers triggers)
virtual bool event(QEvent *event) override
QAbstractItemModel * model() const const
void setSelectionMode(QAbstractItemView::SelectionMode mode)
QItemSelectionModel * selectionModel() const const
void setItemDelegate(QAbstractItemDelegate *delegate)
void setDropIndicatorShown(bool enable)
State state() const const
QWidget * viewport() const const
QPoint pos()
void setSectionsClickable(bool clickable)
void setStretchLastSection(bool stretch)
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
int column() const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
IgnoreAction
AscendingOrder
virtual void dragMoveEvent(QDragMoveEvent *event) override
QHeaderView * header() const const
virtual QModelIndex indexAt(const QPoint &point) const const override
virtual void scrollTo(const QModelIndex &index, ScrollHint hint) override
void setExpanded(const QModelIndex &index, bool expanded)
virtual void setModel(QAbstractItemModel *model) override
void sortByColumn(int column, Qt::SortOrder order)
void setSortingEnabled(bool enable)
virtual void timerEvent(QTimerEvent *event) override
T value() const const
void setAcceptDrops(bool on)
QPoint mapFromGlobal(const QPoint &pos) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:30 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.