File indexing completed on 2024-04-28 16:52:19

0001 /*
0002     SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 /// Qt Utilities
0008 #include <QGuiApplication>
0009 #include <QInputMethod>
0010 #include <QInputMethodEvent>
0011 #include <QKeyEvent>
0012 #include <QQuickWindow>
0013 #include <QStringBuilder>
0014 #include <QToolTip>
0015 
0016 // Widgets for the popup
0017 #include <QGridLayout>
0018 #include <QLabel>
0019 #include <QPushButton>
0020 
0021 #include "data/plasmakeydata.h"
0022 
0023 #include "plasmaimcontext.h"
0024 
0025 static QList<TooltipData> dataForIndex(const QString &ch, bool upperCase)
0026 {
0027     QList<TooltipData> ret;
0028     int i = 0;
0029     for (const auto &item : KeyData::KeyMappings[ch]) {
0030         ret << TooltipData{upperCase ? item.toUpper() : item, QString::number((i + 1) < 10 ? (i + 1) : 0, 10), i};
0031         i++;
0032     }
0033     return ret;
0034 }
0035 
0036 PlasmaIMContext::PlasmaIMContext()
0037 {
0038     connect(watcher.data(), &KConfigWatcher::configChanged, this, &PlasmaIMContext::configChangedHandler);
0039 }
0040 
0041 PlasmaIMContext::~PlasmaIMContext()
0042 {
0043     if (!popup.isNull()) {
0044         popup->hide();
0045         popup->deleteLater();
0046     }
0047 }
0048 
0049 bool PlasmaIMContext::isValid() const
0050 {
0051     return true;
0052 }
0053 
0054 void PlasmaIMContext::cleanUpState()
0055 {
0056     if (!popup.isNull()) {
0057         popup->hide();
0058         popup->deleteLater();
0059     }
0060 
0061     isPreHold = false;
0062     preHoldText = QString();
0063 }
0064 
0065 void PlasmaIMContext::setFocusObject(QObject *object)
0066 {
0067     m_focusObject = object;
0068 }
0069 
0070 void PlasmaIMContext::configChangedHandler(const KConfigGroup &, const QByteArrayList &)
0071 {
0072     config->reparseConfiguration();
0073 }
0074 
0075 void PlasmaIMContext::showPopup(const QList<TooltipData> &text)
0076 {
0077     QPoint position;
0078     QWindow *parentWin = nullptr;
0079 
0080     auto im = QGuiApplication::inputMethod();
0081     if (im != nullptr && im->cursorRectangle().isValid()) {
0082         position = im->cursorRectangle().topRight().toPoint();
0083         parentWin = QGuiApplication::focusWindow();
0084     }
0085 
0086     auto isRtl = text[0].character[0].script() == QChar::Script_Arabic || text[0].character[0].script() == QChar::Script_Hebrew;
0087 
0088     popup = new QWidget;
0089     auto grid = new QGridLayout(popup.data());
0090     popup->setLayoutDirection(isRtl ? Qt::RightToLeft : Qt::LeftToRight);
0091     popup->setLayout(grid);
0092     int col = 0;
0093     for (const auto &item : text) {
0094         auto label = new QLabel(item.character, popup.data());
0095         auto button = new QPushButton(item.number, popup.data());
0096 
0097         button->setMaximumWidth(button->height());
0098 
0099         grid->addWidget(label, 0, col, Qt::AlignCenter);
0100         grid->addWidget(button, 1, col, Qt::AlignHCenter);
0101 
0102         connect(button, &QPushButton::clicked, [=]() {
0103             applyReplacement(item.character);
0104             popup->hide();
0105             popup->deleteLater();
0106         });
0107 
0108         col++;
0109     }
0110 
0111     connect(parentWin, &QWindow::activeChanged, this, &PlasmaIMContext::cleanUpState, Qt::UniqueConnection);
0112 
0113     if (parentWin != nullptr) {
0114         popup->setWindowFlags(Qt::WindowDoesNotAcceptFocus | Qt::ToolTip);
0115         popup->adjustSize();
0116         popup->move(position + parentWin->framePosition() - QPoint((isRtl ? popup->width() : 0), 0));
0117         popup->show();
0118     }
0119 }
0120 
0121 void PlasmaIMContext::applyReplacement(const QString &data)
0122 {
0123     if (m_focusObject != nullptr) {
0124         QInputMethodEvent ev;
0125         ev.setCommitString(data, -1, 1);
0126         QCoreApplication::sendEvent(m_focusObject, &ev);
0127     }
0128 }
0129 
0130 inline bool between(int min, int val, int max)
0131 {
0132     qDebug() << min << val << max << !(val < min || max < val);
0133     return !(val < min || max < val);
0134 }
0135 
0136 bool PlasmaIMContext::filterEvent(const QEvent *event)
0137 {
0138     bool isAccent = keyboard.readEntry("KeyRepeat", "accent") == QLatin1String("accent");
0139     bool isNothing = keyboard.readEntry("KeyRepeat", "accent") == QLatin1String("nothing");
0140 
0141     if (!isAccent && !isNothing) {
0142         return false;
0143     }
0144 
0145     if (event->type() == QEvent::KeyPress) {
0146         auto ev = static_cast<const QKeyEvent *>(event);
0147 
0148         // this is the state when we have a held key
0149         if (isPreHold) {
0150             if (ev->isAutoRepeat() && ev->text() == preHoldText)
0151                 return true;
0152 
0153             if (!between(Qt::Key_0, ev->key(), Qt::Key_9) && !between(Qt::Key_F1, ev->key(), Qt::Key_F10)) {
0154                 cleanUpState();
0155                 return false;
0156             }
0157 
0158             auto str = preHoldText;
0159             bool isUpper = str.isUpper();
0160             str = str.toLower();
0161 
0162             int keyDataIndex;
0163             if (ev->key() > Qt::Key_9) {
0164                 keyDataIndex = ev->key() - Qt::Key_F1;
0165                 keyDataIndex++;
0166             } else {
0167                 keyDataIndex = ev->key() - Qt::Key_0;
0168                 if (keyDataIndex == 0) {
0169                     keyDataIndex = 10;
0170                 }
0171             }
0172 
0173             if (KeyData::KeyMappings[str].count() < keyDataIndex) {
0174                 cleanUpState();
0175                 return false;
0176             }
0177 
0178             auto data = KeyData::KeyMappings[str][keyDataIndex - 1];
0179             applyReplacement(isUpper ? data.toUpper() : data);
0180             isPreHold = false;
0181             preHoldText = QString();
0182             popup->hide();
0183             return true;
0184         }
0185 
0186         // this is the state before we have a held key
0187         if (ev->isAutoRepeat()) {
0188             if (isNothing)
0189                 return true;
0190 
0191             if (!isPreHold) {
0192                 if (ev->text().isEmpty())
0193                     return false;
0194                 if (!KeyData::KeyMappings.contains(ev->text().toLower()))
0195                     return false;
0196 
0197                 auto tooltipText = dataForIndex(ev->text().toLower(), ev->text().isUpper());
0198                 showPopup(tooltipText);
0199 
0200                 isPreHold = true;
0201                 preHoldText = ev->text();
0202             }
0203 
0204             return true;
0205         }
0206 
0207         cleanUpState();
0208     }
0209 
0210     return false;
0211 }