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 }