File indexing completed on 2024-09-15 10:32:55
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 "kcm_hotkeys.h" 0008 #include "hotkeys_context_menu.h" 0009 #include "ui_kcm_hotkeys.h" 0010 0011 #include <typeinfo> 0012 0013 // ACTION_DATAS 0014 #include "action_data/action_data_group.h" 0015 // OUR ACTION WIDGETS 0016 #include "action_group_widget.h" 0017 #include "global_settings_widget.h" 0018 #include "simple_action_data_widget.h" 0019 // REST 0020 #include "daemon/daemon.h" 0021 #include "hotkeys_model.h" 0022 #include "hotkeys_proxy_model.h" 0023 #include "hotkeys_tree_view.h" 0024 #include "khotkeys_interface.h" 0025 #include "khotkeysglobal.h" 0026 0027 #include <QDBusConnection> 0028 #include <QDBusError> 0029 0030 #include <KAboutData> 0031 #include <KMessageBox> 0032 #include <QDebug> 0033 0034 K_PLUGIN_FACTORY(KCMHotkeysFactory, registerPlugin<KCMHotkeys>();) 0035 0036 class KCMHotkeysPrivate : public Ui::KCMHotkeysWidget 0037 { 0038 public: 0039 KCMHotkeysPrivate(KCMHotkeys *host); 0040 0041 /** The model holding the shortcut settings. Beware! There is a proxy 0042 * model between us and that model */ 0043 KHotkeysModel *model; 0044 0045 //! Our host 0046 KCMHotkeys *q; 0047 0048 //! The currently shown dialog 0049 HotkeysWidgetIFace *current; 0050 0051 //! The currently shown item 0052 QModelIndex currentIndex; 0053 0054 /** 0055 * Show the widget. If the current widget has changes allow 0056 * cancelation ! of this action 0057 */ 0058 bool maybeShowWidget(const QModelIndex &next); 0059 0060 /** 0061 * Applies the changes from the current item 0062 */ 0063 void applyCurrentItem(); 0064 0065 void load(); 0066 void save(); 0067 }; 0068 0069 KCMHotkeys::KCMHotkeys(QWidget *parent, const QVariantList & /* args */) 0070 : KCModule(parent) 0071 , d(new KCMHotkeysPrivate(this)) 0072 { 0073 // Inform KCModule of the buttons we support 0074 KCModule::setButtons(KCModule::Buttons(KCModule::Default | KCModule::Apply | KCModule::Help)); 0075 0076 // Add the about data 0077 KAboutData *about = new KAboutData("khotkeys", 0078 i18n("KDE Hotkeys Configuration Module"), 0079 PROJECT_VERSION, 0080 QString(), 0081 KAboutLicense::GPL, 0082 i18n("Copyright 2008 (c) Michael Jansen")); 0083 about->addAuthor(i18n("Michael Jansen"), i18n("Maintainer"), "kde@michael-jansen.biz"); 0084 setAboutData(about); 0085 0086 // Tell KCModule we were changed. 0087 connect(d->action_group, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); 0088 connect(d->simple_action, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); 0089 connect(d->global_settings, SIGNAL(changed(bool)), this, SIGNAL(changed(bool))); 0090 // Update TreeView if hotkeys was changed 0091 auto emitModelChange = [this](KHotKeys::ActionDataBase *hotkey) { 0092 d->model->emitChanged(hotkey); 0093 }; 0094 connect(d->simple_action, static_cast<void (HotkeysWidgetBase::*)(KHotKeys::ActionDataBase *)>(&HotkeysWidgetBase::changed), emitModelChange); 0095 connect(d->action_group, static_cast<void (HotkeysWidgetBase::*)(KHotKeys::ActionDataBase *)>(&HotkeysWidgetBase::changed), emitModelChange); 0096 0097 // Show the context menu 0098 d->menu_button->setMenu(new HotkeysTreeViewContextMenu(d->tree_view)); 0099 0100 // Switch to the global settings dialog 0101 connect(d->settings_button, SIGNAL(clicked(bool)), SLOT(showGlobalSettings())); 0102 } 0103 0104 void KCMHotkeys::currentChanged(const QModelIndex &pCurrent, const QModelIndex &pPrevious) 0105 { 0106 // We're not interested in changes of columns. Just compare the rows 0107 QModelIndex current = pCurrent.isValid() ? pCurrent.sibling(pCurrent.row(), 0) : QModelIndex(); 0108 QModelIndex previous = pPrevious.isValid() ? pPrevious.sibling(pPrevious.row(), 0) : QModelIndex(); 0109 0110 // Now it's possible for previous and current to be the same 0111 if (current == previous || current == d->currentIndex) { 0112 return; 0113 } 0114 0115 // Current and previous differ. Ask user if there are unsaved changes 0116 if (!d->maybeShowWidget(current)) { 0117 // Bring focus back to the current item 0118 d->tree_view->selectionModel()->setCurrentIndex(d->currentIndex, QItemSelectionModel::SelectCurrent); 0119 return; 0120 } 0121 0122 if (!current.isValid()) { 0123 if (previous.isValid()) { // throw away old widget and stuff lest we have dangling pointers https://bugs.kde.org/show_bug.cgi?id=443656 0124 d->simple_action->unsetActionData(); 0125 } 0126 return showGlobalSettings(); 0127 } 0128 0129 // Now go on and activate the new item; 0130 KHotKeys::ActionDataBase *item = d->model->indexToActionDataBase(current); 0131 QModelIndex typeOfIndex = d->model->index(current.row(), KHotkeysModel::TypeColumn, current.parent()); 0132 0133 switch (d->model->data(typeOfIndex).toInt()) { 0134 case KHotkeysModel::SimpleActionData: { 0135 KHotKeys::SimpleActionData *data = dynamic_cast<KHotKeys::SimpleActionData *>(item); 0136 if (data) { 0137 d->simple_action->setActionData(data); 0138 d->current = d->simple_action; 0139 } 0140 } break; 0141 0142 case KHotkeysModel::ActionDataGroup: { 0143 KHotKeys::ActionDataGroup *group = dynamic_cast<KHotKeys::ActionDataGroup *>(item); 0144 if (group) { 0145 d->action_group->setActionData(group); 0146 d->current = d->action_group; 0147 } 0148 } break; 0149 0150 default: { 0151 const std::type_info &ti = typeid(*item); 0152 qDebug() << "##### Unknown ActionDataType " << ti.name(); 0153 } 0154 0155 } // switch 0156 0157 d->currentIndex = current; 0158 d->stack->setCurrentWidget(d->current); 0159 } 0160 0161 KCMHotkeys::~KCMHotkeys() 0162 { 0163 delete d; 0164 d = nullptr; 0165 } 0166 0167 void KCMHotkeys::defaults() 0168 { 0169 qWarning() << "not yet implemented!"; 0170 } 0171 0172 void KCMHotkeys::load() 0173 { 0174 showGlobalSettings(); 0175 d->load(); 0176 } 0177 0178 void KCMHotkeys::showGlobalSettings() 0179 { 0180 d->current = d->global_settings; 0181 d->currentIndex = QModelIndex(); 0182 0183 d->tree_view->setCurrentIndex(d->currentIndex); 0184 d->global_settings->copyFromObject(); 0185 d->stack->setCurrentWidget(d->global_settings); 0186 } 0187 0188 void KCMHotkeys::slotChanged() 0189 { 0190 emit changed(true); 0191 } 0192 0193 void KCMHotkeys::slotReset() 0194 { 0195 showGlobalSettings(); 0196 } 0197 0198 void KCMHotkeys::save() 0199 { 0200 d->save(); 0201 emit changed(false); 0202 } 0203 0204 // ========================================================================== 0205 // KCMHotkeysPrivate 0206 0207 KCMHotkeysPrivate::KCMHotkeysPrivate(KCMHotkeys *host) 0208 : Ui::KCMHotkeysWidget() 0209 , model(nullptr) 0210 , q(host) 0211 , current(nullptr) 0212 { 0213 setupUi(q); 0214 0215 // Initialize the global part of the khotkeys lib ( handler ... ) 0216 KHotKeys::init_global_data(false, q); 0217 } 0218 0219 void KCMHotkeysPrivate::load() 0220 { 0221 // Start khotkeys 0222 KHotKeys::Daemon::start(); 0223 0224 // disconnect the signals 0225 if (tree_view->selectionModel()) { 0226 // clang-format off 0227 QObject::disconnect(tree_view->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), q, SLOT(currentChanged(QModelIndex,QModelIndex))); 0228 // clang-format on 0229 } 0230 0231 // Create a new model; 0232 tree_view->setModel(new KHotkeysModel); 0233 // Delete the old 0234 delete model; 0235 // Now use the old 0236 model = tree_view->model(); 0237 0238 model->load(); 0239 global_settings->setModel(model); 0240 0241 // clang-format off 0242 QObject::connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(slotChanged())); 0243 QObject::connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(slotChanged())); 0244 QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(slotChanged())); 0245 QObject::connect(model, SIGNAL(modelAboutToBeReset()), q, SLOT(slotReset())); 0246 0247 // reconnect the signals 0248 QObject::connect(tree_view->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), q, SLOT(currentChanged(QModelIndex,QModelIndex))); 0249 // clang-format on 0250 } 0251 0252 bool KCMHotkeysPrivate::maybeShowWidget(const QModelIndex &nextIndex) 0253 { 0254 qDebug(); 0255 0256 // If the current widget is changed, ask user if switch is ok 0257 if (current && (currentIndex != nextIndex) && current->isChanged()) { 0258 const int choice = KMessageBox::warningYesNoCancel(q, 0259 i18n("The current action has unsaved changes.\n" 0260 "Do you want to apply the changes or discard them?"), 0261 i18n("Save changes"), 0262 KStandardGuiItem::apply(), 0263 KStandardGuiItem::discard(), 0264 KStandardGuiItem::cancel()); 0265 0266 switch (choice) { 0267 case KMessageBox::Yes: 0268 applyCurrentItem(); 0269 save(); 0270 return true; 0271 case KMessageBox::No: 0272 return true; 0273 case KMessageBox::Cancel: 0274 return false; 0275 default: 0276 Q_UNREACHABLE(); 0277 return false; 0278 } 0279 } 0280 return true; 0281 } 0282 0283 void KCMHotkeysPrivate::save() 0284 { 0285 if (!KHotKeys::Daemon::isRunning()) { 0286 // the daemon is not running (yet), we can just write the config and 0287 // try to start it 0288 if (current) 0289 applyCurrentItem(); 0290 // Write the settings 0291 model->save(); 0292 0293 if (KHotKeys::Daemon::start()) { 0294 // On startup the demon does the updating stuff, therefore reload 0295 // the actions. 0296 model->load(); 0297 } else { 0298 KMessageBox::error(q, "<qt>" + i18n("Unable to contact khotkeys. Your changes are saved, but they could not be activated.") + "</qt>"); 0299 } 0300 return; 0301 } 0302 0303 bool daemonFailed = false; 0304 QDBusConnection bus = QDBusConnection::sessionBus(); 0305 QPointer<OrgKdeKhotkeysInterface> iface = new OrgKdeKhotkeysInterface("org.kde.kded5", "/modules/khotkeys", bus, q); 0306 0307 QDBusError err; 0308 if (!iface->isValid()) { 0309 err = iface->lastError(); 0310 if (err.isValid()) { 0311 qCritical() << err.name() << ":" << err.message(); 0312 } 0313 daemonFailed = true; 0314 } 0315 0316 // prevent the daemon from writing dated information 0317 if (!daemonFailed) 0318 iface->declareConfigOutdated(); // mutex on 0319 0320 if (current) 0321 applyCurrentItem(); 0322 0323 // Write the settings 0324 model->save(); 0325 0326 if (!iface->isValid()) { 0327 err = iface->lastError(); 0328 if (err.isValid()) { 0329 qCritical() << err.name() << ":" << err.message(); 0330 } 0331 daemonFailed = true; 0332 } 0333 0334 // Reread the configuration. We have no possibility to check if it worked. 0335 if (daemonFailed) 0336 KMessageBox::error(q, "<qt>" + i18n("Unable to contact khotkeys. Your changes are saved, but they could not be activated.") + "</qt>"); 0337 else 0338 iface->reread_configuration(); // mutex off 0339 } 0340 0341 void KCMHotkeysPrivate::applyCurrentItem() 0342 { 0343 Q_ASSERT(current); 0344 // Only save when really changed 0345 if (current->isChanged()) { 0346 current->apply(); 0347 } 0348 } 0349 0350 #include "kcm_hotkeys.moc"