kcheckaccelerators.cpp 11 KB
Newer Older
1 2 3 4 5
/*
    This file is part of the KDE libraries
    SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
    SPDX-FileCopyrightText: 1998, 1999, 2000 KDE Team 
    SPDX-FileCopyrightText: 2008 Nick Shaforostoff <shaforostoff@kde.ru>
6

7
    SPDX-License-Identifier: LGPL-2.0-or-later
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 */

#include "kcheckaccelerators.h"

#include <QApplication>
#include <QCheckBox>
#include <QDialog>
#include <QShortcutEvent>
#include <QMouseEvent>
#include <QPushButton>
#include <QTabBar>
#include <QTextBrowser>
#include <QChar>
#include <QLabel>
#include <QComboBox>
#include <QGroupBox>
#include <QClipboard>
#include <QProcess>
Pino Toscano's avatar
Pino Toscano committed
26
#include <QDialogButtonBox>
Laurent Montel's avatar
Laurent Montel committed
27 28 29
#include <QAction>
#include <QMenu>
#include <QVBoxLayout>
30 31 32 33 34 35 36 37 38 39

#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <klocalizedstring.h>
#include <kacceleratormanager.h>

class KCheckAcceleratorsInitializer : public QObject
{
    Q_OBJECT
40
public:
Kevin Funk's avatar
Kevin Funk committed
41
    explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr)
42 43 44 45
        : QObject(parent)
    {
    }

46 47 48
public Q_SLOTS:
    void initiateIfNeeded()
    {
49 50
        KConfigGroup cg(KSharedConfig::openConfig(), "Development");
        QString sKey = cg.readEntry("CheckAccelerators").trimmed();
51 52
        int key = 0;
        if (!sKey.isEmpty()) {
53 54 55 56
            QList<QKeySequence> cuts = QKeySequence::listFromString(sKey);
            if (!cuts.isEmpty()) {
                key = cuts.first()[0];
            }
57
        }
58 59
        const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true);
        const bool copyWidgetText = cg.readEntry("CopyWidgetText", false);
60
        if (!copyWidgetText && key == 0 && !autoCheck) {
61
            deleteLater();
62
            return;
63
        }
64 65 66 67 68 69 70 71

        new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText);
        deleteLater();
    }
};

static void startupFunc()
{
72 73 74 75 76 77 78 79 80
    // Static because in some cases this is called multiple times
    // but if an application had any of the bad cases we always want
    // to skip the check
    static bool doCheckAccelerators = true;

    if (!doCheckAccelerators) {
        return;
    }

81
    QCoreApplication *app = QCoreApplication::instance();
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    if (!app) {
        // We're being loaded by something that doesn't have a QCoreApplication
        // this would probably crash at some later point since we do use qApp->
        // quite a lot, so skip the magic
        doCheckAccelerators = false;
        return;
    }

    if (!QCoreApplication::startingUp()) {
        // If the app has already started, this means we're not being run as part of
        // qt_call_pre_routines, which most probably means that we're being run as part
        // of KXmlGui being loaded as part of some plugin of the app, so don't
        // do any magic
        doCheckAccelerators = false;
        return;
    }

    if (!QCoreApplication::eventDispatcher()) {
        // We are called with event dispatcher being null when KXmlGui is being loaded
        // through plasma-integration instead of being linked to the app (i.e. QtCreator vs Okular)
        // For apps that don't link directly to KXmlGui do not do the accelerator magic
        doCheckAccelerators = false;
        return;
    }

107
    KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app);
108 109
    // Call initiateIfNeeded once we're in the event loop
    // This is to prevent using KSharedConfig before main() can set the app name
110 111 112 113 114
    QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection);
}

Q_COREAPP_STARTUP_FUNCTION(startupFunc)

115
KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_)
116 117 118 119 120
    : QObject(parent)
    , key(key_)
    , block(false)
    , autoCheck(autoCheck_)
    , copyWidgetText(copyWidgetText_)
Kevin Funk's avatar
Kevin Funk committed
121
    , drklash(nullptr)
122
{
123
    setObjectName(QStringLiteral("kapp_accel_filter"));
124

125 126 127
    KConfigGroup cg(KSharedConfig::openConfig(), "Development");
    alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false);
    copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString());
