File indexing completed on 2024-03-24 17:02:08

0001 /*
0002    SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "hotkeys_model.h"
0008 
0009 #include "action_data/action_data_group.h"
0010 #include "action_data/menuentry_shortcut_action_data.h"
0011 #include "action_data/simple_action_data.h"
0012 
0013 #include <typeinfo>
0014 
0015 #include <QMimeData>
0016 
0017 #include <QDebug>
0018 #include <QIcon>
0019 #include <QPalette>
0020 
0021 static KHotKeys::ActionDataBase *findElement(void *ptr, KHotKeys::ActionDataGroup *root)
0022 {
0023     Q_ASSERT(root);
0024     if (!root)
0025         return nullptr;
0026 
0027     KHotKeys::ActionDataBase *match = nullptr;
0028 
0029     Q_FOREACH (KHotKeys::ActionDataBase *element, root->children()) {
0030         if (ptr == element) {
0031             match = element;
0032             break;
0033         }
0034 
0035         if (KHotKeys::ActionDataGroup *subGroup = dynamic_cast<KHotKeys::ActionDataGroup *>(element)) {
0036             match = findElement(ptr, subGroup);
0037             if (match)
0038                 break;
0039         }
0040     }
0041 
0042     return match;
0043 }
0044 
0045 KHotkeysModel::KHotkeysModel(QObject *parent)
0046     : QAbstractItemModel(parent)
0047     , _settings()
0048     , _actions(0)
0049 {
0050 }
0051 
0052 KHotkeysModel::~KHotkeysModel()
0053 {
0054 }
0055 
0056 QModelIndex KHotkeysModel::addGroup(const QModelIndex &parent)
0057 {
0058     KHotKeys::ActionDataGroup *list;
0059     if (parent.isValid()) {
0060         list = indexToActionDataGroup(parent);
0061     } else {
0062         list = _actions;
0063     }
0064     Q_ASSERT(list);
0065 
0066     beginInsertRows(parent, list->size(), list->size());
0067 
0068     /* KHotKeys:: ActionDataGroup *action = */
0069     new KHotKeys::ActionDataGroup(list, i18n("New Group"), i18n("Comment"));
0070 
0071     endInsertRows();
0072     return index(list->size() - 1, NameColumn, parent);
0073 }
0074 
0075 // Add a group
0076 QModelIndex KHotkeysModel::insertActionData(KHotKeys::ActionDataBase *data, const QModelIndex &parent)
0077 {
0078     Q_ASSERT(data);
0079 
0080     KHotKeys::ActionDataGroup *list;
0081     if (parent.isValid()) {
0082         list = indexToActionDataGroup(parent);
0083     } else {
0084         list = _actions;
0085     }
0086     Q_ASSERT(list);
0087 
0088     beginInsertRows(parent, list->size(), list->size());
0089 
0090     list->add_child(data);
0091 
0092     endInsertRows();
0093     return index(list->size() - 1, NameColumn, parent);
0094 }
0095 
0096 int KHotkeysModel::columnCount(const QModelIndex &) const
0097 {
0098     return 2;
0099 }
0100 
0101 QVariant KHotkeysModel::data(const QModelIndex &index, int role) const
0102 {
0103     // Check that the index is valid
0104     if (!index.isValid()) {
0105         return QVariant();
0106     }
0107 
0108     // Get the item behind the index
0109     KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
0110     Q_ASSERT(action);
0111 
0112     // Handle CheckStateRole
0113     if (role == Qt::CheckStateRole) {
0114         switch (index.column()) {
0115         case EnabledColumn:
0116             // If the parent is enabled we display the state of the object.
0117             // If the parent is disabled this object is disabled too.
0118             if (action->parent() && !action->parent()->isEnabled()) {
0119                 return Qt::Unchecked;
0120             }
0121             return action->isEnabled() ? Qt::Checked : Qt::Unchecked;
0122 
0123         default:
0124             return QVariant();
0125         }
0126     }
0127 
0128     // Display and Tooltip. Tooltip displays the complete name. That's nice if
0129     // there is not enough space
0130     else if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
0131         switch (index.column()) {
0132         case NameColumn:
0133             return action->name();
0134 
0135         case EnabledColumn:
0136             return QVariant();
0137 
0138         case IsGroupColumn:
0139             return indexToActionDataGroup(index) != 0;
0140 
0141         case TypeColumn: {
0142             const std::type_info &ti = typeid(*action);
0143             if (ti == typeid(KHotKeys::SimpleActionData))
0144                 return KHotkeysModel::SimpleActionData;
0145             else if (ti == typeid(KHotKeys::MenuEntryShortcutActionData))
0146                 return KHotkeysModel::SimpleActionData;
0147             else if (ti == typeid(KHotKeys::ActionDataGroup))
0148                 return KHotkeysModel::ActionDataGroup;
0149             else
0150                 return KHotkeysModel::Other;
0151         }
0152 
0153         default:
0154             return QVariant();
0155         }
0156     }
0157 
0158     // Decoration role
0159     else if (role == Qt::DecorationRole) {
0160         switch (index.column()) {
0161         // The 0 is correct here. We want to decorate that column
0162         // regardless of the content it has
0163         case 0:
0164             return dynamic_cast<KHotKeys::ActionDataGroup *>(action) ? QIcon::fromTheme("folder") : QVariant();
0165 
0166         default:
0167             return QVariant();
0168         }
0169     }
0170 
0171     // Providing the current action name on edit
0172     else if (role == Qt::EditRole) {
0173         switch (index.column()) {
0174         case NameColumn:
0175             return action->name();
0176 
0177         default:
0178             return QVariant();
0179         }
0180     }
0181 
0182     else if (role == Qt::ForegroundRole) {
0183         QPalette pal;
0184         switch (index.column()) {
0185         case NameColumn:
0186             if (!action->isEnabled()) {
0187                 return pal.color(QPalette::Disabled, QPalette::WindowText);
0188             }
0189 
0190         default:
0191             return QVariant();
0192         }
0193     }
0194 
0195     // For everything else
0196     return QVariant();
0197 }
0198 
0199 bool KHotkeysModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0200 {
0201     Q_UNUSED(column);
0202 
0203     // We only support move actions and our own mime type
0204     if ((action != Qt::CopyAction) || !data->hasFormat("application/x-pointer")) {
0205         qDebug() << "Drop not supported " << data->formats();
0206         return false;
0207     }
0208 
0209     // Decode the stream
0210     QByteArray encodedData = data->data("application/x-pointer");
0211     QDataStream stream(&encodedData, QIODevice::ReadOnly);
0212     QList<quintptr> ptrs;
0213     while (!stream.atEnd()) {
0214         quintptr ptr;
0215         stream >> ptr;
0216         ptrs << ptr;
0217     }
0218 
0219     // No pointers, nothing to do
0220     if (ptrs.empty())
0221         return false;
0222 
0223     // Get the group we have to drop into. If the drop target is no group get
0224     // it's parent and drop behind it
0225     int position = row;
0226     QModelIndex dropIndex = parent;
0227     KHotKeys::ActionDataGroup *dropToGroup = indexToActionDataGroup(dropIndex);
0228     if (!dropToGroup) {
0229         dropIndex = parent.parent();
0230         dropToGroup = indexToActionDataGroup(dropIndex);
0231         position = dropToGroup->children().indexOf(indexToActionDataBase(parent));
0232     }
0233 
0234     if (position == -1) {
0235         position = dropToGroup->size();
0236     }
0237 
0238     // Do the moves
0239     Q_FOREACH (quintptr ptr, ptrs) {
0240         KHotKeys::ActionDataBase *element = findElement(reinterpret_cast<void *>(ptr), _actions);
0241 
0242         if (element)
0243             moveElement(element, dropToGroup, position);
0244     }
0245 
0246     return true;
0247 }
0248 
0249 void KHotkeysModel::emitChanged(KHotKeys::ActionDataBase *item)
0250 {
0251     Q_ASSERT(item);
0252 
0253     KHotKeys::ActionDataGroup *parent = item->parent();
0254     QModelIndex topLeft;
0255     QModelIndex bottomRight;
0256     if (!parent) {
0257         topLeft = createIndex(0, 0, _actions);
0258         bottomRight = createIndex(0, 0, _actions);
0259     } else {
0260         int row = parent->children().indexOf(item);
0261         topLeft = createIndex(row, 0, parent);
0262         bottomRight = createIndex(row, columnCount(topLeft), parent);
0263     }
0264 
0265     emit dataChanged(topLeft, bottomRight);
0266 }
0267 
0268 void KHotkeysModel::exportInputActions(const QModelIndex &index, KConfigBase &config, const QString &id, const KHotKeys::ActionState state, bool mergingAllowed)
0269 {
0270     KHotKeys::ActionDataBase *element = indexToActionDataBase(index);
0271     KHotKeys::ActionDataGroup *group = indexToActionDataGroup(index);
0272 
0273     settings()->exportTo(group ? group : element->parent(), config, id, state, mergingAllowed);
0274 }
0275 
0276 Qt::ItemFlags KHotkeysModel::flags(const QModelIndex &index) const
0277 {
0278     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
0279 
0280     Q_ASSERT(!(flags & Qt::ItemIsDropEnabled));
0281     Q_ASSERT(!(flags & Qt::ItemIsDragEnabled));
0282 
0283     if (!index.isValid()) {
0284         return flags | Qt::ItemIsDropEnabled;
0285     }
0286 
0287     KHotKeys::ActionDataBase *element = indexToActionDataBase(index);
0288     KHotKeys::ActionDataGroup *actionGroup = indexToActionDataGroup(index);
0289     if (!actionGroup)
0290         actionGroup = element->parent();
0291 
0292     Q_ASSERT(element);
0293     Q_ASSERT(actionGroup);
0294 
0295     // We do not allow dragging for system groups and their elements
0296     // We do not allow dropping into systemgroups
0297     if (!actionGroup->is_system_group()) {
0298         flags |= Qt::ItemIsDragEnabled;
0299         flags |= Qt::ItemIsDropEnabled;
0300     }
0301 
0302     // Show a checkbox in column 1 whatever is shown there.
0303     switch (index.column()) {
0304     case 1:
0305         return flags | Qt::ItemIsUserCheckable;
0306 
0307     default:
0308         return flags | Qt::ItemIsEditable;
0309     }
0310 }
0311 
0312 // Get header data for section
0313 QVariant KHotkeysModel::headerData(int section, Qt::Orientation, int role) const
0314 {
0315     if (role != Qt::DisplayRole) {
0316         return QVariant();
0317     }
0318 
0319     switch (section) {
0320     case NameColumn:
0321         return QVariant(i18nc("action name", "Name"));
0322 
0323     case EnabledColumn:
0324         return QVariant();
0325         return QVariant(i18nc("action enabled", "Enabled"));
0326 
0327     case IsGroupColumn:
0328         return QVariant(i18n("Type"));
0329 
0330     default:
0331         return QVariant();
0332     }
0333 }
0334 
0335 void KHotkeysModel::importInputActions(const QModelIndex &index, KConfigBase const &config)
0336 {
0337     KHotKeys::ActionDataGroup *group = indexToActionDataGroup(index);
0338     QModelIndex groupIndex = index;
0339     if (!group) {
0340         group = indexToActionDataBase(index)->parent();
0341         groupIndex = index.parent();
0342     }
0343 
0344     if (settings()->importFrom(group, config, KHotKeys::ImportAsk, KHotKeys::Retain)) {
0345         qDebug();
0346         reset();
0347         save();
0348     }
0349 }
0350 
0351 QModelIndex KHotkeysModel::index(int row, int column, const QModelIndex &parent) const
0352 {
0353     KHotKeys::ActionDataGroup *actionGroup = indexToActionDataGroup(parent);
0354     if (!actionGroup || row >= actionGroup->children().size()) {
0355         return QModelIndex();
0356     }
0357 
0358     KHotKeys::ActionDataBase *action = actionGroup->children().at(row);
0359     Q_ASSERT(action);
0360     return createIndex(row, column, action);
0361 }
0362 
0363 // Convert index to ActionDataBase
0364 KHotKeys::ActionDataBase *KHotkeysModel::indexToActionDataBase(const QModelIndex &index) const
0365 {
0366     if (!index.isValid()) {
0367         return _actions;
0368     }
0369     return static_cast<KHotKeys::ActionDataBase *>(index.internalPointer());
0370 }
0371 
0372 // Convert index to ActionDataGroup
0373 KHotKeys::ActionDataGroup *KHotkeysModel::indexToActionDataGroup(const QModelIndex &index) const
0374 {
0375     if (!index.isValid()) {
0376         return _actions;
0377     }
0378     return dynamic_cast<KHotKeys::ActionDataGroup *>(indexToActionDataBase(index));
0379 }
0380 
0381 void KHotkeysModel::load()
0382 {
0383     _settings.reread_settings(true);
0384     _actions = _settings.actions();
0385     reset();
0386 }
0387 
0388 QMimeData *KHotkeysModel::mimeData(const QModelIndexList &indexes) const
0389 {
0390     QMimeData *mimeData = new QMimeData();
0391     QByteArray encodedData;
0392 
0393     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0394 
0395     Q_FOREACH (const QModelIndex &index, indexes) {
0396         if (index.isValid() && index.column() == 0) {
0397             KHotKeys::ActionDataBase *element = indexToActionDataBase(index);
0398             // We use the pointer as id.
0399             stream << reinterpret_cast<quintptr>(element);
0400         }
0401     }
0402 
0403     mimeData->setData("application/x-pointer", encodedData);
0404     return mimeData;
0405 }
0406 
0407 QStringList KHotkeysModel::mimeTypes() const
0408 {
0409     QStringList types;
0410     types << "application/x-pointer";
0411     return types;
0412 }
0413 
0414 bool KHotkeysModel::moveElement(KHotKeys::ActionDataBase *element, KHotKeys::ActionDataGroup *newGroup, int position)
0415 {
0416     Q_ASSERT(element && newGroup);
0417     if (!element || !newGroup)
0418         return false;
0419 
0420     // TODO: Make this logic more advanced
0421     // We do not allow moving into our systemgroup
0422     if (newGroup->is_system_group())
0423         return false;
0424 
0425     // Make sure we don't move a group to one of it's children or
0426     // itself.
0427     KHotKeys::ActionDataGroup *tmp = newGroup;
0428     do {
0429         if (tmp == element) {
0430             qDebug() << "Forbidden move" << tmp->name();
0431             return false;
0432         }
0433     } while ((tmp = tmp->parent()));
0434 
0435     KHotKeys::ActionDataGroup *oldParent = element->parent();
0436 
0437     // TODO: Make this logic more advanced
0438     // We do not allow moving from our systemgroup
0439     if (oldParent->is_system_group())
0440         return false;
0441 
0442     // Adjust position if oldParent and newGroup are identical
0443     if (oldParent == newGroup) {
0444         if (oldParent->children().indexOf(element) < position) {
0445             --position;
0446         }
0447     }
0448 
0449     emit layoutAboutToBeChanged();
0450 
0451     // Remove it from it's current place
0452     oldParent->remove_child(element);
0453     newGroup->add_child(element, position);
0454 
0455     emit layoutChanged();
0456 
0457     return true;
0458 }
0459 
0460 // Get parent object for index
0461 QModelIndex KHotkeysModel::parent(const QModelIndex &index) const
0462 {
0463     KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
0464     if (!action) {
0465         return QModelIndex();
0466     }
0467 
0468     KHotKeys::ActionDataGroup *parent = action->parent();
0469     if (!parent) {
0470         return QModelIndex();
0471     }
0472 
0473     KHotKeys::ActionDataGroup *grandparent = parent->parent();
0474     if (!grandparent) {
0475         return QModelIndex();
0476     }
0477 
0478     int row = grandparent->children().indexOf(parent);
0479     return createIndex(row, 0, parent);
0480 }
0481 
0482 // Remove rows ( items )
0483 bool KHotkeysModel::removeRows(int row, int count, const QModelIndex &parent)
0484 {
0485     Q_ASSERT(count == 1);
0486 
0487     beginRemoveRows(parent, row, row + count - 1);
0488 
0489     KHotKeys::ActionDataGroup *list;
0490     if (parent.isValid()) {
0491         list = indexToActionDataGroup(parent);
0492     } else {
0493         list = _actions;
0494     }
0495     Q_ASSERT(list);
0496 
0497     KHotKeys::ActionDataBase *action = indexToActionDataBase(index(row, 0, parent));
0498 
0499     action->aboutToBeErased();
0500     delete action;
0501 
0502     endRemoveRows();
0503     return true;
0504 }
0505 
0506 // Number of rows for index
0507 int KHotkeysModel::rowCount(const QModelIndex &index) const
0508 {
0509     KHotKeys::ActionDataGroup *group = indexToActionDataGroup(index);
0510     if (!group) {
0511         return 0;
0512     }
0513 
0514     return group->children().count();
0515 }
0516 
0517 void KHotkeysModel::save()
0518 {
0519     _settings.write();
0520 }
0521 
0522 // Set data
0523 bool KHotkeysModel::setData(const QModelIndex &index, const QVariant &value, int role)
0524 {
0525     if (!index.isValid()) {
0526         return false;
0527     }
0528 
0529     KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
0530     Q_ASSERT(action);
0531 
0532     // Handle CheckStateRole
0533     if (role == Qt::CheckStateRole) {
0534         switch (index.column()) {
0535         case EnabledColumn: {
0536             // If the parent is enabled we display the state of the object.
0537             // If the parent is disabled this object is disabled too.
0538             if (action->parent() && !action->parent()->isEnabled()) {
0539                 // TODO: Either show a message box or enhance the gui to
0540                 // show this item cannot be enabled
0541                 return false;
0542             }
0543 
0544             value.toInt() == Qt::Checked ? action->enable() : action->disable();
0545 
0546             // If this is a group we have to inform the view that all our
0547             // children have changed. They are all disabled now
0548             KHotKeys::ActionDataGroup *actionGroup = indexToActionDataGroup(index);
0549             if (actionGroup && actionGroup->size()) {
0550                 Q_EMIT dataChanged(createIndex(0, 0, actionGroup), createIndex(actionGroup->size(), columnCount(index), actionGroup));
0551             }
0552         } break;
0553 
0554         default:
0555             return false;
0556         }
0557     } else if (role == Qt::EditRole) {
0558         switch (index.column()) {
0559         case NameColumn: {
0560             action->set_name(value.toString());
0561         } break;
0562 
0563         default:
0564             return false;
0565         }
0566     } else
0567         return false;
0568 
0569     emit dataChanged(index, index);
0570     return true;
0571 }
0572 
0573 KHotKeys::Settings *KHotkeysModel::settings()
0574 {
0575     return &_settings;
0576 }
0577 
0578 #include "moc_hotkeys_model.cpp"