File indexing completed on 2024-04-14 03:57:08

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
0004     SPDX-FileCopyrightText: 1998, 1999, 2000 KDE Team
0005     SPDX-FileCopyrightText: 2008 Nick Shaforostoff <shaforostoff@kde.ru>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "kcheckaccelerators.h"
0011 
0012 #include <QAction>
0013 #include <QApplication>
0014 #include <QChar>
0015 #include <QCheckBox>
0016 #include <QClipboard>
0017 #include <QComboBox>
0018 #include <QDebug>
0019 #include <QDialog>
0020 #include <QDialogButtonBox>
0021 #include <QFile>
0022 #include <QGroupBox>
0023 #include <QLabel>
0024 #include <QMenu>
0025 #include <QMouseEvent>
0026 #include <QPushButton>
0027 #include <QShortcutEvent>
0028 #include <QTabBar>
0029 #include <QTextBrowser>
0030 #include <QVBoxLayout>
0031 
0032 #include <KAcceleratorManager>
0033 #include <KConfig>
0034 #include <KConfigGroup>
0035 #include <KLocalizedString>
0036 #include <KSharedConfig>
0037 
0038 class KCheckAcceleratorsInitializer : public QObject
0039 {
0040     Q_OBJECT
0041 public:
0042     explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr)
0043         : QObject(parent)
0044     {
0045     }
0046 
0047 public Q_SLOTS:
0048     void initiateIfNeeded()
0049     {
0050         KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Development"));
0051         QString sKey = cg.readEntry("CheckAccelerators").trimmed();
0052         int key = 0;
0053         if (!sKey.isEmpty()) {
0054             QList<QKeySequence> cuts = QKeySequence::listFromString(sKey);
0055             if (!cuts.isEmpty()) {
0056                 key = cuts.first()[0].toCombined();
0057             }
0058         }
0059         const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true);
0060         if (key == 0 && !autoCheck) {
0061             deleteLater();
0062             return;
0063         }
0064 
0065         new KCheckAccelerators(qApp, key, autoCheck);
0066         deleteLater();
0067     }
0068 };
0069 
0070 static void startupFunc()
0071 {
0072     // Static because in some cases this is called multiple times
0073     // but if an application had any of the bad cases we always want
0074     // to skip the check
0075     static bool doCheckAccelerators = true;
0076 
0077     if (!doCheckAccelerators) {
0078         return;
0079     }
0080 
0081     QCoreApplication *app = QCoreApplication::instance();
0082     if (!app) {
0083         // We're being loaded by something that doesn't have a QCoreApplication
0084         // this would probably crash at some later point since we do use qApp->
0085         // quite a lot, so skip the magic
0086         doCheckAccelerators = false;
0087         return;
0088     }
0089 
0090     if (!QCoreApplication::startingUp()) {
0091         // If the app has already started, this means we're not being run as part of
0092         // qt_call_pre_routines, which most probably means that we're being run as
0093         // part of KXmlGui being loaded as part of some plugin of the app, so don't
0094         // do any magic
0095         doCheckAccelerators = false;
0096         return;
0097     }
0098 
0099     if (!QCoreApplication::eventDispatcher()) {
0100         // We are called with event dispatcher being null when KXmlGui is being
0101         // loaded through plasma-integration instead of being linked to the app
0102         // (i.e. QtCreator vs Okular) For apps that don't link directly to KXmlGui
0103         // do not do the accelerator magic
0104         doCheckAccelerators = false;
0105         return;
0106     }
0107 
0108     KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app);
0109     // Call initiateIfNeeded once we're in the event loop
0110     // This is to prevent using KSharedConfig before main() can set the app name
0111     QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection);
0112 }
0113 
0114 Q_COREAPP_STARTUP_FUNCTION(startupFunc)
0115 
0116 KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_)
0117     : QObject(parent)
0118     , key(key_)
0119     , block(false)
0120     , autoCheck(autoCheck_)
0121     , drklash(nullptr)
0122 {
0123     setObjectName(QStringLiteral("kapp_accel_filter"));
0124 
0125     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Development"));
0126     alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false);
0127 
0128     parent->installEventFilter(this);
0129     connect(&autoCheckTimer, &QTimer::timeout, this, &KCheckAccelerators::autoCheckSlot);
0130 }
0131 
0132 bool KCheckAccelerators::eventFilter(QObject * /*obj*/, QEvent *e)
0133 {
0134     if (block) {
0135         return false;
0136     }
0137 
0138     switch (e->type()) { // just simplify debuggin
0139     case QEvent::ShortcutOverride:
0140         if (key && (static_cast<QKeyEvent *>(e)->key() == key)) {
0141             block = true;
0142             checkAccelerators(false);
0143             block = false;
0144             e->accept();
0145             return true;
0146         }
0147         break;
0148     case QEvent::ChildAdded:
0149     case QEvent::ChildRemoved:
0150         // Only care about widgets; this also avoids starting the timer in other
0151         // threads
0152         if (!static_cast<QChildEvent *>(e)->child()->isWidgetType()) {
0153             break;
0154         }
0155         Q_FALLTHROUGH();
0156     // fall-through
0157     case QEvent::Resize:
0158     case QEvent::LayoutRequest:
0159     case QEvent::WindowActivate:
0160     case QEvent::WindowDeactivate:
0161         if (autoCheck) {
0162             autoCheckTimer.setSingleShot(true);
0163             autoCheckTimer.start(20); // 20 ms
0164         }
0165         return false;
0166     case QEvent::Timer:
0167     case QEvent::MouseMove:
0168     case QEvent::Paint:
0169         return false;
0170     default:
0171         // qCDebug(DEBUG_KXMLGUI) << "KCheckAccelerators::eventFilter " << e->type()
0172         // << " " << autoCheck;
0173         break;
0174     }
0175     return false;
0176 }
0177 
0178 void KCheckAccelerators::autoCheckSlot()
0179 {
0180     if (block) {
0181         autoCheckTimer.setSingleShot(true);
0182         autoCheckTimer.start(20);
0183         return;
0184     }
0185     block = true;
0186     checkAccelerators(!alwaysShow);
0187     block = false;
0188 }
0189 
0190 void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic)
0191 {
0192     if (drklash) {
0193         return;
0194     }
0195 
0196     drklash = new QDialog(actWin);
0197     drklash->setAttribute(Qt::WA_DeleteOnClose);
0198     drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg"));
0199     drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis"));
0200     drklash->resize(500, 460);
0201     QVBoxLayout *layout = new QVBoxLayout(drklash);
0202     drklash_view = new QTextBrowser(drklash);
0203     layout->addWidget(drklash_view);
0204     QCheckBox *disableAutoCheck = nullptr;
0205     if (automatic) {
0206         disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash);
0207         connect(disableAutoCheck, &QCheckBox::toggled, this, &KCheckAccelerators::slotDisableCheck);
0208         layout->addWidget(disableAutoCheck);
0209     }
0210     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash);
0211     layout->addWidget(buttonBox);
0212     connect(buttonBox, &QDialogButtonBox::rejected, drklash, &QDialog::close);
0213     if (disableAutoCheck) {
0214         disableAutoCheck->setFocus();
0215     } else {
0216         drklash_view->setFocus();
0217     }
0218 }
0219 
0220 void KCheckAccelerators::slotDisableCheck(bool on)
0221 {
0222     autoCheck = !on;
0223     if (!on) {
0224         autoCheckSlot();
0225     }
0226 }
0227 
0228 void KCheckAccelerators::checkAccelerators(bool automatic)
0229 {
0230     QWidget *actWin = qApp->activeWindow();
0231     if (!actWin) {
0232         return;
0233     }
0234 
0235     KAcceleratorManager::manage(actWin);
0236     QString a;
0237     QString c;
0238     QString r;
0239     KAcceleratorManager::last_manage(a, c, r);
0240 
0241     if (automatic) { // for now we only show dialogs on F12 checks
0242         return;
0243     }
0244 
0245     if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) {
0246         return;
0247     }
0248 
0249     QString s;
0250 
0251     if (!c.isEmpty()) {
0252         s += i18n("<h2>Accelerators changed</h2>")
0253             + QLatin1String(
0254                   "<table "
0255                   "border><tr><th><b>%1</b></th><th><b>%2</b></th></tr>%3</table>")
0256                   .arg(i18n("Old Text"), i18n("New Text"), c);
0257     }
0258 
0259     if (!r.isEmpty()) {
0260         s += i18n("<h2>Accelerators removed</h2>") + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("Old Text"), r);
0261     }
0262 
0263     if (!a.isEmpty()) {
0264         s += i18n("<h2>Accelerators added (just for your info)</h2>")
0265             + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("New Text"), a);
0266     }
0267 
0268     createDialog(actWin, automatic);
0269     drklash_view->setHtml(s);
0270     drklash->show();
0271     drklash->raise();
0272 
0273     // dlg will be destroyed before returning
0274 }
0275 
0276 #include "kcheckaccelerators.moc"
0277 #include "moc_kcheckaccelerators.cpp"