1 /***************************************************************************
2 * actioncollectionmodel.cpp
3 * This file is part of the KDE project
4 * copyright (C) 2006-2007 by Sebastian Sauer (mail@dipe.org)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this program; see the file COPYING. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 ***************************************************************************/
19
20 #include "actioncollectionmodel.h"
21 #include "krossui_debug.h"
22
23 #include <kross/core/action.h>
24 #include <kross/core/actioncollection.h>
25 #include <kross/core/manager.h>
26
27 #include <klocalizedstring.h>
28 #include <QDebug>
29
30 #include <QEvent>
31 #include <QMimeData>
32 #include <QPointer>
33
34 using namespace Kross;
35
36 /******************************************************************************
37 * ActionCollectionModel
38 */
39
40 namespace Kross
41 {
42
43 /// \internal d-pointer class.
44 class ActionCollectionModel::Private
45 {
46 public:
47 QPointer<ActionCollection> collection;
48 Mode mode;
49 };
50
51 }
52
ActionCollectionModel(QObject * parent,ActionCollection * collection,Mode mode)53 ActionCollectionModel::ActionCollectionModel(QObject *parent, ActionCollection *collection, Mode mode)
54 : QAbstractItemModel(parent)
55 , d(new Private())
56 {
57 //krossdebug( QString( "ActionCollectionModel::ActionCollectionModel:") );
58 d->collection = collection ? collection : Kross::Manager::self().actionCollection();
59 d->mode = mode;
60 //setSupportedDragActions(Qt::MoveAction);
61
62 //ActionCollection propagates signals to parent
63 QObject::connect(d->collection, SIGNAL(dataChanged(Action*)), this, SLOT(slotDataChanged(Action*)));
64 QObject::connect(d->collection, SIGNAL(dataChanged(ActionCollection*)), this, SLOT(slotDataChanged(ActionCollection*)));
65
66 QObject::connect(d->collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionToBeInserted(ActionCollection*,ActionCollection*)));
67 QObject::connect(d->collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionInserted(ActionCollection*,ActionCollection*)));
68 QObject::connect(d->collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionToBeRemoved(ActionCollection*,ActionCollection*)));
69 QObject::connect(d->collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionRemoved(ActionCollection*,ActionCollection*)));
70
71 QObject::connect(d->collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SLOT(slotActionToBeInserted(Action*,ActionCollection*)));
72 QObject::connect(d->collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SLOT(slotActionInserted(Action*,ActionCollection*)));
73 QObject::connect(d->collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SLOT(slotActionToBeRemoved(Action*,ActionCollection*)));
74 QObject::connect(d->collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SLOT(slotActionRemoved(Action*,ActionCollection*)));
75 }
76
~ActionCollectionModel()77 ActionCollectionModel::~ActionCollectionModel()
78 {
79 delete d;
80 }
81
rootCollection() const82 ActionCollection *ActionCollectionModel::rootCollection() const
83 {
84 return d->collection;
85 }
86
rowNumber(ActionCollection * collection) const87 int ActionCollectionModel::rowNumber(ActionCollection *collection) const
88 {
89 Q_ASSERT(collection != nullptr);
90 ActionCollection *par = collection->parentCollection();
91 Q_ASSERT(par != nullptr);
92 int row = par->collections().indexOf(collection->objectName()) + par->actions().count();
93 return row;
94 }
95
indexForCollection(ActionCollection * collection) const96 QModelIndex ActionCollectionModel::indexForCollection(ActionCollection *collection) const
97 {
98 if (collection == d->collection) {
99 return QModelIndex();
100 }
101 return createIndex(rowNumber(collection), 0, collection->parentCollection());
102 }
103
indexForAction(Action * act) const104 QModelIndex ActionCollectionModel::indexForAction(Action *act) const
105 {
106 ActionCollection *coll = static_cast<ActionCollection *>(act->parent());
107 return createIndex(coll->actions().indexOf(act), 0, coll);
108 }
109
slotCollectionToBeInserted(ActionCollection * child,ActionCollection * parent)110 void ActionCollectionModel::slotCollectionToBeInserted(ActionCollection *child, ActionCollection *parent)
111 {
112 //krossdebug( QString( "ActionCollectionModel::slotCollectionToBeInserted: %1 %2" ).arg( child->name() ).arg( parent->name( ) ) );
113 Q_ASSERT(parent);
114 Q_UNUSED(child)
115 int row = parent->actions().count() + parent->collections().count(); // we assume child is appended!!
116 QModelIndex parIdx = indexForCollection(parent);
117 beginInsertRows(parIdx, row, row);
118 }
119
slotCollectionInserted(ActionCollection *,ActionCollection *)120 void ActionCollectionModel::slotCollectionInserted(ActionCollection *, ActionCollection *)
121 {
122 //krossdebug( QString( "ActionCollectionModel::slotCollectionInserted: %1 %2" ).arg( child->name( ) ).arg( parent->name( ) ) );
123 endInsertRows();
124 }
125
slotCollectionToBeRemoved(ActionCollection * child,ActionCollection * parent)126 void ActionCollectionModel::slotCollectionToBeRemoved(ActionCollection *child, ActionCollection *parent)
127 {
128 //krossdebug( QString( "ActionCollectionModel::slotCollectionToBeRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
129 int row = rowNumber(child);
130 QModelIndex parIdx = indexForCollection(parent);
131 beginRemoveRows(parIdx, row, row);
132 }
133
slotCollectionRemoved(ActionCollection *,ActionCollection *)134 void ActionCollectionModel::slotCollectionRemoved(ActionCollection *, ActionCollection *)
135 {
136 //krossdebug( QString( "ActionCollectionModel::slotCollectionRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
137 endRemoveRows();
138 }
139
slotActionToBeInserted(Action * child,ActionCollection * parent)140 void ActionCollectionModel::slotActionToBeInserted(Action *child, ActionCollection *parent)
141 {
142 //krossdebug( QString( "ActionCollectionModel::slotActionInserted: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
143 Q_ASSERT(parent);
144 Q_UNUSED(child)
145 int row = parent->actions().count(); // assume child is appended to actions!!
146 QModelIndex parIdx = indexForCollection(parent);
147 beginInsertRows(parIdx, row, row);
148 }
149
slotActionInserted(Action *,ActionCollection *)150 void ActionCollectionModel::slotActionInserted(Action *, ActionCollection *)
151 {
152 //krossdebug( QString( "ActionCollectionModel::slotActionInserted: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
153 endInsertRows();
154 }
155
slotActionToBeRemoved(Action * child,ActionCollection * parent)156 void ActionCollectionModel::slotActionToBeRemoved(Action *child, ActionCollection *parent)
157 {
158 //krossdebug( QString( "ActionCollectionModel::slotActionToBeRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
159 Q_ASSERT(parent);
160 int row = parent->actions().indexOf(child);
161 QModelIndex parIdx = indexForCollection(parent);
162 beginRemoveRows(parIdx, row, row);
163 }
164
slotActionRemoved(Action *,ActionCollection *)165 void ActionCollectionModel::slotActionRemoved(Action *, ActionCollection *)
166 {
167 //krossdebug( QString( "ActionCollectionModel::slotActionRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
168 endRemoveRows();
169 }
170
171 //NOTE: not used anymore, remove?
slotUpdated()172 void ActionCollectionModel::slotUpdated()
173 {
174 //emit layoutAboutToBeChanged();
175 //emit layoutChanged();
176 }
177
slotDataChanged(ActionCollection * coll)178 void ActionCollectionModel::slotDataChanged(ActionCollection *coll)
179 {
180 //krossdebug( QString( "ActionCollectionModel::slotDataChanged: %1" ).arg( coll->name() ) );
181 QModelIndex idx = indexForCollection(coll);
182 emit dataChanged(idx, idx); // NOTE: change if more than one column
183 }
184
slotDataChanged(Action * act)185 void ActionCollectionModel::slotDataChanged(Action *act)
186 {
187 //krossdebug( QString( "ActionCollectionModel::slotDataChanged: %1" ).arg( act->name() ) );
188 QModelIndex idx = indexForAction(act);
189 emit dataChanged(idx, idx); // NOTE: change if more than one column
190 }
191
action(const QModelIndex & index)192 Action *ActionCollectionModel::action(const QModelIndex &index)
193 {
194 ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
195 if (par == nullptr || index.row() >= par->actions().count()) {
196 return nullptr;
197 }
198 return par->actions().value(index.row());
199 }
200
collection(const QModelIndex & index)201 ActionCollection *ActionCollectionModel::collection(const QModelIndex &index)
202 {
203 ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
204 if (par == nullptr) {
205 return nullptr;
206 }
207 int row = index.row() - par->actions().count();
208 if (row < 0) {
209 return nullptr; // this is probably an action
210 }
211 return par->collection(par->collections().value(row));
212 }
213
columnCount(const QModelIndex &) const214 int ActionCollectionModel::columnCount(const QModelIndex &) const
215 {
216 return 1;
217 }
218
rowCount(const QModelIndex & index) const219 int ActionCollectionModel::rowCount(const QModelIndex &index) const
220 {
221 if (action(index)) {
222 return 0;
223 }
224 ActionCollection *par = index.isValid() ? collection(index) : d->collection.data();
225 Q_ASSERT_X(par, "ActionCollectionModel::rowCount", "index is not an action nor a collection");
226 if (!par) {
227 qWarning() << "index is not an action nor a collection" << index;
228 return 0;
229 }
230 int rows = par->actions().count() + par->collections().count();
231 return rows;
232 }
233
index(int row,int column,const QModelIndex & parent) const234 QModelIndex ActionCollectionModel::index(int row, int column, const QModelIndex &parent) const
235 {
236 if (! hasIndex(row, column, parent)) {
237 return QModelIndex();
238 }
239 ActionCollection *par = parent.isValid() ? collection(parent) : d->collection.data();
240 if (par == nullptr) {
241 // safety: may happen if parent index is an action (ModelTest tests this)
242 return QModelIndex();
243 }
244 return createIndex(row, column, par);
245 }
246
parent(const QModelIndex & index) const247 QModelIndex ActionCollectionModel::parent(const QModelIndex &index) const
248 {
249 if (! index.isValid()) {
250 return QModelIndex();
251 }
252 ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
253 Q_ASSERT(par != nullptr);
254 if (par == d->collection) {
255 return QModelIndex();
256 }
257 return createIndex(rowNumber(par), 0, par->parentCollection());
258 }
259
flags(const QModelIndex & index) const260 Qt::ItemFlags ActionCollectionModel::flags(const QModelIndex &index) const
261 {
262 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
263 if (! index.isValid()) {
264 return Qt::ItemIsDropEnabled | flags;
265 }
266
267 flags |= Qt::ItemIsSelectable;
268 //flags |= Qt::ItemIsEditable;
269 flags |= Qt::ItemIsDragEnabled;
270 flags |= Qt::ItemIsDropEnabled;
271
272 if ((index.column() == 0) && (d->mode & UserCheckable)) {
273 flags |= Qt::ItemIsUserCheckable;
274 }
275 return flags;
276 }
277
data(const QModelIndex & index,int role) const278 QVariant ActionCollectionModel::data(const QModelIndex &index, int role) const
279 {
280 if (index.isValid()) {
281 Action *act = action(index);
282 if (act) {
283 switch (role) {
284 case Qt::DecorationRole: {
285 if (d->mode & Icons)
286 if (! act->iconName().isEmpty()) {
287 return act->icon();
288 }
289 } break;
290 case Qt::DisplayRole:
291 return KLocalizedString::removeAcceleratorMarker(act->text());
292 case Qt::ToolTipRole: // fall through
293 case Qt::WhatsThisRole: {
294 if (d->mode & ToolTips) {
295 const QString file = QFileInfo(act->file()).fileName();
296 return QString("<qt><b>%1</b><br>%2</qt>")
297 .arg(file.isEmpty() ? act->name() : file)
298 .arg(act->description());
299 }
300 } break;
301 case Qt::CheckStateRole: {
302 if (d->mode & UserCheckable) {
303 return act->isEnabled() ? Qt::Checked : Qt::Unchecked;
304 }
305 } break;
306 default: break;
307 }
308 return QVariant();
309 }
310 ActionCollection *coll = collection(index);
311 if (coll) {
312 switch (role) {
313 case Qt::DecorationRole: {
314 if (d->mode & Icons)
315 if (! coll->iconName().isEmpty()) {
316 return coll->icon();
317 }
318 } break;
319 case Qt::DisplayRole:
320 return coll->text();
321 case Qt::ToolTipRole: // fall through
322 case Qt::WhatsThisRole: {
323 if (d->mode & ToolTips) {
324 return QString("<qt><b>%1</b><br>%2</qt>").arg(coll->text()).arg(coll->description());
325 }
326 } break;
327 case Qt::CheckStateRole: {
328 if (d->mode & UserCheckable) {
329 return coll->isEnabled() ? Qt::Checked : Qt::Unchecked;
330 }
331 } break;
332 default: break;
333 }
334 return QVariant();
335 }
336 }
337 return QVariant();
338 }
339
setData(const QModelIndex & index,const QVariant & value,int role)340 bool ActionCollectionModel::setData(const QModelIndex &index, const QVariant &value, int role)
341 {
342 Q_UNUSED(value);
343 if (! index.isValid() /*|| ! (d->mode & UserCheckable)*/) {
344 return false;
345 }
346
347 Action *act = action(index);
348 if (act) {
349 switch (role) {
350 //case Qt::EditRole: act->setText( value.toString() ); break;
351 case Qt::CheckStateRole: act->setEnabled(! act->isEnabled()); break;
352 default: return false;
353 }
354 return false;
355 }
356 ActionCollection *coll = collection(index);
357 if (coll) {
358 switch (role) {
359 //case Qt::EditRole: item->coll->setText( value.toString() ); break;
360 case Qt::CheckStateRole: coll->setEnabled(! coll->isEnabled()); break;
361 default: return false;
362 }
363 return false;
364 }
365 //emit dataChanged(index, index);
366 return true;
367 }
368
insertRows(int row,int count,const QModelIndex & parent)369 bool ActionCollectionModel::insertRows(int row, int count, const QModelIndex &parent)
370 {
371 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: row=" << row << " count=" << count;
372 if (! parent.isValid()) {
373 return false;
374 }
375
376 ActionCollection *coll = collection(parent);
377 if (coll) {
378 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: parentindex is ActionCollection with name=" << coll->name();
379 } else {
380 Action *act = action(parent);
381 if (act) {
382 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: parentindex is Action with name=" << act->name();
383 }
384 }
385 return QAbstractItemModel::insertRows(row, count, parent);
386 }
387
removeRows(int row,int count,const QModelIndex & parent)388 bool ActionCollectionModel::removeRows(int row, int count, const QModelIndex &parent)
389 {
390 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::removeRows: row=" << row << " count=" << count;
391 return QAbstractItemModel::removeRows(row, count, parent);
392 }
393
insertColumns(int column,int count,const QModelIndex & parent)394 bool ActionCollectionModel::insertColumns(int column, int count, const QModelIndex &parent)
395 {
396 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertColumns: column=" << column << " count=" << count;
397 return QAbstractItemModel::insertColumns(column, count, parent);
398 }
399
removeColumns(int column,int count,const QModelIndex & parent)400 bool ActionCollectionModel::removeColumns(int column, int count, const QModelIndex &parent)
401 {
402 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::removeColumns: column=" << column << " count=" << count;
403 return QAbstractItemModel::removeColumns(column, count, parent);
404 }
405
mimeTypes() const406 QStringList ActionCollectionModel::mimeTypes() const
407 {
408 //krossdebug( QString("ActionCollectionModel::mimeTypes") );
409 return QStringList() << "application/vnd.text.list";
410 }
411
fullPath(const QModelIndex & index)412 QString fullPath(const QModelIndex &index)
413 {
414 if (! index.isValid()) {
415 return QString();
416 }
417 QString n;
418 Action *a = ActionCollectionModel::action(index);
419 if (a) {
420 n = a->name();
421 } else {
422 ActionCollection *c = ActionCollectionModel::collection(index);
423 if (c) {
424 n = c->name() + '/';
425 if (! n.endsWith('/')) {
426 n += '/';
427 }
428 }
429 }
430 ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
431 for (ActionCollection *p = par; p != nullptr; p = par->parentCollection()) {
432 QString s = p->name();
433 if (! s.endsWith('/')) {
434 s += '/';
435 }
436 n = s + n;
437 }
438 return n;
439 }
440
mimeData(const QModelIndexList & indexes) const441 QMimeData *ActionCollectionModel::mimeData(const QModelIndexList &indexes) const
442 {
443 //krossdebug( QString("ActionCollectionModel::mimeData") );
444 QMimeData *mimeData = new QMimeData();
445 QByteArray encodedData;
446
447 QDataStream stream(&encodedData, QIODevice::WriteOnly);
448 foreach (const QModelIndex &index, indexes) {
449 //if( ! index.isValid() ) continue;
450 //QString text = data(index, Qt::DisplayRole).toString();
451 QString path = fullPath(index);
452 if (! path.isNull()) {
453 stream << path;
454 }
455 }
456
457 mimeData->setData("application/vnd.text.list", encodedData);
458 return mimeData;
459 }
460
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)461 bool ActionCollectionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
462 {
463 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: row=" << row << " col=" << column;
464 if (action == Qt::IgnoreAction) {
465 return true;
466 }
467 if (! data->hasFormat("application/vnd.text.list")) {
468 return false;
469 }
470 if (column > 0) {
471 return false;
472 }
473
474 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: ENCODED DATA:";
475 QByteArray encodedData = data->data("application/vnd.text.list");
476 QDataStream stream(&encodedData, QIODevice::ReadOnly);
477 QStringList newItems;
478 int rows = 0;
479 while (! stream.atEnd()) {
480 QString text;
481 stream >> text;
482 newItems << text;
483 qCDebug(KROSS_UI_LOG) << QString(" %1 \"%2\"").arg(rows).arg(text);
484 ++rows;
485 }
486
487 //FIXME: return false for now since insertRows/removeRows need to be implemented before!
488 //return false;
489
490 /*
491 int beginRow;
492 if( row != -1 )
493 beginRow = row;
494 else if( parent.isValid() )
495 beginRow = parent.row();
496 else
497 beginRow = rowCount( QModelIndex() );
498 krossdebug( QString("ActionCollectionModel::dropMimeData: beginRow=%1").arg(beginRow) );
499 */
500
501 QModelIndex targetindex = index(row, column, parent);
502 ActionCollection *coll = collection(targetindex);
503 if (coll) {
504 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: parentindex is ActionCollection with name=" << coll->name();
505 } else {
506 Action *act = this->action(targetindex);
507 if (act) {
508 qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: parentindex is Action with name=" << act->name();
509 }
510 }
511 return false;
512 //return QAbstractItemModel::dropMimeData(data, action, row, column, parent);
513 }
514
supportedDropActions() const515 Qt::DropActions ActionCollectionModel::supportedDropActions() const
516 {
517 return Qt::CopyAction | Qt::MoveAction | Qt::TargetMoveAction;
518 //return Qt::CopyAction | Qt::MoveAction | Qt::TargetMoveAction | Qt::LinkAction;
519 }
520
521 /******************************************************************************
522 * ActionCollectionProxyModel
523 */
524
ActionCollectionProxyModel(QObject * parent,ActionCollectionModel * model)525 ActionCollectionProxyModel::ActionCollectionProxyModel(QObject *parent, ActionCollectionModel *model)
526 : QSortFilterProxyModel(parent)
527 {
528 setSourceModel(model ? model : new ActionCollectionModel(this));
529 setFilterCaseSensitivity(Qt::CaseInsensitive);
530 setDynamicSortFilter(true);
531 }
532
~ActionCollectionProxyModel()533 ActionCollectionProxyModel::~ActionCollectionProxyModel()
534 {
535 }
536
setSourceModel(QAbstractItemModel * sourceModel)537 void ActionCollectionProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
538 {
539 Q_ASSERT(dynamic_cast< ActionCollectionModel * >(sourceModel));
540 QSortFilterProxyModel::setSourceModel(sourceModel);
541 }
542
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const543 bool ActionCollectionProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
544 {
545 //krossdebug( QString( "ActionCollectionProxyModel::filterAcceptsRow: row=%1 parentrow=%2" ).arg( source_row ).arg( source_parent.row() ) );
546 QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
547 if (! index.isValid()) {
548 return false;
549 }
550
551 Action *action = ActionCollectionModel::action(index);
552 if (action) {
553 return action->isEnabled() && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
554 }
555 ActionCollection *collection = ActionCollectionModel::collection(index);
556 if (collection) {
557 return collection->isEnabled();
558 }
559 return true;
560 }
561
562