File indexing completed on 2025-01-26 05:09:32

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 "tabletareaselectioncontroller.h"
0021 
0022 #include "calibrationdialog.h"
0023 #include "logging.h"
0024 #include "tabletareaselectionview.h"
0025 
0026 #include "screensinfo.h"
0027 #include "stringutils.h"
0028 #include "x11wacom.h"
0029 
0030 using namespace Wacom;
0031 
0032 namespace Wacom
0033 {
0034 class TabletAreaSelectionControllerPrivate
0035 {
0036 public:
0037     TabletAreaSelectionView *view = nullptr;
0038     TabletArea tabletGeometry; // the original tablet geometry
0039     TabletArea tabletGeometryRotated; // the rotated tablet geometry if rotation is active
0040     QMap<QString, QRect> screenGeometries; // the geometries of all screens which form the desktop
0041     ScreenSpace currentScreen;
0042     QString deviceName; // the device this instance is handling
0043     ScreenMap screenMap; // the current screen mappings
0044     ScreenRotation tabletRotation = ScreenRotation::NONE; // the tablet rotation
0045 };
0046 }
0047 
0048 TabletAreaSelectionController::TabletAreaSelectionController()
0049     : QObject()
0050     , d_ptr(new TabletAreaSelectionControllerPrivate)
0051 {
0052     // nothing to do except for initializing our private class
0053 }
0054 
0055 TabletAreaSelectionController::~TabletAreaSelectionController()
0056 {
0057     delete this->d_ptr;
0058 }
0059 
0060 const ScreenMap &TabletAreaSelectionController::getScreenMap()
0061 {
0062     Q_D(const TabletAreaSelectionController);
0063 
0064     // make sure the current mapping is included
0065     setMapping(d->currentScreen, d->view->getSelection());
0066 
0067     // return mapping
0068     return d->screenMap;
0069 }
0070 
0071 const ScreenSpace TabletAreaSelectionController::getScreenSpace() const
0072 {
0073     Q_D(const TabletAreaSelectionController);
0074 
0075     return d->currentScreen;
0076 }
0077 
0078 void TabletAreaSelectionController::select(const ScreenSpace &screenSpace)
0079 {
0080     Q_D(TabletAreaSelectionController);
0081 
0082     if (!hasView()) {
0083         return;
0084     }
0085 
0086     setMapping(d->currentScreen, d->view->getSelection());
0087 
0088     d->currentScreen = screenSpace;
0089     d->view->select(screenSpace.toString(), screenSpace.isDesktop(), getMapping(d->currentScreen));
0090 }
0091 
0092 void TabletAreaSelectionController::setView(TabletAreaSelectionView *view)
0093 {
0094     Q_D(TabletAreaSelectionController);
0095 
0096     // cleanup signal/slot connections if we already have a view
0097     if (d->view) {
0098         disconnect(d->view, SIGNAL(signalCalibrateClicked()), this, SLOT(onCalibrateClicked()));
0099         disconnect(d->view, SIGNAL(signalFullTabletSelection()), this, SLOT(onFullTabletSelected()));
0100         disconnect(d->view, SIGNAL(signalScreenToggle()), this, SLOT(onScreenToggle()));
0101         disconnect(d->view, SIGNAL(signalSetScreenProportions()), this, SLOT(onSetScreenProportions()));
0102         disconnect(d->view, SIGNAL(signalTabletAreaSelection()), this, SLOT(onTabletAreaSelected()));
0103     }
0104 
0105     // save view and connect signals
0106     d->view = view;
0107 
0108     if (view) {
0109         connect(view, SIGNAL(signalCalibrateClicked()), this, SLOT(onCalibrateClicked()));
0110         connect(view, SIGNAL(signalFullTabletSelection()), this, SLOT(onFullTabletSelected()));
0111         connect(view, SIGNAL(signalScreenToggle()), this, SLOT(onScreenToggle()));
0112         connect(view, SIGNAL(signalSetScreenProportions()), this, SLOT(onSetScreenProportions()));
0113         connect(view, SIGNAL(signalTabletAreaSelection()), this, SLOT(onTabletAreaSelected()));
0114     }
0115 }
0116 
0117 void TabletAreaSelectionController::setupController(const ScreenMap &mappings, const QString &deviceName, const ScreenRotation &rotation)
0118 {
0119     Q_D(TabletAreaSelectionController);
0120 
0121     if (!hasView()) {
0122         return;
0123     }
0124 
0125     d->deviceName = deviceName;
0126     d->tabletGeometry = X11Wacom::getMaximumTabletArea(deviceName);
0127     d->screenGeometries = ScreensInfo::getScreenGeometries();
0128     d->screenMap = mappings;
0129 
0130     if (rotation == ScreenRotation::AUTO) {
0131         // TODO
0132         d->tabletRotation = ScreensInfo::getScreenRotation();
0133         // we have a tablet (not a canvas) viewpoint here, so we need to invert the screen rotation
0134         d->tabletRotation = d->tabletRotation.invert();
0135 
0136     } else if (rotation == ScreenRotation::AUTO_INVERTED) {
0137         // TODO
0138         d->tabletRotation = ScreensInfo::getScreenRotation();
0139 
0140     } else {
0141         d->tabletRotation = rotation;
0142     }
0143 
0144     d->tabletGeometryRotated = d->tabletGeometry;
0145 
0146     if (d->tabletRotation == ScreenRotation::CW || d->tabletRotation == ScreenRotation::CCW) {
0147         d->tabletGeometryRotated.setWidth(d->tabletGeometry.height());
0148         d->tabletGeometryRotated.setHeight(d->tabletGeometry.width());
0149     }
0150 
0151     qCDebug(KCM) << "Calling setupScreens and setupTablet from setupController.  ScreenGeometries: " << d->screenGeometries;
0152     d->view->setupScreens(d->screenGeometries, QSize(200, 200));
0153     d->view->setupTablet(d->tabletGeometryRotated, QSize(400, 400));
0154 
0155     // make sure we have valid data set
0156     d->view->select(d->currentScreen.toString(), d->currentScreen.isDesktop(), getMapping(d->currentScreen));
0157 }
0158 
0159 void TabletAreaSelectionController::onCalibrateClicked()
0160 {
0161     Q_D(TabletAreaSelectionController);
0162 
0163     QScopedPointer<CalibrationDialog> calibDialog(new CalibrationDialog(d->deviceName, d->currentScreen.toString()));
0164     calibDialog->exec();
0165 
0166     setSelection(TabletArea(calibDialog->calibratedArea()));
0167 }
0168 
0169 void TabletAreaSelectionController::onFullTabletSelected()
0170 {
0171     checkConfigurationForTrackingModeProblems();
0172 }
0173 
0174 void TabletAreaSelectionController::onScreenToggle()
0175 {
0176     Q_D(TabletAreaSelectionController);
0177 
0178     select(d->currentScreen.next());
0179 }
0180 
0181 void TabletAreaSelectionController::onSetScreenProportions()
0182 {
0183     Q_D(TabletAreaSelectionController);
0184 
0185     QRect tabletGeometry = d->tabletGeometryRotated;
0186     QRect screenSelection = getScreenGeometry(d->currentScreen.toString());
0187 
0188     if (screenSelection.isEmpty()) {
0189         return;
0190     }
0191 
0192     // calculate new height and width of the selection
0193     qreal screenAreaSelectionRatio = (float)screenSelection.width() / screenSelection.height();
0194     qreal newWidth, newHeight;
0195 
0196     if (screenSelection.width() > screenSelection.height()) {
0197         newWidth = tabletGeometry.width();
0198         newHeight = newWidth / screenAreaSelectionRatio;
0199 
0200         if (newHeight > tabletGeometry.height()) {
0201             newHeight = tabletGeometry.height();
0202             newWidth = newHeight * screenAreaSelectionRatio;
0203         }
0204 
0205     } else {
0206         newHeight = tabletGeometry.height();
0207         newWidth = newHeight * screenAreaSelectionRatio;
0208 
0209         if (newWidth > tabletGeometry.width()) {
0210             newWidth = tabletGeometry.width();
0211             newHeight = newWidth / screenAreaSelectionRatio;
0212         }
0213     }
0214 
0215     // calculate x and y to center the selection
0216     int newX = tabletGeometry.x() + (int)((tabletGeometry.width() - newWidth) / 2.);
0217     int newY = tabletGeometry.y() + (int)((tabletGeometry.height() - newHeight) / 2.);
0218 
0219     setSelection(TabletArea(QRect(newX, newY, qRound(newWidth), qRound(newHeight))));
0220 }
0221 
0222 void TabletAreaSelectionController::onTabletAreaSelected()
0223 {
0224     checkConfigurationForTrackingModeProblems();
0225 }
0226 
0227 bool TabletAreaSelectionController::hasView() const
0228 {
0229     Q_D(const TabletAreaSelectionController);
0230 
0231     return (d->view != nullptr);
0232 }
0233 
0234 void TabletAreaSelectionController::checkConfigurationForTrackingModeProblems()
0235 {
0236     Q_D(TabletAreaSelectionController);
0237 
0238     // a device can not be mapped to a single screen in relative mode
0239     if (d->currentScreen.isMonitor()) {
0240         d->view->setTrackingModeWarning(true);
0241     } else {
0242         d->view->setTrackingModeWarning(false);
0243     }
0244 }
0245 
0246 const TabletArea TabletAreaSelectionController::convertAreaFromRotation(const TabletArea &tablet, const TabletArea &area, const ScreenRotation &rotation) const
0247 {
0248     TabletArea result = area;
0249 
0250     if (rotation == ScreenRotation::CW) {
0251         result.setX(area.y());
0252         result.setY(tablet.height() - area.x() - area.width());
0253         result.setWidth(area.height());
0254         result.setHeight(area.width());
0255 
0256     } else if (rotation == ScreenRotation::CCW) {
0257         result.setX(tablet.width() - area.y() - area.height());
0258         result.setY(area.x());
0259         result.setWidth(area.height());
0260         result.setHeight(area.width());
0261 
0262     } else if (rotation == ScreenRotation::HALF) {
0263         result.setX(tablet.width() - area.width() - area.x());
0264         result.setY(tablet.height() - area.height() - area.y());
0265         result.setWidth(area.width());
0266         result.setHeight(area.height());
0267     }
0268 
0269     return result;
0270 }
0271 
0272 const TabletArea TabletAreaSelectionController::convertAreaToRotation(const TabletArea &tablet, const TabletArea &area, const ScreenRotation &rotation) const
0273 {
0274     TabletArea result = area;
0275 
0276     if (rotation == ScreenRotation::CW) {
0277         result.setX(tablet.height() - area.height() - area.y());
0278         result.setY(area.x());
0279         result.setWidth(area.height());
0280         result.setHeight(area.width());
0281 
0282     } else if (rotation == ScreenRotation::CCW) {
0283         result.setX(area.y());
0284         result.setY(tablet.width() - area.width() - area.x());
0285         result.setWidth(area.height());
0286         result.setHeight(area.width());
0287 
0288     } else if (rotation == ScreenRotation::HALF) {
0289         result.setX(tablet.width() - area.width() - area.x());
0290         result.setY(tablet.height() - area.height() - area.y());
0291         result.setWidth(area.width());
0292         result.setHeight(area.height());
0293     }
0294 
0295     return result;
0296 }
0297 
0298 const QRect TabletAreaSelectionController::getScreenGeometry(QString output) const
0299 {
0300     Q_D(const TabletAreaSelectionController);
0301 
0302     return d->screenGeometries.value(output, ScreensInfo::getUnifiedDisplayGeometry());
0303 }
0304 
0305 const TabletArea TabletAreaSelectionController::getMapping(ScreenSpace screenSpace) const
0306 {
0307     Q_D(const TabletAreaSelectionController);
0308 
0309     return convertAreaToRotation(d->tabletGeometry, d->screenMap.getMapping(screenSpace), d->tabletRotation);
0310 }
0311 
0312 void TabletAreaSelectionController::setMapping(ScreenSpace screenSpace, const TabletArea &mapping)
0313 {
0314     Q_D(TabletAreaSelectionController);
0315 
0316     TabletArea area = convertAreaFromRotation(d->tabletGeometry, mapping, d->tabletRotation);
0317     d->screenMap.setMapping(screenSpace, area);
0318 }
0319 
0320 void TabletAreaSelectionController::setSelection(const TabletArea &selection)
0321 {
0322     Q_D(TabletAreaSelectionController);
0323 
0324     if (!hasView()) {
0325         return;
0326     }
0327 
0328     if (selection.isEmpty() || selection == d->tabletGeometryRotated) {
0329         d->view->selectFullTablet();
0330     } else {
0331         d->view->selectPartOfTablet(selection);
0332     }
0333 }
0334 
0335 #include "moc_tabletareaselectioncontroller.cpp"