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