File indexing completed on 2024-09-08 12:23:21
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"