File indexing completed on 2025-02-09 04:30:12
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"