File indexing completed on 2024-05-12 05:35:51

0001 /*
0002     SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kcmtablet.h"
0008 #include "inputdevice.h"
0009 #include "tabletevents.h"
0010 
0011 #include <KConfigGroup>
0012 #include <KLocalizedString>
0013 #include <KPluginFactory>
0014 #include <QGuiApplication>
0015 #include <QScreen>
0016 #include <QStandardItemModel>
0017 
0018 K_PLUGIN_FACTORY_WITH_JSON(TabletFactory, "kcm_tablet.json", registerPlugin<Tablet>();)
0019 
0020 class OrientationsModel : public QStandardItemModel
0021 {
0022     Q_OBJECT
0023 public:
0024     OrientationsModel()
0025     {
0026         auto addOrientation = [this](const QString &display, Qt::ScreenOrientation o) {
0027             auto item = new QStandardItem(display);
0028             item->setData(o, Qt::UserRole);
0029             appendRow(item);
0030         };
0031 
0032         addOrientation(i18n("Primary (default)"), Qt::PrimaryOrientation);
0033         addOrientation(i18n("Portrait"), Qt::PortraitOrientation);
0034         addOrientation(i18n("Landscape"), Qt::LandscapeOrientation);
0035         addOrientation(i18n("Inverted Portrait"), Qt::InvertedPortraitOrientation);
0036         addOrientation(i18n("Inverted Landscape"), Qt::InvertedLandscapeOrientation);
0037 
0038         setItemRoleNames({
0039             {Qt::DisplayRole, "display"},
0040             {Qt::UserRole, "value"},
0041         });
0042     }
0043 
0044     Q_SCRIPTABLE int orientationAt(int row) const
0045     {
0046         return item(row)->data(Qt::UserRole).toInt();
0047     }
0048     Q_SCRIPTABLE int rowForOrientation(int orientation)
0049     {
0050         for (int i = 0, c = rowCount(); i < c; ++i) {
0051             if (item(i)->data(Qt::UserRole) == orientation) {
0052                 return i;
0053             }
0054         }
0055 
0056         // If not found, let's just return the PrimaryOrientation
0057         return 0;
0058     }
0059 };
0060 
0061 /// This is for the Fitting tablet to screen, "" means to follow the Active screen, otherwise we pass the name
0062 class OutputsModel : public QStandardItemModel
0063 {
0064     Q_OBJECT
0065 public:
0066     OutputsModel()
0067     {
0068         setItemRoleNames({
0069             {Qt::DisplayRole, "display"},
0070             {Qt::UserRole, "name"},
0071             {Qt::UserRole + 1, "physicalSize"},
0072             {Qt::UserRole + 2, "size"},
0073         });
0074 
0075         reset();
0076 
0077         connect(qGuiApp, &QGuiApplication::screenAdded, this, &OutputsModel::reset);
0078         connect(qGuiApp, &QGuiApplication::screenRemoved, this, &OutputsModel::reset);
0079     }
0080 
0081     void reset()
0082     {
0083         clear();
0084 
0085         auto screens = qGuiApp->screens();
0086         auto it = new QStandardItem(i18n("Follow the active screen"));
0087         it->setData(screens[0]->physicalSize(), Qt::UserRole + 1); // we use the first display to give an idea
0088         it->setData(screens[0]->size(), Qt::UserRole + 2);
0089         appendRow(it);
0090 
0091         for (auto screen : screens) {
0092             auto geo = screen->geometry();
0093             auto it = new QStandardItem(i18nc("model - (x,y widthxheight)",
0094                                               "%1 - (%2,%3 %4×%5)",
0095                                               screen->model(),
0096                                               QString::number(geo.x()),
0097                                               QString::number(geo.y()),
0098                                               QString::number(geo.width()),
0099                                               QString::number(geo.height())));
0100             it->setData(screen->name(), Qt::UserRole);
0101             it->setData(screen->physicalSize(), Qt::UserRole + 1);
0102             it->setData(screen->size(), Qt::UserRole + 2);
0103             appendRow(it);
0104         }
0105 
0106         setItemRoleNames({
0107             {Qt::DisplayRole, "display"},
0108             {Qt::UserRole, "name"},
0109             {Qt::UserRole + 1, "physicalSize"},
0110             {Qt::UserRole + 2, "size"},
0111         });
0112     }
0113 
0114     Q_SCRIPTABLE QString outputNameAt(int row) const
0115     {
0116         return item(row)->data(Qt::UserRole).toString();
0117     }
0118     Q_SCRIPTABLE int rowForOutputName(const QString &outputName)
0119     {
0120         for (int i = 0, c = rowCount(); i < c; ++i) {
0121             if (item(i)->data(Qt::UserRole) == outputName) {
0122                 return i;
0123             }
0124         }
0125 
0126         // If not found, let's just return the PrimaryOrientation
0127         return 0;
0128     }
0129 };
0130 
0131 /// This model lists the different ways the tablet will fit onto its output
0132 class OutputsFittingModel : public QStandardItemModel
0133 {
0134 public:
0135     OutputsFittingModel()
0136     {
0137         appendRow(new QStandardItem(i18n("Fit to Output")));
0138         appendRow(new QStandardItem(i18n("Fit Output in tablet")));
0139         appendRow(new QStandardItem(i18n("Custom size")));
0140 
0141         setItemRoleNames({{Qt::DisplayRole, "display"}});
0142     }
0143 };
0144 
0145 Tablet::Tablet(QObject *parent, const KPluginMetaData &metaData)
0146     : KQuickManagedConfigModule(parent, metaData)
0147     , m_toolsModel(new DevicesModel("tabletTool", this))
0148     , m_padsModel(new DevicesModel("tabletPad", this))
0149 {
0150     qmlRegisterType<OutputsModel>("org.kde.plasma.tablet.kcm", 1, 0, "OutputsModel");
0151     qmlRegisterType<OrientationsModel>("org.kde.plasma.tablet.kcm", 1, 0, "OrientationsModel");
0152     qmlRegisterType<OutputsFittingModel>("org.kde.plasma.tablet.kcm", 1, 1, "OutputsFittingModel");
0153     qmlRegisterType<TabletEvents>("org.kde.plasma.tablet.kcm", 1, 1, "TabletEvents");
0154     qmlRegisterAnonymousType<InputDevice>("org.kde.plasma.tablet.kcm", 1);
0155 
0156     connect(m_toolsModel, &DevicesModel::needsSaveChanged, this, &Tablet::refreshNeedsSave);
0157     connect(m_padsModel, &DevicesModel::needsSaveChanged, this, &Tablet::refreshNeedsSave);
0158     connect(this, &Tablet::settingsRestored, this, &Tablet::refreshNeedsSave);
0159 }
0160 
0161 Tablet::~Tablet() = default;
0162 
0163 void Tablet::refreshNeedsSave()
0164 {
0165     setNeedsSave(isSaveNeeded());
0166 }
0167 
0168 bool Tablet::isSaveNeeded() const
0169 {
0170     return !m_unsavedMappings.isEmpty() || m_toolsModel->isSaveNeeded() || m_padsModel->isSaveNeeded();
0171 }
0172 
0173 bool Tablet::isDefaults() const
0174 {
0175     if (!m_unsavedMappings.isEmpty())
0176         return false;
0177 
0178     const auto cfg = KSharedConfig::openConfig("kcminputrc");
0179     if (cfg->group("ButtonRebinds").group("Tablet").isValid()) {
0180         return false;
0181     }
0182     if (cfg->group("ButtonRebinds").group("TabletTool").isValid()) {
0183         return false;
0184     }
0185     return m_toolsModel->isDefaults() && m_padsModel->isDefaults();
0186 }
0187 
0188 void Tablet::load()
0189 {
0190     m_toolsModel->load();
0191     m_padsModel->load();
0192 
0193     m_unsavedMappings.clear();
0194     Q_EMIT settingsRestored();
0195 }
0196 
0197 void Tablet::save()
0198 {
0199     m_toolsModel->save();
0200     m_padsModel->save();
0201 
0202     auto generalGroup = KSharedConfig::openConfig("kcminputrc")->group("ButtonRebinds");
0203     for (const auto &device : QStringList{"Tablet", "TabletTool"}) {
0204         for (auto it = m_unsavedMappings[device].cbegin(), itEnd = m_unsavedMappings[device].cend(); it != itEnd; ++it) {
0205             auto group = generalGroup.group(device).group(it.key());
0206             for (auto itDevice = it->cbegin(), itDeviceEnd = it->cend(); itDevice != itDeviceEnd; ++itDevice) {
0207                 const auto key = itDevice->toString(QKeySequence::PortableText);
0208                 const auto button = QString::number(itDevice.key());
0209                 if (key.isEmpty()) {
0210                     group.deleteEntry(button, KConfig::Notify);
0211                 } else {
0212                     group.writeEntry(button, QStringList{"Key", key}, KConfig::Notify);
0213                 }
0214             }
0215         }
0216     }
0217     generalGroup.sync();
0218     m_unsavedMappings.clear();
0219 }
0220 
0221 void Tablet::defaults()
0222 {
0223     m_toolsModel->defaults();
0224     m_padsModel->defaults();
0225 
0226     m_unsavedMappings.clear();
0227     const auto generalGroup = KSharedConfig::openConfig("kcminputrc")->group("ButtonRebinds");
0228     for (const auto &deviceType : QStringList{"Tablet", "TabletTool"}) {
0229         auto tabletGroup = generalGroup.group(deviceType);
0230         const auto tablets = tabletGroup.groupList();
0231         for (const auto &deviceName : tablets) {
0232             const auto buttons = tabletGroup.group(deviceName).keyList();
0233             for (const auto &button : buttons) {
0234                 m_unsavedMappings[deviceType][deviceName][button.toUInt()] = {};
0235             }
0236         }
0237     }
0238     Q_EMIT settingsRestored();
0239 }
0240 
0241 void Tablet::assignPadButtonMapping(const QString &deviceName, uint button, const QKeySequence &keySequence)
0242 {
0243     m_unsavedMappings["Tablet"][deviceName][button] = keySequence;
0244     Q_EMIT settingsRestored();
0245 }
0246 
0247 void Tablet::assignToolButtonMapping(const QString &deviceName, uint button, const QKeySequence &keySequence)
0248 {
0249     m_unsavedMappings["TabletTool"][deviceName][button] = keySequence;
0250     Q_EMIT settingsRestored();
0251 }
0252 
0253 QKeySequence Tablet::padButtonMapping(const QString &deviceName, uint button) const
0254 {
0255     if (deviceName.isEmpty()) {
0256         return {};
0257     }
0258 
0259     if (const auto &device = m_unsavedMappings["Tablet"][deviceName]; device.contains(button)) {
0260         return device.value(button);
0261     }
0262 
0263     const auto cfg = KSharedConfig::openConfig("kcminputrc");
0264     const auto group = cfg->group("ButtonRebinds").group("Tablet").group(deviceName);
0265     const auto sequence = group.readEntry(QString::number(button), QStringList());
0266     if (sequence.size() != 2) {
0267         return {};
0268     }
0269     return QKeySequence(sequence.constLast());
0270 }
0271 
0272 QKeySequence Tablet::toolButtonMapping(const QString &deviceName, uint button) const
0273 {
0274     if (deviceName.isEmpty()) {
0275         return {};
0276     }
0277 
0278     if (const auto &device = m_unsavedMappings["TabletTool"][deviceName]; device.contains(button)) {
0279         return device.value(button);
0280     }
0281 
0282     const auto cfg = KSharedConfig::openConfig("kcminputrc");
0283     const auto group = cfg->group("ButtonRebinds").group("TabletTool").group(deviceName);
0284     const auto sequence = group.readEntry(QString::number(button), QStringList());
0285     if (sequence.size() != 2) {
0286         return {};
0287     }
0288     return QKeySequence(sequence.constLast());
0289 }
0290 
0291 DevicesModel *Tablet::toolsModel() const
0292 {
0293     return m_toolsModel;
0294 }
0295 
0296 DevicesModel *Tablet::padsModel() const
0297 {
0298     return m_padsModel;
0299 }
0300 
0301 #include "kcmtablet.moc"