File indexing completed on 2025-01-26 05:09:31
0001 /* 0002 * This file is part of the KDE wacomtablet project. For copyright 0003 * information and license terms see the AUTHORS and COPYING files 0004 * in the top-level directory of this distribution. 0005 * 0006 * This program is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU General Public License as 0008 * published by the Free Software Foundation; either version 2 of 0009 * the License, or (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "kcmwacomtabletwidget.h" 0021 0022 #include "ui_errorwidget.h" 0023 #include "ui_kcmwacomtabletwidget.h" 0024 0025 #include "logging.h" 0026 0027 #include "buttonpagewidget.h" 0028 #include "generalpagewidget.h" 0029 #include "profilemanagement.h" 0030 #include "styluspagewidget.h" 0031 #include "tabletpagewidget.h" 0032 #include "touchpagewidget.h" 0033 0034 // common 0035 #include "dbustabletinterface.h" 0036 #include "devicetype.h" 0037 0038 #include <KMessageBox> 0039 0040 // Qt includes 0041 #include <QDBusReply> 0042 #include <QDialogButtonBox> 0043 #include <QInputDialog> 0044 #include <QLineEdit> 0045 #include <QMessageBox> 0046 #include <QPixmap> 0047 #include <QPointer> 0048 #include <QScrollArea> 0049 #include <QStringList> 0050 #include <kwidgetsaddons_version.h> 0051 0052 using namespace Wacom; 0053 0054 namespace Wacom 0055 { 0056 /** 0057 * Private class for the d-pointer. 0058 */ 0059 class KCMWacomTabletWidgetPrivate 0060 { 0061 public: 0062 Ui::KCMWacomTabletWidget ui; //!< This user interface. 0063 0064 GeneralPageWidget generalPage; //!< Widget that shows some basic information about the tablet. 0065 StylusPageWidget stylusPage; //!< Widget for the pen settings (stylus/eraser). 0066 ButtonPageWidget buttonPage; //!< Widget for the express button settings. 0067 TabletPageWidget tabletPage; //!< Widget for the tablet settings. 0068 TouchPageWidget touchPage; //!< Widget for the touch settings. 0069 QWidget deviceErrorWidget; //!< Device error widget. 0070 Ui::ErrorWidget deviceErrorUi; //!< Device error widget ui. 0071 bool profileChanged; //!< True if the profile was changed and not saved yet. 0072 }; // CLASS 0073 } // NAMESPACE 0074 0075 void makeScrollableTab(QTabWidget *parent, QWidget &tab, const QString &title) 0076 { 0077 auto scrollableTab = new QScrollArea(parent); 0078 scrollableTab->setWidget(&tab); 0079 scrollableTab->setWidgetResizable(true); 0080 scrollableTab->setFrameShadow(QFrame::Shadow::Plain); 0081 parent->addTab(scrollableTab, title); 0082 } 0083 0084 KCMWacomTabletWidget::KCMWacomTabletWidget(QWidget *parent) 0085 : QWidget(parent) 0086 , d_ptr(new KCMWacomTabletWidgetPrivate) 0087 { 0088 setupUi(); 0089 loadTabletInformation(); 0090 showHideConfig(); 0091 } 0092 0093 KCMWacomTabletWidget::~KCMWacomTabletWidget() 0094 { 0095 delete this->d_ptr; 0096 } 0097 0098 void KCMWacomTabletWidget::setupUi() 0099 { 0100 Q_D(KCMWacomTabletWidget); 0101 0102 DBusTabletInterface *dbusTabletInterface = &DBusTabletInterface::instance(); 0103 0104 if (!dbusTabletInterface->isValid()) { 0105 qCWarning(KCM) << "DBus interface not available"; 0106 } 0107 0108 d->profileChanged = false; 0109 0110 // setup error widget 0111 d->deviceErrorUi.setupUi(&(d->deviceErrorWidget)); 0112 d->deviceErrorUi.errorImage->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); 0113 connect(d->deviceErrorUi.buttonRunTabletFinder, &QCommandLinkButton::clicked, this, &KCMWacomTabletWidget::showTabletFinder); 0114 d->deviceErrorUi.buttonRunTabletFinder->setVisible(false); 0115 0116 // setup normal ui 0117 d->ui.setupUi(this); 0118 d->ui.addProfileButton->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); 0119 d->ui.delProfileButton->setIcon(QIcon::fromTheme(QLatin1String("edit-delete-page"))); 0120 0121 // connect tablet selector 0122 connect(d->ui.tabletListSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KCMWacomTabletWidget::onTabletSelectionChanged); 0123 0124 // connect profile selector 0125 connect(d->ui.addProfileButton, SIGNAL(clicked(bool)), SLOT(addProfile())); 0126 connect(d->ui.delProfileButton, SIGNAL(clicked(bool)), SLOT(delProfile())); 0127 connect(d->ui.profileSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, [this, d](int index) { 0128 switchProfile(d->ui.profileSelector->itemText(index)); 0129 }); 0130 0131 // connect configuration tabs 0132 connect(&(d->generalPage), SIGNAL(changed()), SLOT(profileChanged())); 0133 connect(&(d->stylusPage), SIGNAL(changed()), SLOT(profileChanged())); 0134 connect(&(d->buttonPage), SIGNAL(changed()), SLOT(profileChanged())); 0135 connect(&(d->tabletPage), SIGNAL(changed()), SLOT(profileChanged())); 0136 connect(&(d->touchPage), SIGNAL(changed()), SLOT(profileChanged())); 0137 0138 // connect rotation handling 0139 connect(&(d->tabletPage), SIGNAL(rotationChanged(ScreenRotation)), &(d->touchPage), SLOT(onRotationChanged(ScreenRotation))); 0140 0141 // connect DBus signals 0142 connect(dbusTabletInterface, SIGNAL(tabletAdded(QString)), SLOT(onTabletAdded(QString))); 0143 connect(dbusTabletInterface, SIGNAL(tabletRemoved(QString)), SLOT(onTabletRemoved(QString))); 0144 } 0145 0146 void KCMWacomTabletWidget::loadTabletInformation() 0147 { 0148 Q_D(KCMWacomTabletWidget); 0149 QDBusReply<QStringList> connectedTablets = DBusTabletInterface::instance().getTabletList(); 0150 0151 if (!connectedTablets.isValid()) { 0152 return; 0153 } 0154 0155 d->ui.tabletListSelector->blockSignals(true); 0156 foreach (const QString &tabletId, connectedTablets.value()) { 0157 addTabletToSelector(tabletId); 0158 } 0159 d->ui.tabletListSelector->blockSignals(false); 0160 } 0161 0162 void KCMWacomTabletWidget::showHideConfig() 0163 { 0164 // request this to see if dbus works and tablets are connected 0165 QDBusReply<QStringList> connectedTablets = DBusTabletInterface::instance().getTabletList(); 0166 0167 if (!connectedTablets.isValid()) { 0168 QString errorTitle = i18n("KDE tablet service not found"); 0169 QString errorMsg = i18n( 0170 "Please start the KDE wacom tablet service to use this configuration dialog.\n" 0171 "The service is required for tablet detection and profile support."); 0172 showError(errorTitle, errorMsg); 0173 } else if (connectedTablets.value().count() == 0) { 0174 QString errorTitle = i18n("No tablet device detected"); 0175 QString errorMsg = i18n( 0176 "Please connect a tablet device to continue.\n" 0177 "If your device is already connected, it is currently not in the device database."); 0178 showError(errorTitle, errorMsg, true); 0179 } else { 0180 showConfig(); 0181 } 0182 } 0183 0184 void KCMWacomTabletWidget::onTabletAdded(const QString &tabletId) 0185 { 0186 addTabletToSelector(tabletId); 0187 } 0188 0189 void KCMWacomTabletWidget::onTabletRemoved(const QString &tabletId) 0190 { 0191 Q_D(KCMWacomTabletWidget); 0192 0193 int index = d->ui.tabletListSelector->findData(tabletId); 0194 0195 if (index >= 0) { 0196 d->ui.tabletListSelector->removeItem(index); 0197 } 0198 } 0199 0200 void KCMWacomTabletWidget::onTabletSelectionChanged() 0201 { 0202 Q_D(KCMWacomTabletWidget); 0203 0204 showSaveChanges(); 0205 0206 // tell all widgets to operate on a different tablet now 0207 QString tabletId = d->ui.tabletListSelector->itemData(d->ui.tabletListSelector->currentIndex()).toString(); 0208 d->generalPage.setTabletId(tabletId); 0209 d->stylusPage.setTabletId(tabletId); 0210 d->buttonPage.setTabletId(tabletId); 0211 d->tabletPage.setTabletId(tabletId); 0212 d->touchPage.setTabletId(tabletId); 0213 0214 showHideConfig(); 0215 } 0216 0217 void KCMWacomTabletWidget::addProfile() 0218 { 0219 bool ok; 0220 QString text = QInputDialog::getText(this, i18n("Add new profile"), i18n("Profile name:"), QLineEdit::Normal, QString(), &ok); 0221 if (!ok || text.isEmpty()) { 0222 return; 0223 } 0224 0225 ProfileManagement::instance().createNewProfile(text); 0226 refreshProfileSelector(text); 0227 switchProfile(text); 0228 } 0229 0230 void KCMWacomTabletWidget::delProfile() 0231 { 0232 Q_D(KCMWacomTabletWidget); 0233 0234 ProfileManagement::instance().deleteProfile(); 0235 refreshProfileSelector(); 0236 switchProfile(d->ui.profileSelector->currentText()); 0237 0238 // update profile rotation selection 0239 d->generalPage.reloadWidget(); 0240 } 0241 0242 void KCMWacomTabletWidget::saveProfile() 0243 { 0244 Q_D(KCMWacomTabletWidget); 0245 0246 auto &profileManagement = ProfileManagement::instance(); 0247 0248 d->generalPage.saveToProfile(); 0249 d->stylusPage.saveToProfile(profileManagement); 0250 d->buttonPage.saveToProfile(profileManagement); 0251 d->tabletPage.saveToProfile(profileManagement); 0252 d->touchPage.saveToProfile(profileManagement); 0253 0254 d->profileChanged = false; 0255 emit changed(false); 0256 0257 applyProfile(); 0258 } 0259 0260 void KCMWacomTabletWidget::switchProfile(const QString &profile) 0261 { 0262 showSaveChanges(); 0263 0264 ProfileManagement::instance().setProfileName(profile); 0265 0266 reloadProfile(); 0267 applyProfile(); 0268 } 0269 0270 void KCMWacomTabletWidget::reloadProfile() 0271 { 0272 Q_D(KCMWacomTabletWidget); 0273 0274 auto &profileManagement = ProfileManagement::instance(); 0275 0276 d->generalPage.loadFromProfile(); 0277 d->stylusPage.loadFromProfile(profileManagement); 0278 d->buttonPage.loadFromProfile(profileManagement); 0279 d->tabletPage.loadFromProfile(profileManagement); 0280 d->touchPage.loadFromProfile(profileManagement); 0281 0282 d->profileChanged = false; 0283 emit changed(false); 0284 } 0285 0286 void KCMWacomTabletWidget::applyProfile() 0287 { 0288 Q_D(KCMWacomTabletWidget); 0289 0290 QString tabletId = d->ui.tabletListSelector->itemData(d->ui.tabletListSelector->currentIndex()).toString(); 0291 DBusTabletInterface::instance().setProfile(tabletId, ProfileManagement::instance().profileName()); 0292 } 0293 0294 void KCMWacomTabletWidget::profileChanged() 0295 { 0296 Q_D(KCMWacomTabletWidget); 0297 0298 d->profileChanged = true; 0299 emit changed(true); 0300 } 0301 0302 void KCMWacomTabletWidget::showError(const QString &errorTitle, const QString &errorMsg, bool showTabletFinderButton) 0303 { 0304 Q_D(KCMWacomTabletWidget); 0305 0306 hideError(); 0307 hideConfig(); 0308 0309 d->deviceErrorUi.errorTitle->setText(errorTitle); 0310 d->deviceErrorUi.errorText->setText(errorMsg); 0311 d->ui.verticalLayout->addWidget(&(d->deviceErrorWidget)); 0312 d->deviceErrorWidget.setVisible(true); 0313 d->deviceErrorUi.buttonRunTabletFinder->setVisible(showTabletFinderButton); 0314 } 0315 0316 void KCMWacomTabletWidget::hideConfig() 0317 { 0318 Q_D(KCMWacomTabletWidget); 0319 0320 d->ui.tabletListSelector->setVisible(false); 0321 d->ui.tabletListLabel->setVisible(false); 0322 d->ui.profileSelector->setVisible(false); 0323 d->ui.profileLabel->setVisible(false); 0324 d->ui.addProfileButton->setVisible(false); 0325 d->ui.delProfileButton->setVisible(false); 0326 0327 d->ui.tabletListSelector->setEnabled(false); 0328 d->ui.profileSelector->setEnabled(false); 0329 d->ui.addProfileButton->setEnabled(false); 0330 d->ui.delProfileButton->setEnabled(false); 0331 d->ui.deviceTabWidget->setVisible(false); 0332 } 0333 0334 void KCMWacomTabletWidget::hideError() 0335 { 0336 Q_D(KCMWacomTabletWidget); 0337 0338 d->deviceErrorWidget.setVisible(false); 0339 d->ui.verticalLayout->removeWidget(&(d->deviceErrorWidget)); 0340 } 0341 0342 bool KCMWacomTabletWidget::refreshProfileSelector(const QString &profile) 0343 { 0344 Q_D(KCMWacomTabletWidget); 0345 0346 int index = -1; 0347 QStringList profiles = ProfileManagement::instance().availableProfiles(); 0348 0349 d->ui.profileSelector->blockSignals(true); 0350 d->ui.profileSelector->clear(); 0351 d->ui.profileSelector->addItems(profiles); 0352 0353 if (!profile.isEmpty()) { 0354 index = d->ui.profileSelector->findText(profile); 0355 d->ui.profileSelector->setCurrentIndex(index); 0356 0357 } else if (!profiles.isEmpty()) { 0358 index = 0; 0359 d->ui.profileSelector->setCurrentIndex(index); 0360 } 0361 0362 d->ui.profileSelector->blockSignals(false); 0363 0364 return (index >= 0); 0365 } 0366 0367 void KCMWacomTabletWidget::showConfig() 0368 { 0369 Q_D(KCMWacomTabletWidget); 0370 0371 // make sure no error message is active 0372 hideError(); 0373 0374 // reload profile and widget data 0375 QString tabletId = d->ui.tabletListSelector->itemData(d->ui.tabletListSelector->currentIndex()).toString(); 0376 ProfileManagement::instance().setTabletId(tabletId); 0377 ProfileManagement::instance().reload(); 0378 0379 d->generalPage.setTabletId(tabletId); 0380 d->stylusPage.setTabletId(tabletId); 0381 d->buttonPage.setTabletId(tabletId); 0382 d->tabletPage.setTabletId(tabletId); 0383 0384 QDBusReply<QString> touchDeviceName = DBusTabletInterface::instance().getDeviceName(tabletId, DeviceType::Touch.key()); 0385 QDBusReply<QString> touchSensorId = DBusTabletInterface::instance().getTouchSensorId(tabletId); 0386 0387 const bool hasBuiltInTouch = (touchDeviceName.isValid() && !touchDeviceName.value().isEmpty()); 0388 const bool hasPairedTouch = (touchSensorId.isValid() && !touchSensorId.value().isEmpty()); 0389 0390 if (hasPairedTouch) { 0391 d->touchPage.setTabletId(touchSensorId.value()); 0392 } else { 0393 d->touchPage.setTabletId(tabletId); 0394 } 0395 0396 d->generalPage.reloadWidget(); 0397 d->stylusPage.reloadWidget(); 0398 d->buttonPage.reloadWidget(); 0399 d->tabletPage.reloadWidget(); 0400 d->touchPage.reloadWidget(); 0401 0402 // show tablet Selector 0403 d->ui.tabletListSelector->setEnabled(true); 0404 d->ui.tabletListLabel->setVisible(true); 0405 d->ui.tabletListSelector->setVisible(true); 0406 0407 // initialize profile selector 0408 d->ui.profileSelector->setEnabled(true); 0409 d->ui.addProfileButton->setEnabled(true); 0410 d->ui.delProfileButton->setEnabled(true); 0411 d->ui.profileLabel->setVisible(true); 0412 d->ui.profileSelector->setVisible(true); 0413 d->ui.addProfileButton->setVisible(true); 0414 d->ui.delProfileButton->setVisible(true); 0415 0416 if (ProfileManagement::instance().availableProfiles().isEmpty()) { 0417 ProfileManagement::instance().createNewProfile(i18nc("Name of the default profile that will be created if none exist.", "Default")); 0418 applyProfile(); 0419 } 0420 0421 refreshProfileSelector(); 0422 0423 // initialize configuration tabs 0424 d->ui.deviceTabWidget->clear(); 0425 makeScrollableTab(d->ui.deviceTabWidget, d->generalPage, i18nc("Basic overview page for the tablet hardware", "General")); 0426 makeScrollableTab(d->ui.deviceTabWidget, d->stylusPage, i18n("Stylus")); 0427 0428 QDBusReply<bool> hasPadButtons = DBusTabletInterface::instance().hasPadButtons(tabletId); 0429 0430 if (hasPadButtons.isValid() && hasPadButtons.value()) { 0431 makeScrollableTab(d->ui.deviceTabWidget, d->buttonPage, i18n("Express Buttons")); 0432 } 0433 0434 makeScrollableTab(d->ui.deviceTabWidget, d->tabletPage, i18n("Tablet")); 0435 0436 if (hasBuiltInTouch || hasPairedTouch) { 0437 makeScrollableTab(d->ui.deviceTabWidget, d->touchPage, i18n("Touch")); 0438 } 0439 0440 d->ui.deviceTabWidget->setEnabled(true); 0441 d->ui.deviceTabWidget->setVisible(true); 0442 0443 // switch to the currently active profile 0444 QDBusReply<QString> profile = DBusTabletInterface::instance().getProfile(tabletId); 0445 if (profile.isValid()) { 0446 d->ui.profileSelector->setCurrentText(profile); 0447 switchProfile(profile); 0448 } 0449 } 0450 0451 void KCMWacomTabletWidget::showSaveChanges() 0452 { 0453 Q_D(KCMWacomTabletWidget); 0454 0455 if (!d->profileChanged) { 0456 return; 0457 } 0458 0459 // TODO: This should be a proper Yes/No/Cancel dialog 0460 // but this probably requires custom ComboBoxes for canceling selection 0461 if (KMessageBox::questionTwoActions(this, 0462 i18n("Save changes to the current profile?"), 0463 i18n("Save Profile"), 0464 KStandardGuiItem::save(), 0465 KStandardGuiItem::discard()) 0466 == KMessageBox::ButtonCode::PrimaryAction) { 0467 saveProfile(); 0468 } 0469 } 0470 0471 void KCMWacomTabletWidget::showTabletFinder() 0472 { 0473 const bool success = QProcess::startDetached(QStringLiteral("kde_wacom_tabletfinder"), QStringList()); 0474 0475 if (!success) { 0476 QString err = i18n("Failed to launch Wacom tablet finder tool. Check your installation."); 0477 QMessageBox::warning(QApplication::activeWindow(), QApplication::applicationName(), err); 0478 } 0479 } 0480 0481 void KCMWacomTabletWidget::addTabletToSelector(const QString &tabletId) 0482 { 0483 Q_D(KCMWacomTabletWidget); 0484 0485 QDBusReply<QString> deviceName = DBusTabletInterface::instance().getInformation(tabletId, TabletInfo::TabletName.key()); 0486 QDBusReply<QStringList> inputDevices = DBusTabletInterface::instance().getDeviceList(tabletId); 0487 QDBusReply<bool> isTouchSensor = DBusTabletInterface::instance().isTouchSensor(tabletId); 0488 if (isTouchSensor.isValid() && isTouchSensor.value()) { 0489 qCDebug(KCM) << "Ignoring tablet" << deviceName << tabletId << "because it's a touch sensor"; 0490 return; 0491 } 0492 0493 qCDebug(KCM) << "Adding tablet" << deviceName << tabletId << "with" << inputDevices.value(); 0494 0495 d->ui.tabletListSelector->addItem(QString::fromLatin1("%1 [%2]").arg(deviceName).arg(tabletId), tabletId); 0496 } 0497 0498 #include "moc_kcmwacomtabletwidget.cpp"