File indexing completed on 2024-04-21 15:05:47

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 <QProcess>
0027 #include <QPushButton>
0028 #include <QShortcutEvent>
0029 #include <QTabBar>
0030 #include <QTextBrowser>
0031 #include <QVBoxLayout>
0032 
0033 #include <KAcceleratorManager>
0034 #include <KConfig>
0035 #include <KConfigGroup>
0036 #include <KLocalizedString>
0037 #include <KSharedConfig>
0038 
0039 class KCheckAcceleratorsInitializer : public QObject
0040 {
0041     Q_OBJECT
0042 public:
0043     explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr)
0044         : QObject(parent)
0045     {
0046     }
0047 
0048 public Q_SLOTS:
0049     void initiateIfNeeded()
0050     {
0051         KConfigGroup cg(KSharedConfig::openConfig(), "Development");
0052         QString sKey = cg.readEntry("CheckAccelerators").trimmed();
0053         int key = 0;
0054         if (!sKey.isEmpty()) {
0055             QList<QKeySequence> cuts = QKeySequence::listFromString(sKey);
0056             if (!cuts.isEmpty()) {
0057                 key = cuts.first()[0];
0058             }
0059         }
0060         const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true);
0061         const bool copyWidgetText = cg.readEntry("CopyWidgetText", false);
0062         if (!copyWidgetText && key == 0 && !autoCheck) {
0063             deleteLater();
0064             return;
0065         }
0066 
0067         new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText);
0068         deleteLater();
0069     }
0070 };
0071 
0072 static void startupFunc()
0073 {
0074     // Static because in some cases this is called multiple times
0075     // but if an application had any of the bad cases we always want
0076     // to skip the check
0077     static bool doCheckAccelerators = true;
0078 
0079     if (!doCheckAccelerators) {
0080         return;
0081     }
0082 
0083     QCoreApplication *app = QCoreApplication::instance();
0084     if (!app) {
0085         // We're being loaded by something that doesn't have a QCoreApplication
0086         // this would probably crash at some later point since we do use qApp->
0087         // quite a lot, so skip the magic
0088         doCheckAccelerators = false;
0089         return;
0090     }
0091 
0092     if (!QCoreApplication::startingUp()) {
0093         // If the app has already started, this means we're not being run as part of
0094         // qt_call_pre_routines, which most probably means that we're being run as
0095         // part of KXmlGui being loaded as part of some plugin of the app, so don't
0096         // do any magic
0097         doCheckAccelerators = false;
0098         return;
0099     }
0100 
0101     if (!QCoreApplication::eventDispatcher()) {
0102         // We are called with event dispatcher being null when KXmlGui is being
0103         // loaded through plasma-integration instead of being linked to the app
0104         // (i.e. QtCreator vs Okular) For apps that don't link directly to KXmlGui
0105         // do not do the accelerator magic
0106         doCheckAccelerators = false;
0107         return;
0108     }
0109 
0110     KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app);
0111     // Call initiateIfNeeded once we're in the event loop
0112     // This is to prevent using KSharedConfig before main() can set the app name
0113     QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection);
0114 }
0115 
0116 Q_COREAPP_STARTUP_FUNCTION(startupFunc)
0117 
0118 KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_)
0119     : QObject(parent)
0120     , key(key_)
0121     , block(false)
0122     , autoCheck(autoCheck_)
0123     , copyWidgetText(copyWidgetText_)
0124     , drklash(nullptr)
0125 {
0126     setObjectName(QStringLiteral("kapp_accel_filter"));
0127 
0128     KConfigGroup cg(KSharedConfig::openConfig(), "Development");
0129     alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false);
0130     copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString());
0131 
0132     parent->installEventFilter(this);
0133     connect(&autoCheckTimer, &QTimer::timeout, this, &KCheckAccelerators::autoCheckSlot);
0134 }
0135 
0136 bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e)
0137 {
0138     if (block) {
0139         return false;
0140     }
0141 
0142     switch (e->type()) { // just simplify debuggin
0143     case QEvent::ShortcutOverride:
0144         if (key && (static_cast<QKeyEvent *>(e)->key() == key)) {
0145             block = true;
0146             checkAccelerators(false);
0147             block = false;
0148             e->accept();
0149             return true;
0150         }
0151         break;
0152     case QEvent::ChildAdded:
0153     case QEvent::ChildRemoved:
0154         // Only care about widgets; this also avoids starting the timer in other
0155         // threads
0156         if (!static_cast<QChildEvent *>(e)->child()->isWidgetType()) {
0157             break;
0158         }
0159         Q_FALLTHROUGH();
0160     // fall-through
0161     case QEvent::Resize:
0162     case QEvent::LayoutRequest:
0163     case QEvent::WindowActivate:
0164     case QEvent::WindowDeactivate:
0165         if (autoCheck) {
0166             autoCheckTimer.setSingleShot(true);
0167             autoCheckTimer.start(20); // 20 ms
0168         }
0169         break;
0170     // case QEvent::MouseButtonDblClick:
0171     case QEvent::MouseButtonPress:
0172         if (copyWidgetText && static_cast<QMouseEvent *>(e)->button() == Qt::MiddleButton) {
0173             QWidget *w = qobject_cast<QWidget *>(obj);
0174             if (!w) {
0175                 return false;
0176             }
0177             if (auto child = w->childAt(static_cast<QMouseEvent *>(e)->pos())) {
0178                 w = child;
0179             }
0180             // kWarning()<<"MouseButtonDblClick"<<w;
0181             QString text;
0182             if (qobject_cast<QLabel *>(w)) {
0183                 text = static_cast<QLabel *>(w)->text();
0184             } else if (qobject_cast<QAbstractButton *>(w)) {
0185                 text = static_cast<QAbstractButton *>(w)->text();
0186             } else if (qobject_cast<QComboBox *>(w)) {
0187                 text = static_cast<QComboBox *>(w)->currentText();
0188             } else if (qobject_cast<QTabBar *>(w)) {
0189                 text = static_cast<QTabBar *>(w)->tabText(static_cast<QTabBar *>(w)->tabAt(static_cast<QMouseEvent *>(e)->pos()));
0190             } else if (qobject_cast<QGroupBox *>(w)) {
0191                 text = static_cast<QGroupBox *>(w)->title();
0192             } else if (qobject_cast<QMenu *>(obj)) {
0193                 QAction *a = static_cast<QMenu *>(obj)->actionAt(static_cast<QMouseEvent *>(e)->pos());
0194                 if (!a) {
0195                     return false;
0196                 }
0197                 text = a->text();
0198                 if (text.isEmpty()) {
0199                     text = a->iconText();
0200                 }
0201             }
0202             if (text.isEmpty()) {
0203                 return false;
0204             }
0205 
0206             if (static_cast<QMouseEvent *>(e)->modifiers() == Qt::ControlModifier) {
0207                 text.remove(QChar::fromLatin1('&'));
0208             }
0209 
0210             // kWarning()<<KGlobal::activeComponent().catalogName()<<text;
0211             if (copyWidgetTextCommand.isEmpty()) {
0212                 QClipboard *clipboard = QApplication::clipboard();
0213                 clipboard->setText(text);
0214             } else {
0215                 const QString textCmd = copyWidgetTextCommand.arg(text, QFile::decodeName(KLocalizedString::applicationDomain()));
0216                 const QString exec = QStandardPaths::findExecutable(textCmd);
0217                 if (exec.isEmpty()) {
0218                     qWarning() << "Could not find executable:" << textCmd;
0219                     return false;
0220                 }
0221 
0222                 QProcess *script = new QProcess(this);
0223                 script->start(exec, QStringList{});
0224                 connect(script, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), script, &QObject::deleteLater);
0225             }
0226             e->accept();
0227             return true;
0228 
0229             // kWarning()<<"MouseButtonDblClick"<<static_cast<QWidget*>(obj)->childAt(static_cast<QMouseEvent*>(e)->globalPos());
0230         }
0231         return false;
0232     case QEvent::Timer:
0233     case QEvent::MouseMove:
0234     case QEvent::Paint:
0235         return false;
0236     default:
0237         // qCDebug(DEBUG_KXMLGUI) << "KCheckAccelerators::eventFilter " << e->type()
0238         // << " " << autoCheck;
0239         break;
0240     }
0241     return false;
0242 }
0243 
0244 void KCheckAccelerators::autoCheckSlot()
0245 {
0246     if (block) {
0247         autoCheckTimer.setSingleShot(true);
0248         autoCheckTimer.start(20);
0249         return;
0250     }
0251     block = true;
0252     checkAccelerators(!alwaysShow);
0253     block = false;
0254 }
0255 
0256 void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic)
0257 {
0258     if (drklash) {
0259         return;
0260     }
0261 
0262     drklash = new QDialog(actWin);
0263     drklash->setAttribute(Qt::WA_DeleteOnClose);
0264     drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg"));
0265     drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis"));
0266     drklash->resize(500, 460);
0267     QVBoxLayout *layout = new QVBoxLayout(drklash);
0268     drklash_view = new QTextBrowser(drklash);
0269     layout->addWidget(drklash_view);
0270     QCheckBox *disableAutoCheck = nullptr;
0271     if (automatic) {
0272         disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash);
0273         connect(disableAutoCheck, &QCheckBox::toggled, this, &KCheckAccelerators::slotDisableCheck);
0274         layout->addWidget(disableAutoCheck);
0275     }
0276     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash);
0277     layout->addWidget(buttonBox);
0278     connect(buttonBox, &QDialogButtonBox::rejected, drklash, &QDialog::close);
0279     if (disableAutoCheck) {
0280         disableAutoCheck->setFocus();
0281     } else {
0282         drklash_view->setFocus();
0283     }
0284 }
0285 
0286 void KCheckAccelerators::slotDisableCheck(bool on)
0287 {
0288     autoCheck = !on;
0289     if (!on) {
0290         autoCheckSlot();
0291     }
0292 }
0293 
0294 void KCheckAccelerators::checkAccelerators(bool automatic)
0295 {
0296     QWidget *actWin = qApp->activeWindow();
0297     if (!actWin) {
0298         return;
0299     }
0300 
0301     KAcceleratorManager::manage(actWin);
0302     QString a;
0303     QString c;
0304     QString r;
0305     KAcceleratorManager::last_manage(a, c, r);
0306 
0307     if (automatic) { // for now we only show dialogs on F12 checks
0308         return;
0309     }
0310 
0311     if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) {
0312         return;
0313     }
0314 
0315     QString s;
0316 
0317     if (!c.isEmpty()) {
0318         s += i18n("<h2>Accelerators changed</h2>")
0319             + QLatin1String(
0320                   "<table "
0321                   "border><tr><th><b>%1</b></th><th><b>%2</b></th></tr>%3</table>")
0322                   .arg(i18n("Old Text"), i18n("New Text"), c);
0323     }
0324 
0325     if (!r.isEmpty()) {
0326         s += i18n("<h2>Accelerators removed</h2>") + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("Old Text"), r);
0327     }
0328 
0329     if (!a.isEmpty()) {
0330         s += i18n("<h2>Accelerators added (just for your info)</h2>")
0331             + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("New Text"), a);
0332     }
0333 
0334     createDialog(actWin, automatic);
0335     drklash_view->setHtml(s);
0336     drklash->show();
0337     drklash->raise();
0338 
0339     // dlg will be destroyed before returning
0340 }
0341 
0342 #include "kcheckaccelerators.moc"
0343 #include "moc_kcheckaccelerators.cpp"