128

129
    parent->installEventFilter(this);
130
    connect(&autoCheckTimer, &QTimer::timeout, this, &KCheckAccelerators::autoCheckSlot);
131 132
}

133
bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e)
134
{
135
    if (block) {
136
        return false;
137
    }
138

139
    switch (e->type()) {   // just simplify debuggin
140
    case QEvent::ShortcutOverride:
141 142 143 144 145 146 147
        if (key && (static_cast<QKeyEvent *>(e)->key() == key)) {
            block = true;
            checkAccelerators(false);
            block = false;
            e->accept();
            return true;
        }
148 149 150 151
        break;
    case QEvent::ChildAdded:
    case QEvent::ChildRemoved:
        // Only care about widgets; this also avoids starting the timer in other threads
152
        if (!static_cast<QChildEvent *>(e)->child()->isWidgetType()) {
153
            break;
154
        }
Laurent Montel's avatar
Laurent Montel committed
155
        Q_FALLTHROUGH();
156
    // fall-through
157 158 159 160
    case QEvent::Resize:
    case QEvent::LayoutRequest:
    case QEvent::WindowActivate:
    case QEvent::WindowDeactivate:
161 162 163
        if (autoCheck) {
            autoCheckTimer.setSingleShot(true);
            autoCheckTimer.start(20);   // 20 ms
164 165 166 167
        }
        break;
    //case QEvent::MouseButtonDblClick:
    case QEvent::MouseButtonPress:
168
        if (copyWidgetText && static_cast<QMouseEvent *>(e)->button() == Qt::MiddleButton) {
169
            //kWarning()<<"obj"<<obj;
170 171 172 173 174
            QWidget *w = static_cast<QWidget *>(obj)->childAt(static_cast<QMouseEvent *>(e)->pos());
            if (!w) {
                w = static_cast<QWidget *>(obj);
            }
            if (!w) {
175
                return false;
176
            }
177 178
            //kWarning()<<"MouseButtonDblClick"<<w;
            QString text;
179 180 181 182 183 184 185 186 187 188 189 190 191
            if (qobject_cast<QLabel *>(w)) {
                text = static_cast<QLabel *>(w)->text();
            } else if (qobject_cast<QAbstractButton *>(w)) {
                text = static_cast<QAbstractButton *>(w)->text();
            } else if (qobject_cast<QComboBox *>(w)) {
                text = static_cast<QComboBox *>(w)->currentText();
            } else if (qobject_cast<QTabBar *>(w)) {
                text = static_cast<QTabBar *>(w)->tabText(static_cast<QTabBar *>(w)->tabAt(static_cast<QMouseEvent *>(e)->pos()));
            } else if (qobject_cast<QGroupBox *>(w)) {
                text = static_cast<QGroupBox *>(w)->title();
            } else if (qobject_cast<QMenu *>(obj)) {
                QAction *a = static_cast<QMenu *>(obj)->actionAt(static_cast<QMouseEvent *>(e)->pos());
                if (!a) {
192
                    return false;
193 194 195 196 197
                }
                text = a->text();
                if (text.isEmpty()) {
                    text = a->iconText();
                }
198
            }
199
            if (text.isEmpty()) {
200
                return false;
201
            }
202

203
            if (static_cast<QMouseEvent *>(e)->modifiers() == Qt::ControlModifier) {
204
                text.remove(QChar::fromLatin1('&'));
205
            }
206 207

            //kWarning()<<KGlobal::activeComponent().catalogName()<<text;
208
            if (copyWidgetTextCommand.isEmpty()) {
209 210
                QClipboard *clipboard = QApplication::clipboard();
                clipboard->setText(text);
211 212
            } else {
                QProcess *script = new QProcess(this);
213
                script->start(copyWidgetTextCommand.arg(text, QFile::decodeName(KLocalizedString::applicationDomain())), QStringList());
214 215
                connect(script, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                        script, &QObject::deleteLater);
216 217 218 219 220
            }
            e->accept();
            return true;

            //kWarning()<<"MouseButtonDblClick"<<static_cast<QWidget*>(obj)->childAt(static_cast<QMouseEvent*>(e)->globalPos());
221
        }
222 223 224 225 226 227
        return false;
    case QEvent::Timer:
    case QEvent::MouseMove:
    case QEvent::Paint:
        return false;
    default:
Laurent Montel's avatar
Laurent Montel committed
228
        // qCDebug(DEBUG_KXMLGUI) << "KCheckAccelerators::eventFilter " << e->type() << " " << autoCheck;
229 230 231 232 233 234 235
        break;
    }
    return false;
}

