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 | |
38 | class KCheckAcceleratorsInitializer : public QObject |
39 | { |
40 | Q_OBJECT |
41 | public: |
42 | explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr) |
43 | : QObject(parent) |
44 | { |
45 | } |
46 | |
47 | public 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 | |
70 | static 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 | |
114 | Q_COREAPP_STARTUP_FUNCTION(startupFunc) |
115 | |
116 | KCheckAccelerators::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 | |
132 | bool 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 | |
178 | void 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 | |
190 | void 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 | |
220 | void KCheckAccelerators::slotDisableCheck(bool on) |
221 | { |
222 | autoCheck = !on; |
223 | if (!on) { |
224 | autoCheckSlot(); |
225 | } |
226 | } |
227 | |
228 | void 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 | |