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-2023 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 Akonadi::EntityTreeViewPrivate
35 {
36 public:
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 
60 void 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);
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(EntityTreeView::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 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 
122 void 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 
140 void 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 
157 void 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 
203  header()->setStretchLastSection(true);
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 
213 void 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
226 void 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 
239 void 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
250 void 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
283 void 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"
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
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:243
void setDefaultPopupMenu(const QString &name)
Set the name of the default popup menu (retrieved from the application's XMLGUI file).
QAbstractItemView::DropIndicatorPosition dropIndicatorPosition() const const
void doubleClicked(const QModelIndex &index)
A view to show an item/collection tree provided by an EntityTreeModel.
void setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
Sets the XML GUI client which the view is used in.
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
bool isManualSortingActive() const
Return true if we use an manual sorting Necessary to fix dnd menu We must show just move when we move...
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setModel(QAbstractItemModel *model) override
QModelIndex sibling(int row, int column) const const
virtual void setModel(QAbstractItemModel *model) override
virtual QVariant data(const QModelIndex &index, int role) const const=0
void setStretchLastSection(bool stretch)
KXMLGUIClient * xmlGuiClient() const
Return the XML GUI client which the view is used in.
QAbstractItemModel * model() const const
QCA_EXPORT void init()
int column() const const
T value() const const
virtual void dropEvent(QDropEvent *event) override
QPoint mapFromGlobal(const QPoint &pos) const const
AscendingOrder
QItemSelectionModel * selectionModel() const const
Represents a collection of PIM items.
Definition: collection.h:61
void setSelectionMode(QAbstractItemView::SelectionMode mode)
void setDropActionMenuEnabled(bool enabled)
Sets whether the drop action menu is enabled and will be shown on drop operation.
virtual void timerEvent(QTimerEvent *event) override
virtual bool event(QEvent *event) override
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QHeaderView * header() const const
bool isDropActionMenuEnabled() const
Returns whether the drop action menu is enabled and will be shown on drop operation.
QAbstractItemView::State state() const const
EntityTreeView(QWidget *parent=nullptr)
Creates a new entity tree view.
@ CollectionRole
The collection.
void setExpanded(const QModelIndex &index, bool expanded)
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
QPoint pos()
virtual QModelIndex indexAt(const QPoint &point) const const override
bool isValid() const const
int row() const const
IgnoreAction
virtual void dragMoveEvent(QDragMoveEvent *event) override
void setManualSortingActive(bool active)
Set true if we automatic sorting.
void clicked(const QModelIndex &index)
QWidget * viewport() const const
~EntityTreeView() override
Destroys the entity tree view.
const QAbstractItemModel * model() const const
QAction * exec()
Represents a PIM item stored in Akonadi storage.
Definition: item.h:100
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Sep 29 2023 03:51:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.