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"