void KCheckAccelerators::autoCheckSlot()
{
236 237 238
    if (block) {
        autoCheckTimer.setSingleShot(true);
        autoCheckTimer.start(20);
239 240 241
        return;
    }
    block = true;
242
    checkAccelerators(!alwaysShow);
243 244 245 246 247
    block = false;
}

void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic)
{
248
    if (drklash) {
249
        return;
250
    }
251

252 253 254 255 256 257 258 259
    drklash = new QDialog(actWin);
    drklash->setAttribute(Qt::WA_DeleteOnClose);
    drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg"));
    drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis"));
    drklash->resize(500, 460);
    QVBoxLayout *layout = new QVBoxLayout(drklash);
    drklash_view = new QTextBrowser(drklash);
    layout->addWidget(drklash_view);
Kevin Funk's avatar
Kevin Funk committed
260
    QCheckBox *disableAutoCheck = nullptr;
261 262
    if (automatic)  {
        disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash);
263
        connect(disableAutoCheck, &QCheckBox::toggled, this, &KCheckAccelerators::slotDisableCheck);
264
        layout->addWidget(disableAutoCheck);
265
    }
Pino Toscano's avatar
Pino Toscano committed
266 267
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash);
    layout->addWidget(buttonBox);
268
    connect(buttonBox, &QDialogButtonBox::rejected, drklash, &QDialog::close);
269
    if (disableAutoCheck) {
270
        disableAutoCheck->setFocus();
271
    } else {
272
        drklash_view->setFocus();
273
    }
274 275 276 277 278
}

void KCheckAccelerators::slotDisableCheck(bool on)
{
    autoCheck = !on;
279
    if (!on) {
280
        autoCheckSlot();
281
    }
282 283
}

284
void KCheckAccelerators::checkAccelerators(bool automatic)
285
{
286 287
    QWidget *actWin = qApp->activeWindow();
    if (!actWin) {
288
        return;
289
    }
290 291 292 293 294

    KAcceleratorManager::manage(actWin);
    QString a, c, r;
    KAcceleratorManager::last_manage(a, c,  r);

295
    if (automatic) { // for now we only show dialogs on F12 checks
296
        return;
297
    }
298

299
    if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) {
300
        return;
301
    }
302 303 304

    QString s;

305
    if (! c.isEmpty())  {
306 307 308 309 310 311 312 313
        s += i18n("<h2>Accelerators changed</h2>") +
             QLatin1String("<table border><tr><th><b>") +
             i18n("Old Text") +
             QLatin1String("</b></th><th><b>") +
             i18n("New Text") +
             QLatin1String("</b></th></tr>") +
             c +
             QLatin1String("</table>");
314 315
    }

316
    if (! r.isEmpty())  {
317 318 319 320 321 322
        s += i18n("<h2>Accelerators removed</h2>") +
             QLatin1String("<table border><tr><th><b>") +
             i18n("Old Text") +
             QLatin1String("</b></th></tr>") +
             r +
             QLatin1String("</table>");
323 324
    }

325
    if (! a.isEmpty())  {
326 327 328 329 330 331
        s += i18n("<h2>Accelerators added (just for your info)</h2>") +
             QLatin1String("<table border><tr><th><b>") +
             i18n("New Text") +
             QLatin1String("</b></th></tr>") +
             a +
             QLatin1String("</table>");
332 333 334 335 336 337 338 339 340 341 342
    }

    createDialog(actWin, automatic);
    drklash_view->setHtml(s);
    drklash->show();
    drklash->raise();

    // dlg will be destroyed before returning
}

#include "kcheckaccelerators.moc"