1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
4 SPDX-FileCopyrightText: 1998, 1999, 2000 KDE Team
5 SPDX-FileCopyrightText: 2008 Nick Shaforostoff <shaforostoff@kde.ru>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
10#include "kcheckaccelerators.h"
11
12#include <QAction>
13#include <QApplication>
14#include <QChar>
15#include <QCheckBox>
16#include <QClipboard>
17#include <QComboBox>
18#include <QDebug>
19#include <QDialog>
20#include <QDialogButtonBox>
21#include <QFile>
22#include <QGroupBox>
23#include <QLabel>
24#include <QMenu>
25#include <QMouseEvent>
26#include <QPushButton>
27#include <QShortcutEvent>
28#include <QTabBar>
29#include <QTextBrowser>
30#include <QVBoxLayout>
31
32#include <KAcceleratorManager>
33#include <KConfig>
34#include <KConfigGroup>
35#include <KLocalizedString>
36#include <KSharedConfig>
37
38class KCheckAcceleratorsInitializer : public QObject
39{
40 Q_OBJECT
41public:
42 explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr)
43 : QObject(parent)
44 {
45 }
46
47public Q_SLOTS:
48 void initiateIfNeeded()
49 {
50 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Development"));
51 QString sKey = cg.readEntry(key: "CheckAccelerators").trimmed();
52 int key = 0;
53 if (!sKey.isEmpty()) {
54 QList<QKeySequence> cuts = QKeySequence::listFromString(str: sKey);
55 if (!cuts.isEmpty()) {
56 key = cuts.first()[0].toCombined();
57 }
58 }
59 const bool autoCheck = cg.readEntry(key: "AutoCheckAccelerators", defaultValue: true);
60 if (key == 0 && !autoCheck) {
61 deleteLater();
62 return;
63 }
64
65 new KCheckAccelerators(qApp, key, autoCheck);
66 deleteLater();
67 }
68};
69
70static void startupFunc()
71{
72 // Static because in some cases this is called multiple times
73 // but if an application had any of the bad cases we always want
74 // to skip the check
75 static bool doCheckAccelerators = true;
76
77 if (!doCheckAccelerators) {
78 return;
79 }
80
81 QCoreApplication *app = QCoreApplication::instance();
82 if (!app) {
83 // We're being loaded by something that doesn't have a QCoreApplication
84 // this would probably crash at some later point since we do use qApp->
85 // quite a lot, so skip the magic
86 doCheckAccelerators = false;
87 return;
88 }
89
90 if (!QCoreApplication::startingUp()) {
91 // If the app has already started, this means we're not being run as part of
92 // qt_call_pre_routines, which most probably means that we're being run as
93 // part of KXmlGui being loaded as part of some plugin of the app, so don't
94 // do any magic
95 doCheckAccelerators = false;
96 return;
97 }
98
99 if (!QCoreApplication::eventDispatcher()) {
100 // We are called with event dispatcher being null when KXmlGui is being
101 // loaded through plasma-integration instead of being linked to the app
102 // (i.e. QtCreator vs Okular) For apps that don't link directly to KXmlGui
103 // do not do the accelerator magic
104 doCheckAccelerators = false;
105 return;
106 }
107
108 KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app);
109 // Call initiateIfNeeded once we're in the event loop
110 // This is to prevent using KSharedConfig before main() can set the app name
111 QMetaObject::invokeMethod(obj: initializer, member: "initiateIfNeeded", c: Qt::QueuedConnection);
112}
113
114Q_COREAPP_STARTUP_FUNCTION(startupFunc)
115
116KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_)
117 : QObject(parent)
118 , key(key_)
119 , block(false)
120 , autoCheck(autoCheck_)
121 , drklash(nullptr)
122{
123 setObjectName(QStringLiteral("kapp_accel_filter"));
124
125 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("Development"));
126 alwaysShow = cg.readEntry(key: "AlwaysShowCheckAccelerators", defaultValue: false);
127
128 parent->installEventFilter(filterObj: this);
129 connect(sender: &autoCheckTimer, signal: &QTimer::timeout, context: this, slot: &KCheckAccelerators::autoCheckSlot);
130}
131
132bool KCheckAccelerators::eventFilter(QObject * /*obj*/, QEvent *e)
133{
134 if (block) {
135 return false;
136 }
137
138 switch (e->type()) { // just simplify debuggin
139 case QEvent::ShortcutOverride:
140 if (key && (static_cast<QKeyEvent *>(e)->key() == key)) {
141 block = true;
142 checkAccelerators(automatic: false);
143 block = false;
144 e->accept();
145 return true;
146 }
147 break;
148 case QEvent::ChildAdded:
149 case QEvent::ChildRemoved:
150 // Only care about widgets; this also avoids starting the timer in other
151 // threads
152 if (!static_cast<QChildEvent *>(e)->child()->isWidgetType()) {
153 break;
154 }
155 Q_FALLTHROUGH();
156 // fall-through
157 case QEvent::Resize:
158 case QEvent::LayoutRequest:
159 case QEvent::WindowActivate:
160 case QEvent::WindowDeactivate:
161 if (autoCheck) {
162 autoCheckTimer.setSingleShot(true);
163 autoCheckTimer.start(msec: 20); // 20 ms
164 }
165 return false;
166 case QEvent::Timer:
167 case QEvent::MouseMove:
168 case QEvent::Paint:
169 return false;
170 default:
171 // qCDebug(DEBUG_KXMLGUI) << "KCheckAccelerators::eventFilter " << e->type()
172 // << " " << autoCheck;
173 break;
174 }
175 return false;
176}
177
178void KCheckAccelerators::autoCheckSlot()
179{
180 if (block) {
181 autoCheckTimer.setSingleShot(true);
182 autoCheckTimer.start(msec: 20);
183 return;
184 }
185 block = true;
186 checkAccelerators(automatic: !alwaysShow);
187 block = false;
188}
189
190void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic)
191{
192 if (drklash) {
193 return;
194 }
195
196 drklash = new QDialog(actWin);
197 drklash->setAttribute(Qt::WA_DeleteOnClose);
198 drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg"));
199 drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis"));
200 drklash->resize(w: 500, h: 460);
201 QVBoxLayout *layout = new QVBoxLayout(drklash);
202 drklash_view = new QTextBrowser(drklash);
203 layout->addWidget(drklash_view);
204 QCheckBox *disableAutoCheck = nullptr;
205 if (automatic) {
206 disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash);
207 connect(sender: disableAutoCheck, signal: &QCheckBox::toggled, context: this, slot: &KCheckAccelerators::slotDisableCheck);
208 layout->addWidget(disableAutoCheck);
209 }
210 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash);
211 layout->addWidget(buttonBox);
212 connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: drklash, slot: &QDialog::close);
213 if (disableAutoCheck) {
214 disableAutoCheck->setFocus();
215 } else {
216 drklash_view->setFocus();
217 }
218}
219
220void KCheckAccelerators::slotDisableCheck(bool on)
221{
222 autoCheck = !on;
223 if (!on) {
224 autoCheckSlot();
225 }
226}
227
228void KCheckAccelerators::checkAccelerators(bool automatic)
229{
230 QWidget *actWin = qApp->activeWindow();
231 if (!actWin) {
232 return;
233 }
234
235 KAcceleratorManager::manage(widget: actWin);
236 QString a;
237 QString c;
238 QString r;
239 KAcceleratorManager::last_manage(added&: a, changed&: c, removed&: r);
240
241 if (automatic) { // for now we only show dialogs on F12 checks
242 return;
243 }
244
245 if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) {
246 return;
247 }
248
249 QString s;
250
251 if (!c.isEmpty()) {
252 s += i18n("<h2>Accelerators changed</h2>")
253 + QLatin1String(
254 "<table "
255 "border><tr><th><b>%1</b></th><th><b>%2</b></th></tr>%3</table>")
256 .arg(i18n("Old Text"), i18n("New Text"), args&: c);
257 }
258
259 if (!r.isEmpty()) {
260 s += i18n("<h2>Accelerators removed</h2>") + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("Old Text"), args&: r);
261 }
262
263 if (!a.isEmpty()) {
264 s += i18n("<h2>Accelerators added (just for your info)</h2>")
265 + QLatin1String("<table border><tr><th><b>%1</b></th></tr>%2</table>").arg(i18n("New Text"), args&: a);
266 }
267
268 createDialog(actWin, automatic);
269 drklash_view->setHtml(s);
270 drklash->show();
271 drklash->raise();
272
273 // dlg will be destroyed before returning
274}
275
276#include "kcheckaccelerators.moc"
277#include "moc_kcheckaccelerators.cpp"
278

source code of kxmlgui/src/kcheckaccelerators.cpp