Akonadi

entitytreeview.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2007 Volker Krause <[email protected]>
3  SPDX-FileCopyrightText: 2008 Stephen Kelly <[email protected]>
4  SPDX-FileCopyrightText: 2012-2021 Laurent Montel <[email protected]>
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 
29 using namespace Akonadi;
30 
31 /**
32  * @internal
33  */
34 class Q_DECL_HIDDEN EntityTreeView::Private
35 {
36 public:
37  explicit Private(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 
60 void EntityTreeView::Private::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);
77  mParent->sortByColumn(0, Qt::AscendingOrder);
78  mParent->setEditTriggers(QAbstractItemView::EditKeyPressed);
79  mParent->setAcceptDrops(true);
80 #ifndef QT_NO_DRAGANDDROP
81  mParent->setDropIndicatorShown(true);
82  mParent->setDragDropMode(DragDrop);
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 
96 void EntityTreeView::Private::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 
122 void EntityTreeView::Private::itemClicked(const QModelIndex &index) const
123 {
124  if (!index.isValid()) {
125  return;
126  }
127  QModelIndex idx = index.sibling(index.row(), 0);
128 
129  const Collection 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 
140 void EntityTreeView::Private::itemDoubleClicked(const QModelIndex &index) const
141 {
142  if (!index.isValid()) {
143  return;
144  }
145  QModelIndex idx = index.sibling(index.row(), 0);
146  const Collection 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 
157 void EntityTreeView::Private::itemCurrentChanged(const QModelIndex &index) const
158 {
159  if (!index.isValid()) {
160  return;
161  }
162  QModelIndex idx = index.sibling(index.row(), 0);
163  const Collection 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 Private(this))
177 {
179  d->init();
180 }
181 
183  : QTreeView(parent)
184  , d(new Private(this))
185 {
186  d->mXmlGuiClient = xmlGuiClient;
187  d->init();
188 }
189 
191 {
192  delete d->mDragDropManager;
193  delete d;
194 }
195 
197 {
198  if (selectionModel()) {
201  }
202 
203  QTreeView::setModel(model);
204  header()->setStretchLastSection(true);
205 
206  connect(selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const auto &index) {
207  d->itemCurrentChanged(index);
208  });
209  connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const auto &oldSel, const auto &newSel) {
210  d->slotSelectionChanged(oldSel, newSel);
211  });
212 }
213 
214 void EntityTreeView::timerEvent(QTimerEvent *event)
215 {
216  if (event->timerId() == d->mDragExpandTimer.timerId()) {
218  if (state() == QAbstractItemView::DraggingState && viewport()->rect().contains(pos)) {
219  setExpanded(indexAt(pos), true);
220  }
221  }
222 
223  QTreeView::timerEvent(event);
224 }
225 
226 #ifndef QT_NO_DRAGANDDROP
227 void EntityTreeView::dragMoveEvent(QDragMoveEvent *event)
228 {
229  d->mDragExpandTimer.start(QApplication::startDragTime(), this);
230 
231  if (d->mDragDropManager->dropAllowed(event)) {
232  // All urls are supported. process the event.
234  return;
235  }
236 
237  event->setDropAction(Qt::IgnoreAction);
238 }
239 
240 void EntityTreeView::dropEvent(QDropEvent *event)
241 {
242  d->mDragExpandTimer.stop();
243  bool menuCanceled = false;
244  if (d->mDragDropManager->processDropEvent(event, menuCanceled, (dropIndicatorPosition() == QAbstractItemView::OnItem))) {
245  QTreeView::dropEvent(event);
246  }
247 }
248 #endif
249 
250 #ifndef QT_NO_CONTEXTMENU
251 void EntityTreeView::contextMenuEvent(QContextMenuEvent *event)
252 {
253  if (!d->mXmlGuiClient || !model()) {
254  return;
255  }
256 
257  const QModelIndex index = indexAt(event->pos());
258  QString popupName = d->mDefaultPopupMenu;
259 
260  if (index.isValid()) { // popup not over empty space
261  // check whether the index under the cursor is a collection or item
262  const Item item = model()->data(index, EntityTreeModel::ItemRole).value<Item>();
263  popupName = (item.isValid() ? QStringLiteral("akonadi_itemview_contextmenu") : QStringLiteral("akonadi_collectionview_contextmenu"));
264  }
265 
266  auto popup = static_cast<QMenu *>(d->mXmlGuiClient->factory()->container(popupName, d->mXmlGuiClient));
267  if (popup) {
268  popup->exec(event->globalPos());
269  }
270 }
271 #endif
272 
274 {
275  d->mXmlGuiClient = xmlGuiClient;
276 }
277 
279 {
280  return d->mXmlGuiClient;
281 }
282 
283 #ifndef QT_NO_DRAGANDDROP
284 void EntityTreeView::startDrag(Qt::DropActions supportedActions)
285 {
286  d->mDragDropManager->startDrag(supportedActions);
287 }
288 #endif
289 
291 {
292 #ifndef QT_NO_DRAGANDDROP
293  d->mDragDropManager->setShowDropActionMenu(enabled);
294 #endif
295 }
296 
298 {
299 #ifndef QT_NO_DRAGANDDROP
300  return d->mDragDropManager->showDropActionMenu();
301 #else
302  return false;
303 #endif
304 }
305 
307 {
308 #ifndef QT_NO_DRAGANDDROP
309  d->mDragDropManager->setManualSortingActive(active);
310 #endif
311 }
312 
314 {
315 #ifndef QT_NO_DRAGANDDROP
316  return d->mDragDropManager->isManualSortingActive();
317 #else
318  return false;
319 #endif
320 }
321 
323 {
324  d->mDefaultPopupMenu = name;
325 }
326 
327 #include "moc_entitytreeview.cpp"
void doubleClicked(const QModelIndex &index)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
virtual void dragMoveEvent(QDragMoveEvent *event) override
bool isValid() const
Returns whether the collection is valid.
Definition: collection.cpp:121
QAbstractItemView::DropIndicatorPosition dropIndicatorPosition() const const
void setSelectionMode(QAbstractItemView::SelectionMode mode)
QItemSelectionModel * selectionModel() const const
void setDefaultPopupMenu(const QString &name)
Set the name of the default popup menu (retrieved from the application&#39;s XMLGUI file).
Represents a collection of PIM items.
Definition: collection.h:62
const QPersistentModelIndex & bottomRight() const const
T value() const const
QWidget * viewport() const const
bool isManualSortingActive() const
Return true if we use an manual sorting Necessary to fix dnd menu We must show just move when we move...
void setManualSortingActive(bool active)
Set true if we automatic sorting.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
const QPoint & globalPos() const const
void setExpanded(const QModelIndex &index, bool expanded)
bool isValid() const const
static void widgetNeedsAkonadi(QWidget *widget)
Disable the given widget when Akonadi is not operational and show an error overlay (given enough spac...
Definition: controlgui.cpp:250
KXMLGUIClient * xmlGuiClient() const
Return the XML GUI client which the view is used in.
virtual void timerEvent(QTimerEvent *event) override
A view to show an item/collection tree provided by an EntityTreeModel.
void setDropActionMenuEnabled(bool enabled)
Sets whether the drop action menu is enabled and will be shown on drop operation. ...
EntityTreeView(QWidget *parent=nullptr)
Creates a new entity tree view.
int row() const const
bool isDropActionMenuEnabled() const
Returns whether the drop action menu is enabled and will be shown on drop operation.
QPoint pos() const const
virtual QVariant data(const QModelIndex &index, int role) const const =0
QAction * exec()
QRect rect() const const
void setModel(QAbstractItemModel *model) override
QCA_EXPORT void init()
QAbstractItemView::State state() const const
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
virtual bool event(QEvent *event) override
int startDragTime()
const QPoint & pos() const const
const QAbstractItemModel * model() const const
QPoint pos()
AscendingOrder
QModelIndex sibling(int row, int column) const const
Helper integration between Akonadi and Qt.
virtual void setModel(QAbstractItemModel *model) override
virtual void dropEvent(QDropEvent *event) override
QPoint mapFromGlobal(const QPoint &pos) const const
int column() const const
int timerId() const const
void clicked(const QModelIndex &index)
QHeaderView * header() const const
QAbstractItemModel * model() const const
~EntityTreeView() override
Destroys the entity tree view.
void setStretchLastSection(bool stretch)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
Sets the XML GUI client which the view is used in.
QObject * parent() const const
IgnoreAction
int row() const const
virtual QModelIndex indexAt(const QPoint &point) const const override
Represents a PIM item stored in Akonadi storage.
Definition: item.h:101
const QPersistentModelIndex & topLeft() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Mar 4 2021 23:18:11 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.