File indexing completed on 2024-04-14 15:33:18

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"