1 | /* |
2 | Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> |
3 | Copyright (c) 2008 Stephen Kelly <steveire@gmail.com> |
4 | Copyright (c) 2012 Laurent Montel <montel@kde.org> |
5 | |
6 | This library is free software; you can redistribute it and/or modify it |
7 | under the terms of the GNU Library General Public License as published by |
8 | the Free Software Foundation; either version 2 of the License, or (at your |
9 | option) any later version. |
10 | |
11 | This library is distributed in the hope that it will be useful, but WITHOUT |
12 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
14 | License for more details. |
15 | |
16 | You should have received a copy of the GNU Library General Public License |
17 | along with this library; see the file COPYING.LIB. If not, write to the |
18 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | 02110-1301, USA. |
20 | */ |
21 | |
22 | #include "entitytreeview.h" |
23 | |
24 | #include "dragdropmanager_p.h" |
25 | |
26 | #include <QtCore/QDebug> |
27 | #include <QtCore/QTimer> |
28 | #include <QApplication> |
29 | #include <QDragMoveEvent> |
30 | #include <QHeaderView> |
31 | #include <QMenu> |
32 | |
33 | #include <akonadi/collection.h> |
34 | #include <akonadi/control.h> |
35 | #include <akonadi/item.h> |
36 | #include <akonadi/entitytreemodel.h> |
37 | |
38 | #include <kdebug.h> |
39 | #include <kxmlguiclient.h> |
40 | #include <KXMLGUIFactory> |
41 | |
42 | #include "progressspinnerdelegate_p.h" |
43 | |
44 | using namespace Akonadi; |
45 | |
46 | /** |
47 | * @internal |
48 | */ |
49 | class EntityTreeView::Private |
50 | { |
51 | public: |
52 | Private(EntityTreeView *parent) |
53 | : mParent(parent) |
54 | #ifndef QT_NO_DRAGANDDROP |
55 | , mDragDropManager(new DragDropManager(mParent)) |
56 | #endif |
57 | , mXmlGuiClient(0) |
58 | , mDefaultPopupMenu(QLatin1String("akonadi_collectionview_contextmenu" )) |
59 | { |
60 | } |
61 | |
62 | void init(); |
63 | void itemClicked(const QModelIndex &index); |
64 | void itemDoubleClicked(const QModelIndex &index); |
65 | void itemCurrentChanged(const QModelIndex &index); |
66 | |
67 | void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); |
68 | |
69 | EntityTreeView *mParent; |
70 | QBasicTimer mDragExpandTimer; |
71 | DragDropManager *mDragDropManager; |
72 | KXMLGUIClient *mXmlGuiClient; |
73 | QString ; |
74 | }; |
75 | |
76 | void EntityTreeView::Private::init() |
77 | { |
78 | Akonadi::DelegateAnimator *animator = new Akonadi::DelegateAnimator(mParent); |
79 | Akonadi::ProgressSpinnerDelegate *customDelegate = new Akonadi::ProgressSpinnerDelegate(animator, mParent); |
80 | mParent->setItemDelegate(customDelegate); |
81 | |
82 | mParent->header()->setClickable(true); |
83 | mParent->header()->setStretchLastSection(false); |
84 | // mParent->setRootIsDecorated( false ); |
85 | |
86 | // QTreeView::autoExpandDelay has very strange behaviour. It toggles the collapse/expand state |
87 | // of the item the cursor is currently over when a timer event fires. |
88 | // The behaviour we want is to expand a collapsed row on drag-over, but not collapse it. |
89 | // mDragExpandTimer is used to achieve this. |
90 | // mParent->setAutoExpandDelay ( QApplication::startDragTime() ); |
91 | |
92 | mParent->setSortingEnabled(true); |
93 | mParent->sortByColumn(0, Qt::AscendingOrder); |
94 | mParent->setEditTriggers(QAbstractItemView::EditKeyPressed); |
95 | mParent->setAcceptDrops(true); |
96 | #ifndef QT_NO_DRAGANDDROP |
97 | mParent->setDropIndicatorShown(true); |
98 | mParent->setDragDropMode(DragDrop); |
99 | mParent->setDragEnabled(true); |
100 | #endif |
101 | |
102 | mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), |
103 | mParent, SLOT(itemClicked(QModelIndex))); |
104 | mParent->connect(mParent, SIGNAL(doubleClicked(QModelIndex)), |
105 | mParent, SLOT(itemDoubleClicked(QModelIndex))); |
106 | |
107 | Control::widgetNeedsAkonadi(mParent); |
108 | } |
109 | |
110 | void EntityTreeView::Private::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) |
111 | { |
112 | const int column = 0; |
113 | foreach (const QItemSelectionRange &range, selected) { |
114 | const QModelIndex index = range.topLeft(); |
115 | |
116 | if (index.column() > 0) { |
117 | continue; |
118 | } |
119 | |
120 | for (int row = index.row(); row <= range.bottomRight().row(); ++row) { |
121 | // Don't use canFetchMore here. We need to bypass the check in |
122 | // the EntityFilterModel when it shows only collections. |
123 | mParent->model()->fetchMore(index.sibling(row, column)); |
124 | } |
125 | } |
126 | |
127 | if (selected.size() == 1) { |
128 | const QItemSelectionRange &range = selected.first(); |
129 | if (range.topLeft().row() == range.bottomRight().row()) { |
130 | mParent->scrollTo(range.topLeft(), QTreeView::EnsureVisible); |
131 | } |
132 | } |
133 | } |
134 | |
135 | void EntityTreeView::Private::itemClicked(const QModelIndex &index) |
136 | { |
137 | if (!index.isValid()) { |
138 | return; |
139 | } |
140 | QModelIndex idx = index.sibling(index.row(), 0); |
141 | |
142 | const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>(); |
143 | if (collection.isValid()) { |
144 | emit mParent->clicked(collection); |
145 | } else { |
146 | const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>(); |
147 | if (item.isValid()) { |
148 | emit mParent->clicked(item); |
149 | } |
150 | } |
151 | } |
152 | |
153 | void EntityTreeView::Private::itemDoubleClicked(const QModelIndex &index) |
154 | { |
155 | if (!index.isValid()) { |
156 | return; |
157 | } |
158 | QModelIndex idx = index.sibling(index.row(), 0); |
159 | const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>(); |
160 | if (collection.isValid()) { |
161 | emit mParent->doubleClicked(collection); |
162 | } else { |
163 | const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>(); |
164 | if (item.isValid()) { |
165 | emit mParent->doubleClicked(item); |
166 | } |
167 | } |
168 | } |
169 | |
170 | void EntityTreeView::Private::itemCurrentChanged(const QModelIndex &index) |
171 | { |
172 | if (!index.isValid()) { |
173 | return; |
174 | } |
175 | QModelIndex idx = index.sibling(index.row(), 0); |
176 | const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value<Collection>(); |
177 | if (collection.isValid()) { |
178 | emit mParent->currentChanged(collection); |
179 | } else { |
180 | const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value<Item>(); |
181 | if (item.isValid()) { |
182 | emit mParent->currentChanged(item); |
183 | } |
184 | } |
185 | } |
186 | |
187 | EntityTreeView::EntityTreeView(QWidget *parent) |
188 | : QTreeView(parent) |
189 | , d(new Private(this)) |
190 | { |
191 | setSelectionMode(QAbstractItemView::SingleSelection); |
192 | d->init(); |
193 | } |
194 | |
195 | EntityTreeView::EntityTreeView(KXMLGUIClient *xmlGuiClient, QWidget *parent) |
196 | : QTreeView(parent) |
197 | , d(new Private(this)) |
198 | { |
199 | d->mXmlGuiClient = xmlGuiClient; |
200 | d->init(); |
201 | } |
202 | |
203 | EntityTreeView::~EntityTreeView() |
204 | { |
205 | delete d->mDragDropManager; |
206 | delete d; |
207 | } |
208 | |
209 | void EntityTreeView::setModel(QAbstractItemModel *model) |
210 | { |
211 | if (selectionModel()) { |
212 | disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), |
213 | this, SLOT(itemCurrentChanged(QModelIndex))); |
214 | |
215 | disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), |
216 | this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); |
217 | } |
218 | |
219 | QTreeView::setModel(model); |
220 | header()->setStretchLastSection(true); |
221 | |
222 | connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), |
223 | SLOT(itemCurrentChanged(QModelIndex))); |
224 | |
225 | connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), |
226 | SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); |
227 | } |
228 | |
229 | void EntityTreeView::timerEvent(QTimerEvent *event) |
230 | { |
231 | if (event->timerId() == d->mDragExpandTimer.timerId()) { |
232 | const QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); |
233 | if (state() == QAbstractItemView::DraggingState && viewport()->rect().contains(pos)) { |
234 | setExpanded(indexAt(pos), true); |
235 | } |
236 | } |
237 | |
238 | QTreeView::timerEvent(event); |
239 | } |
240 | |
241 | #ifndef QT_NO_DRAGANDDROP |
242 | void EntityTreeView::dragMoveEvent(QDragMoveEvent *event) |
243 | { |
244 | d->mDragExpandTimer.start(QApplication::startDragTime() , this); |
245 | |
246 | if (d->mDragDropManager->dropAllowed(event)) { |
247 | // All urls are supported. process the event. |
248 | QTreeView::dragMoveEvent(event); |
249 | return; |
250 | } |
251 | |
252 | event->setDropAction(Qt::IgnoreAction); |
253 | } |
254 | |
255 | void EntityTreeView::dropEvent(QDropEvent *event) |
256 | { |
257 | d->mDragExpandTimer.stop(); |
258 | bool = false; |
259 | if (d->mDragDropManager->processDropEvent(event, menuCanceled, (dropIndicatorPosition() == QAbstractItemView::OnItem))) { |
260 | QTreeView::dropEvent(event); |
261 | } |
262 | } |
263 | #endif |
264 | |
265 | #ifndef QT_NO_CONTEXTMENU |
266 | void EntityTreeView::(QContextMenuEvent *event) |
267 | { |
268 | if (!d->mXmlGuiClient || !model()) { |
269 | return; |
270 | } |
271 | |
272 | const QModelIndex index = indexAt(event->pos()); |
273 | QString = d->mDefaultPopupMenu; |
274 | |
275 | if (index.isValid()) { // popup not over empty space |
276 | // check whether the index under the cursor is a collection or item |
277 | const Item item = model()->data(index, EntityTreeModel::ItemRole).value<Item>(); |
278 | popupName = (item.isValid() ? QLatin1String("akonadi_itemview_contextmenu" ) : |
279 | QLatin1String("akonadi_collectionview_contextmenu" )); |
280 | } |
281 | |
282 | QMenu * = static_cast<QMenu *>(d->mXmlGuiClient->factory()->container(popupName, d->mXmlGuiClient)); |
283 | if (popup) { |
284 | popup->exec(event->globalPos()); |
285 | } |
286 | } |
287 | #endif |
288 | |
289 | void EntityTreeView::setXmlGuiClient(KXMLGUIClient *xmlGuiClient) |
290 | { |
291 | d->mXmlGuiClient = xmlGuiClient; |
292 | } |
293 | |
294 | KXMLGUIClient *EntityTreeView::xmlGuiClient() const |
295 | { |
296 | return d->mXmlGuiClient; |
297 | } |
298 | |
299 | #ifndef QT_NO_DRAGANDDROP |
300 | void EntityTreeView::startDrag(Qt::DropActions supportedActions) |
301 | { |
302 | d->mDragDropManager->startDrag(supportedActions); |
303 | } |
304 | #endif |
305 | |
306 | void EntityTreeView::(bool enabled) |
307 | { |
308 | #ifndef QT_NO_DRAGANDDROP |
309 | d->mDragDropManager->setShowDropActionMenu(enabled); |
310 | #endif |
311 | } |
312 | |
313 | bool EntityTreeView::() const |
314 | { |
315 | #ifndef QT_NO_DRAGANDDROP |
316 | return d->mDragDropManager->showDropActionMenu(); |
317 | #else |
318 | return false; |
319 | #endif |
320 | } |
321 | |
322 | void EntityTreeView::setManualSortingActive(bool active) |
323 | { |
324 | #ifndef QT_NO_DRAGANDDROP |
325 | d->mDragDropManager->setManualSortingActive(active); |
326 | #endif |
327 | } |
328 | |
329 | bool EntityTreeView::isManualSortingActive() const |
330 | { |
331 | #ifndef QT_NO_DRAGANDDROP |
332 | return d->mDragDropManager->isManualSortingActive(); |
333 | #else |
334 | return false; |
335 | #endif |
336 | } |
337 | |
338 | void EntityTreeView::(const QString &name) |
339 | { |
340 | d->mDefaultPopupMenu = name; |
341 | } |
342 | |
343 | #include "moc_entitytreeview.cpp" |
344 | |