File indexing completed on 2024-09-15 12:55:42
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"