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

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 "areaselectionwidget.h"
0021 
0022 #include <QList>
0023 #include <QRect>
0024 #include <QString>
0025 
0026 #include <QBrush>
0027 #include <QCursor>
0028 #include <QFontMetrics>
0029 #include <QMouseEvent>
0030 #include <QPainter>
0031 #include <QPen>
0032 
0033 using namespace Wacom;
0034 
0035 namespace Wacom
0036 {
0037 class AreaSelectionWidgetPrivate
0038 {
0039 public:
0040     AreaSelectionWidgetPrivate()
0041     {
0042         // set some reasonable default values
0043         dragMode = AreaSelectionWidget::DragMode::DragNone;
0044         widgetTargetSize = QSize(400, 400);
0045         outOfBoundsMargin = 0.;
0046         outOfBoundsVirtualAreaMargin = 0.;
0047         outOfBoundsDisplayAreaMargin = 0.;
0048         scaleFactor = .1; // prevent division by zero.
0049 
0050         drawAreaCaption = true;
0051         drawSelectionCaption = true;
0052 
0053         fontCaptions = QFont(QLatin1String("sans"), 10);
0054 
0055         // TODO: set colors based on UI theme
0056         colorDisplayAreaBrush = QColor(Qt::lightGray);
0057         colorDisplayAreaPen = QColor(Qt::black);
0058         colorDisplayAreaText = QColor(Qt::black);
0059 
0060         colorSelectedAreaBrush = QColor("#9EAEBF");
0061         colorSelectedAreaPen = QColor("#555E67");
0062         colorSelectedAreaText = QColor(colorSelectedAreaPen);
0063 
0064         colorDragHandles = QColor(colorSelectedAreaPen);
0065     }
0066 
0067     static const qreal DISPLAY_AREA_EXTRA_MARGIN; //!< An extra margin around the display area which is not taken into account for out of bounds calculations.
0068     static const qreal DRAG_HANDLE_SIZE; //!< The size of the drag handles.
0069 
0070     bool drawAreaCaption; //!< Determines if the caption of the display areas should be drawn.
0071     bool drawSelectionCaption; //!< Determines if the caption of the selected area should be drawn.
0072 
0073     QColor colorDisplayAreaPen; //!< The outline color of the display areas.
0074     QColor colorDisplayAreaBrush; //!< The fill colors of the display areas.
0075     QColor colorDisplayAreaText; //!< The text color of the display areas.
0076 
0077     QColor colorDragHandles; //!< The color used for the drag handles.
0078 
0079     QColor colorSelectedAreaPen; //!< The outline color of the selected area.
0080     QColor colorSelectedAreaBrush; //!< The fill color of the selected area.
0081     QColor colorSelectedAreaText; //!< The text color of the selected area.
0082 
0083     QFont fontCaptions; //!< The font used for all captions.
0084 
0085     AreaSelectionWidget::DragMode dragMode; //!< The current dragging mode if any.
0086     QPoint dragPoint; //!< The last mouse position while dragging the selected area.
0087 
0088     QSize widgetTargetSize; //!< The size of the widget we want, if possible.
0089 
0090     qreal outOfBoundsMargin; //!< The out of bounds margin as set by the user.
0091     qreal outOfBoundsVirtualAreaMargin; //!< The number of real pixels (or a percentage) the user may drag the selected area outside of the virtual area.
0092     qreal outOfBoundsDisplayAreaMargin; //!< The number of widget pixels which are calculated from the oob virtual area margin.
0093 
0094     qreal scaleFactor; //!< The scale factor which scales the virtual area's size to the widget's display area size.
0095 
0096     QMap<QString, QRect> areaRectsList; //!< The list of area rectangles which form the virtual area in real size.
0097     QStringList areaCaptionsList; //!< The list of captions for each area.
0098 
0099     QRect rectVirtualArea; //!< The rectangle which holds the virtual area in real size.
0100 
0101     QRectF rectDisplayArea; //!< The rectangle which holds the scaled virtual area displayed to the user.
0102     QList<QRectF> rectDisplayAreas; //!< The list of scaled sub-areas which make up the display area.
0103     QRectF rectSelectedArea; //!< The rectangle which holds the size and position of the selected display area.
0104 
0105     QRect rectDragHandleTop; //!< The rectangle which holds the size and position of the top drag handle.
0106     QRect rectDragHandleRight; //!< The rectangle which holds the size and position of the right drag handle.
0107     QRect rectDragHandleBottom; //!< The rectangle which holds the size and position of the bottom drag handle.
0108     QRect rectDragHandleLeft; //!< The rectangle which holds the size and position of the top drag handle.
0109 
0110     qreal proportions = 1;
0111     bool proportionsLocked = false;
0112 }; // PRIVATE CLASS
0113 
0114 const qreal AreaSelectionWidgetPrivate::DISPLAY_AREA_EXTRA_MARGIN = 5.;
0115 const qreal AreaSelectionWidgetPrivate::DRAG_HANDLE_SIZE = 6.;
0116 
0117 } // NAMESPACE
0118 
0119 AreaSelectionWidget::AreaSelectionWidget(QWidget *parent)
0120     : QWidget(parent)
0121     , d_ptr(new AreaSelectionWidgetPrivate)
0122 {
0123 }
0124 
0125 AreaSelectionWidget::~AreaSelectionWidget()
0126 {
0127     delete this->d_ptr;
0128 }
0129 
0130 void AreaSelectionWidget::clearSelection()
0131 {
0132     Q_D(const AreaSelectionWidget);
0133 
0134     setSelection(d->rectVirtualArea, true);
0135 }
0136 
0137 const QRect AreaSelectionWidget::getSelection() const
0138 {
0139     Q_D(const AreaSelectionWidget);
0140 
0141     return calculateUnscaledArea(d->rectSelectedArea, d->scaleFactor, getTotalDisplayAreaMargin());
0142 }
0143 
0144 const QString AreaSelectionWidget::getSelectionAsString() const
0145 {
0146     QRect area = getSelection();
0147     return QString::fromLatin1("%1 %2 %3 %4").arg(area.x()).arg(area.y()).arg(area.width()).arg(area.height());
0148 }
0149 
0150 const QRect &AreaSelectionWidget::getVirtualArea() const
0151 {
0152     Q_D(const AreaSelectionWidget);
0153 
0154     return d->rectVirtualArea;
0155 }
0156 
0157 void AreaSelectionWidget::setArea(const QRect &area, const QString &caption)
0158 {
0159     QMap<QString, QRect> areaList;
0160     QStringList captionList;
0161 
0162     areaList[caption] = area;
0163     captionList.append(caption);
0164 
0165     setAreas(areaList, captionList);
0166 }
0167 
0168 void AreaSelectionWidget::setAreas(const QMap<QString, QRect> &areas, const QStringList &areaCaptions)
0169 {
0170     Q_D(AreaSelectionWidget);
0171 
0172     d->areaRectsList = areas;
0173     d->areaCaptionsList = areaCaptions;
0174     setupWidget();
0175 }
0176 
0177 void AreaSelectionWidget::setDrawAreaCaptions(bool value)
0178 {
0179     Q_D(AreaSelectionWidget);
0180 
0181     d->drawAreaCaption = value;
0182 }
0183 
0184 void AreaSelectionWidget::setDrawSelectionCaption(bool value)
0185 {
0186     Q_D(AreaSelectionWidget);
0187 
0188     d->drawSelectionCaption = value;
0189 }
0190 
0191 void AreaSelectionWidget::setFont(const QFont &font)
0192 {
0193     Q_D(AreaSelectionWidget);
0194 
0195     d->fontCaptions = font;
0196 }
0197 
0198 void AreaSelectionWidget::setOutOfBoundsMargin(qreal margin)
0199 {
0200     Q_D(AreaSelectionWidget);
0201 
0202     if (margin < 0.) {
0203         return;
0204     }
0205 
0206     d->outOfBoundsMargin = margin;
0207     setupWidget();
0208 }
0209 
0210 void AreaSelectionWidget::setSelection(const QRect &selection, bool emitUpdate)
0211 {
0212     Q_D(AreaSelectionWidget);
0213 
0214     // we can not select anything if we do not have areas to select from
0215     if (d->areaRectsList.isEmpty()) {
0216         return;
0217     }
0218 
0219     // if every value is -1 then the user wants to set the selection to the maximum
0220     QRect newSelection = selection;
0221 
0222     if (!selection.isValid() || (selection.x() == -1 && selection.y() == -1 && selection.width() == -1 && selection.height() == -1)) {
0223         newSelection = d->rectVirtualArea;
0224     }
0225 
0226     // update selection and repaint widget
0227     d->rectSelectedArea = calculateScaledArea(newSelection, d->scaleFactor, getTotalDisplayAreaMargin());
0228     if (d->proportionsLocked) {
0229         lockProportions(true);
0230     }
0231 
0232     updateSelectedAreaSize();
0233     updateDragHandles();
0234 
0235     QWidget::update();
0236 
0237     if (emitUpdate) {
0238         emit selectionChanged();
0239     }
0240 }
0241 
0242 void AreaSelectionWidget::setSelection(QString output)
0243 {
0244     Q_D(const AreaSelectionWidget);
0245 
0246     const auto areaRect = d->areaRectsList.find(output);
0247 
0248     if (areaRect == d->areaRectsList.constEnd()) {
0249         return;
0250     }
0251 
0252     setSelection(*areaRect, true);
0253 }
0254 
0255 void AreaSelectionWidget::setWidgetTargetSize(const QSize &size)
0256 {
0257     Q_D(AreaSelectionWidget);
0258 
0259     if (size.height() < 0 || size.width() < 0) {
0260         return;
0261     }
0262 
0263     d->widgetTargetSize = size;
0264     setupWidget();
0265 }
0266 
0267 void AreaSelectionWidget::lockProportions(bool enable)
0268 {
0269     Q_D(AreaSelectionWidget);
0270 
0271     d->proportionsLocked = enable;
0272     if (enable && d->rectSelectedArea.height() > 0) {
0273         d->proportions = d->rectSelectedArea.width() / d->rectSelectedArea.height();
0274     }
0275 }
0276 
0277 void AreaSelectionWidget::mouseMoveEvent(QMouseEvent *event)
0278 {
0279     Q_D(AreaSelectionWidget);
0280 
0281     // do nothing if we do not have areas set
0282     if (d->areaRectsList.isEmpty()) {
0283         return;
0284     }
0285 
0286     updateMouseCursor(event->pos());
0287     updateSelectedAreaOnDrag(event->pos());
0288     updateDragHandles();
0289 
0290     QWidget::update();
0291 }
0292 
0293 void AreaSelectionWidget::mousePressEvent(QMouseEvent *event)
0294 {
0295     Q_D(AreaSelectionWidget);
0296 
0297     // do nothing if we do not have areas set or if the user is already dragging
0298     if (d->areaRectsList.isEmpty() || isUserDragging()) {
0299         return;
0300     }
0301 
0302     // determine what the user wants to drag - a handle or the whole selected area
0303     const QPoint mousePosition(event->pos());
0304 
0305     if (d->rectDragHandleTop.contains(mousePosition)) {
0306         d->dragMode = AreaSelectionWidget::DragMode::DragTopHandle;
0307 
0308     } else if (d->rectDragHandleRight.contains(mousePosition)) {
0309         d->dragMode = AreaSelectionWidget::DragMode::DragRightHandle;
0310 
0311     } else if (d->rectDragHandleBottom.contains(mousePosition)) {
0312         d->dragMode = AreaSelectionWidget::DragMode::DragBottomHandle;
0313 
0314     } else if (d->rectDragHandleLeft.contains(mousePosition)) {
0315         d->dragMode = AreaSelectionWidget::DragMode::DragLeftHandle;
0316 
0317     } else if (d->rectSelectedArea.contains(mousePosition)) {
0318         d->dragMode = AreaSelectionWidget::DragMode::DragSelectedArea;
0319         d->dragPoint = mousePosition;
0320 
0321         QWidget::setCursor(Qt::SizeAllCursor);
0322 
0323     } else {
0324         // the user did not click anything that is dragable
0325         d->dragMode = AreaSelectionWidget::DragMode::DragNone;
0326     }
0327 }
0328 
0329 void AreaSelectionWidget::mouseReleaseEvent(QMouseEvent *event)
0330 {
0331     Q_D(AreaSelectionWidget);
0332     Q_UNUSED(event);
0333 
0334     // do nothing if we do not have areas set
0335     if (d->areaRectsList.isEmpty()) {
0336         return;
0337     }
0338 
0339     // if the user was dragging something, he is no longer now
0340     if (isUserDragging()) {
0341         d->dragMode = AreaSelectionWidget::DragMode::DragNone;
0342         QWidget::setCursor(Qt::ArrowCursor);
0343         emit selectionChanged();
0344     }
0345 }
0346 
0347 void AreaSelectionWidget::paintEvent(QPaintEvent *event)
0348 {
0349     Q_D(AreaSelectionWidget);
0350     Q_UNUSED(event);
0351 
0352     // draw nothing if we do not have areas set
0353     if (d->areaRectsList.isEmpty()) {
0354         QWidget::paintEvent(event);
0355         return;
0356     }
0357 
0358     QPainter painter(this);
0359     painter.setRenderHint(QPainter::Antialiasing);
0360 
0361     paintDisplayAreas(painter, false);
0362     paintSelectedArea(painter, false);
0363     paintDisplayAreas(painter, true);
0364 
0365     if (QWidget::isEnabled()) {
0366         paintDragHandles(painter);
0367     }
0368 
0369     if (d->drawAreaCaption) {
0370         paintDisplayAreaCaptions(painter);
0371     }
0372 
0373     if (d->drawSelectionCaption) {
0374         paintSelectedAreaCaption(painter);
0375     }
0376 }
0377 
0378 const QRectF AreaSelectionWidget::calculateDisplayArea(const QRect &virtualArea, qreal scaleFactor, qreal totalDisplayAreaMargin) const
0379 {
0380     QRectF displayArea;
0381 
0382     displayArea.setX(totalDisplayAreaMargin);
0383     displayArea.setY(totalDisplayAreaMargin);
0384     displayArea.setWidth(virtualArea.width() * scaleFactor);
0385     displayArea.setHeight(virtualArea.height() * scaleFactor);
0386 
0387     return displayArea;
0388 }
0389 
0390 const QList<QRectF> AreaSelectionWidget::calculateDisplayAreas(const QMap<QString, QRect> areas, qreal scaleFactor, qreal totalDisplayAreaMargin) const
0391 {
0392     QList<QRectF> displayAreas;
0393     QRectF displayArea;
0394 
0395     foreach (QRect area, areas.values()) {
0396         displayArea = calculateScaledArea(area, scaleFactor, totalDisplayAreaMargin);
0397         displayAreas.append(displayArea);
0398     }
0399 
0400     return displayAreas;
0401 }
0402 
0403 qreal AreaSelectionWidget::calculateOutOfBoundsVirtualAreaMargin(const QRect &virtualArea, qreal outOfBoundsMargin) const
0404 {
0405     if (!virtualArea.isValid() || outOfBoundsMargin < 0.) {
0406         return 0.;
0407     }
0408 
0409     qreal margin = outOfBoundsMargin;
0410 
0411     if (outOfBoundsMargin <= 1.) {
0412         // out of bounds margin is a percentage - use the longer side of the virtual area as baseline
0413         if (virtualArea.width() > virtualArea.height()) {
0414             margin = outOfBoundsMargin * virtualArea.width();
0415         } else {
0416             margin = outOfBoundsMargin * virtualArea.height();
0417         }
0418     }
0419 
0420     return margin;
0421 }
0422 
0423 qreal AreaSelectionWidget::calculateScaleFactor(const QSize &targetSize,
0424                                                 const QRect &virtualArea,
0425                                                 qreal virtualAreaOutOfBoundsMargin,
0426                                                 qreal displayAreaExtraMargin) const
0427 {
0428     qreal scaleFactor = 0.1; // default is 10% to prevent a division by zero
0429 
0430     if (!virtualArea.isValid() || virtualArea.width() <= 0 || virtualArea.height() <= 0) {
0431         return scaleFactor;
0432     }
0433 
0434     if (virtualArea.width() > virtualArea.height()) {
0435         scaleFactor = (targetSize.width() - 2. * displayAreaExtraMargin) / (virtualArea.width() + 2. * virtualAreaOutOfBoundsMargin);
0436 
0437     } else {
0438         scaleFactor = (targetSize.height() - 2. * displayAreaExtraMargin) / (virtualArea.height() + 2. * virtualAreaOutOfBoundsMargin);
0439     }
0440 
0441     return scaleFactor;
0442 }
0443 
0444 const QRectF AreaSelectionWidget::calculateScaledArea(const QRect &area, qreal scaleFactor, qreal totalDisplayAreaMargin) const
0445 {
0446     QRectF scaledArea;
0447 
0448     scaledArea.setX(area.x() * scaleFactor + totalDisplayAreaMargin);
0449     scaledArea.setY(area.y() * scaleFactor + totalDisplayAreaMargin);
0450     scaledArea.setWidth(area.width() * scaleFactor);
0451     scaledArea.setHeight(area.height() * scaleFactor);
0452 
0453     return scaledArea;
0454 }
0455 
0456 const QRect AreaSelectionWidget::calculateUnscaledArea(const QRectF &area, qreal scaleFactor, qreal totalDisplayAreaMargin) const
0457 {
0458     QRect unscaledArea;
0459 
0460     unscaledArea.setX(qRound((area.x() - totalDisplayAreaMargin) / scaleFactor));
0461     unscaledArea.setY(qRound((area.y() - totalDisplayAreaMargin) / scaleFactor));
0462     unscaledArea.setWidth(qRound(area.width() / scaleFactor));
0463     unscaledArea.setHeight(qRound(area.height() / scaleFactor));
0464 
0465     return unscaledArea;
0466 }
0467 
0468 const QRect AreaSelectionWidget::calculateVirtualArea(const QMap<QString, QRect> &areas) const
0469 {
0470     QRect virtualArea;
0471 
0472     for (const auto &area : areas) {
0473         virtualArea = virtualArea.united(area);
0474     }
0475 
0476     return virtualArea;
0477 }
0478 
0479 qreal AreaSelectionWidget::getTotalDisplayAreaMargin() const
0480 {
0481     Q_D(const AreaSelectionWidget);
0482 
0483     return (d->outOfBoundsDisplayAreaMargin + d->DISPLAY_AREA_EXTRA_MARGIN);
0484 }
0485 
0486 bool AreaSelectionWidget::isUserDragging() const
0487 {
0488     Q_D(const AreaSelectionWidget);
0489 
0490     return (d->dragMode != AreaSelectionWidget::DragMode::DragNone);
0491 }
0492 
0493 void AreaSelectionWidget::paintDisplayAreaCaptions(QPainter &painter)
0494 {
0495     Q_D(AreaSelectionWidget);
0496 
0497     QRectF area;
0498     QString caption;
0499     qreal captionX;
0500     qreal captionY;
0501     QFontMetrics fontMetrics(d->fontCaptions);
0502 
0503     painter.setPen(d->colorDisplayAreaText);
0504     painter.setBrush(d->colorDisplayAreaText);
0505     painter.setFont(d->fontCaptions);
0506 
0507     for (int i = 0; i < d->rectDisplayAreas.size(); ++i) {
0508         area = d->rectDisplayAreas.at(i);
0509         caption = (d->areaCaptionsList.size() > i) ? d->areaCaptionsList.at(i) : QString();
0510 
0511         if (!caption.isEmpty() && area.isValid()) {
0512             captionX = area.x() + (float)area.width() / 2 - (float)fontMetrics.horizontalAdvance(caption) / 2;
0513             captionY = area.y() + (float)area.height() / 2 + (float)fontMetrics.height() / 2;
0514 
0515             painter.drawText(captionX, captionY, caption);
0516         }
0517     }
0518 }
0519 
0520 void AreaSelectionWidget::paintDisplayAreas(QPainter &painter, bool outlineOnly)
0521 {
0522     Q_D(AreaSelectionWidget);
0523 
0524     // paint the whole display area
0525     painter.setPen(d->colorDisplayAreaPen);
0526     painter.setBrush(outlineOnly ? Qt::transparent : d->colorDisplayAreaBrush);
0527 
0528     if (d->rectDisplayAreas.size() > 1) {
0529         painter.drawRect(d->rectDisplayArea);
0530     }
0531 
0532     // paint the display sub areas and captions
0533     QRectF area;
0534 
0535     for (int i = 0; i < d->rectDisplayAreas.size(); ++i) {
0536         area = d->rectDisplayAreas.at(i);
0537 
0538         if (area.isValid()) {
0539             painter.drawRect(area);
0540         }
0541     }
0542 }
0543 
0544 void AreaSelectionWidget::paintDragHandles(QPainter &painter)
0545 {
0546     Q_D(AreaSelectionWidget);
0547 
0548     QColor color("#326583");
0549 
0550     painter.setPen(d->colorSelectedAreaPen);
0551     painter.setBrush(d->colorSelectedAreaPen);
0552 
0553     painter.drawRect(d->rectDragHandleTop);
0554     painter.drawRect(d->rectDragHandleRight);
0555     painter.drawRect(d->rectDragHandleBottom);
0556     painter.drawRect(d->rectDragHandleLeft);
0557 }
0558 
0559 void AreaSelectionWidget::paintSelectedArea(QPainter &painter, bool outlineOnly)
0560 {
0561     Q_D(AreaSelectionWidget);
0562 
0563     painter.setPen(d->colorSelectedAreaPen);
0564     painter.setBrush(outlineOnly ? Qt::transparent : d->colorSelectedAreaBrush);
0565     painter.drawRect(d->rectSelectedArea);
0566 }
0567 
0568 void AreaSelectionWidget::paintSelectedAreaCaption(QPainter &painter)
0569 {
0570     Q_D(AreaSelectionWidget);
0571 
0572     QFontMetrics fontMetrics(d->fontCaptions);
0573 
0574     painter.setPen(d->colorSelectedAreaText);
0575     painter.setBrush(d->colorSelectedAreaText);
0576     painter.setFont(d->fontCaptions);
0577 
0578     QRect selectedArea = getSelection();
0579 
0580     QString text = QString::fromLatin1("%1x%2+%3+%4").arg(selectedArea.width()).arg(selectedArea.height()).arg(selectedArea.x()).arg(selectedArea.y());
0581 
0582     qreal textX = d->rectDisplayArea.x() + (qreal)d->rectDisplayArea.width() / 2 - (qreal)fontMetrics.horizontalAdvance(text) / 2;
0583     qreal textY;
0584 
0585     if (paintBelow) {
0586         // Draw text below the display area
0587         textY = d->rectDisplayArea.y() + d->rectDisplayArea.height() + ((qreal)fontMetrics.height());
0588     } else {
0589         // Draw in the middle of the display area
0590         textY = d->rectDisplayArea.y() + d->rectDisplayArea.height() / 2 + (qreal)fontMetrics.height() / 2;
0591 
0592         // If we are also showing area captions, then the vertical center will
0593         // already be occupied. We move our text a bit further down then.
0594         if (d->drawAreaCaption) {
0595             textY = textY + fontMetrics.height();
0596         }
0597     }
0598 
0599     painter.drawText(qRound(textX), qRound(textY), text);
0600 }
0601 
0602 void AreaSelectionWidget::setupWidget()
0603 {
0604     Q_D(AreaSelectionWidget);
0605 
0606     // do nothing if we do not have areas
0607     if (d->areaRectsList.isEmpty()) {
0608         return;
0609     }
0610 
0611     // calculate transformation data
0612     d->rectVirtualArea = calculateVirtualArea(d->areaRectsList);
0613     d->outOfBoundsVirtualAreaMargin = calculateOutOfBoundsVirtualAreaMargin(d->rectVirtualArea, d->outOfBoundsMargin);
0614     d->scaleFactor = calculateScaleFactor(!d->widgetTargetSize.isEmpty() ? d->widgetTargetSize : size(),
0615                                           d->rectVirtualArea,
0616                                           d->outOfBoundsVirtualAreaMargin,
0617                                           d->DISPLAY_AREA_EXTRA_MARGIN);
0618     d->outOfBoundsDisplayAreaMargin = d->outOfBoundsVirtualAreaMargin * d->scaleFactor;
0619     d->rectDisplayArea = calculateDisplayArea(d->rectVirtualArea, d->scaleFactor, getTotalDisplayAreaMargin());
0620     d->rectDisplayAreas = calculateDisplayAreas(d->areaRectsList, d->scaleFactor, getTotalDisplayAreaMargin());
0621 
0622     // setup widget
0623     QWidget::setMouseTracking(true);
0624 
0625     qreal widgetWidth = d->rectDisplayArea.width() + 2. * getTotalDisplayAreaMargin();
0626     qreal widgetHeight = d->rectDisplayArea.height() + 2. * getTotalDisplayAreaMargin();
0627 
0628     QWidget::setMinimumSize(widgetWidth, widgetHeight);
0629     QWidget::setMaximumSize(widgetWidth, widgetHeight);
0630 
0631     // set selected area to the full display area
0632     d->rectSelectedArea = d->rectDisplayArea;
0633 
0634     // recalculate drag handles positions
0635     updateDragHandles();
0636 }
0637 
0638 void AreaSelectionWidget::updateDragHandles()
0639 {
0640     Q_D(AreaSelectionWidget);
0641 
0642     // the size of the drag handles
0643     static const qreal handleSize = d->DRAG_HANDLE_SIZE;
0644     static const qreal handleSizeHalf = handleSize / 2.;
0645 
0646     // some convenience vars so we have to type less
0647     const qreal areaX = d->rectSelectedArea.x();
0648     const qreal areaY = d->rectSelectedArea.y();
0649     const qreal areaW = d->rectSelectedArea.width();
0650     const qreal areaWHalf = areaW / 2.;
0651     const qreal areaH = d->rectSelectedArea.height();
0652     const qreal areaHHalf = areaH / 2.;
0653 
0654     d->rectDragHandleTop.setX(areaX + areaWHalf - handleSizeHalf);
0655     d->rectDragHandleTop.setY(areaY - handleSizeHalf);
0656     d->rectDragHandleTop.setWidth(handleSize);
0657     d->rectDragHandleTop.setHeight(handleSize);
0658 
0659     d->rectDragHandleRight.setX(areaX + areaW - handleSizeHalf);
0660     d->rectDragHandleRight.setY(areaY + areaHHalf - handleSizeHalf);
0661     d->rectDragHandleRight.setWidth(handleSize);
0662     d->rectDragHandleRight.setHeight(handleSize);
0663 
0664     d->rectDragHandleBottom.setX(areaX + areaWHalf - handleSizeHalf);
0665     d->rectDragHandleBottom.setY(areaY + areaH - handleSizeHalf);
0666     d->rectDragHandleBottom.setWidth(handleSize);
0667     d->rectDragHandleBottom.setHeight(handleSize);
0668 
0669     d->rectDragHandleLeft.setX(areaX - handleSizeHalf);
0670     d->rectDragHandleLeft.setY(areaY + areaHHalf - handleSizeHalf);
0671     d->rectDragHandleLeft.setWidth(handleSize);
0672     d->rectDragHandleLeft.setHeight(handleSize);
0673 }
0674 
0675 void AreaSelectionWidget::updateMouseCursor(const QPoint &mousePosition)
0676 {
0677     Q_D(AreaSelectionWidget);
0678 
0679     // only update the cursor if the user is not dragging a handle.
0680     if (isUserDragging()) {
0681         return;
0682     }
0683 
0684     if (d->rectDragHandleLeft.contains(mousePosition) || d->rectDragHandleRight.contains(mousePosition)) {
0685         QWidget::setCursor(Qt::SizeHorCursor);
0686 
0687     } else if (d->rectDragHandleTop.contains(mousePosition) || d->rectDragHandleBottom.contains(mousePosition)) {
0688         QWidget::setCursor(Qt::SizeVerCursor);
0689 
0690     } else {
0691         QWidget::setCursor(Qt::ArrowCursor);
0692     }
0693 }
0694 
0695 void AreaSelectionWidget::updateSelectedAreaOnDrag(const QPoint &mousePosition)
0696 {
0697     Q_D(AreaSelectionWidget);
0698 
0699     switch (d->dragMode) {
0700     case DragMode::DragNone:
0701         break;
0702 
0703     case AreaSelectionWidget::DragMode::DragTopHandle:
0704         updateSelectedAreaOnDragTop(mousePosition);
0705         break;
0706 
0707     case AreaSelectionWidget::DragMode::DragRightHandle:
0708         updateSelectedAreaOnDragRight(mousePosition);
0709         break;
0710 
0711     case AreaSelectionWidget::DragMode::DragBottomHandle:
0712         updateSelectedAreaOnDragBottom(mousePosition);
0713         break;
0714 
0715     case AreaSelectionWidget::DragMode::DragLeftHandle:
0716         updateSelectedAreaOnDragLeft(mousePosition);
0717         break;
0718 
0719     case AreaSelectionWidget::DragMode::DragSelectedArea:
0720         updateSelectedAreaOnDragArea(mousePosition);
0721         break;
0722     }
0723 }
0724 
0725 void AreaSelectionWidget::updateSelectedAreaOnDragArea(const QPoint &mousePosition)
0726 {
0727     Q_D(AreaSelectionWidget);
0728 
0729     const qreal dragXOffset = mousePosition.x() - d->dragPoint.x();
0730     const qreal dragYOffset = mousePosition.y() - d->dragPoint.y();
0731 
0732     const qreal leftBound = d->rectDisplayArea.x() - d->outOfBoundsDisplayAreaMargin;
0733     const qreal rightBound = d->rectDisplayArea.x() + d->rectDisplayArea.width() + d->outOfBoundsDisplayAreaMargin - d->rectSelectedArea.width();
0734     const qreal topBound = d->rectDisplayArea.y() - d->outOfBoundsDisplayAreaMargin;
0735     const qreal bottomBound = d->rectDisplayArea.y() + d->rectDisplayArea.height() + d->outOfBoundsDisplayAreaMargin - d->rectSelectedArea.height();
0736 
0737     const qreal oldW = d->rectSelectedArea.width();
0738     const qreal oldH = d->rectSelectedArea.height();
0739     const qreal oldX = d->rectSelectedArea.x();
0740     const qreal oldY = d->rectSelectedArea.y();
0741     qreal newX = oldX + dragXOffset;
0742     qreal newY = oldY + dragYOffset;
0743 
0744     // use old values if the new ones are out of bounds
0745     if (newX < leftBound || rightBound < newX) {
0746         newX = oldX;
0747     }
0748 
0749     if (newY < topBound || bottomBound < newY) {
0750         newY = oldY;
0751     }
0752 
0753     // update selected area
0754     d->rectSelectedArea.setX(newX);
0755     d->rectSelectedArea.setY(newY);
0756     d->rectSelectedArea.setWidth(oldW);
0757     d->rectSelectedArea.setHeight(oldH);
0758 
0759     // update last drag point
0760     d->dragPoint = mousePosition;
0761 
0762     updateSelectedAreaSize(false);
0763 }
0764 
0765 void AreaSelectionWidget::updateSelectedAreaOnDragBottom(const QPoint &mousePosition)
0766 {
0767     Q_D(AreaSelectionWidget);
0768 
0769     // the drag handle size in the bounds calculations ensure that the
0770     // box does not get too small to reach the drag handles with the mouse
0771     const qreal topBound = d->rectSelectedArea.y() + d->DRAG_HANDLE_SIZE;
0772     const qreal bottomBound = d->rectDisplayArea.y() + d->rectDisplayArea.height() + d->outOfBoundsDisplayAreaMargin;
0773     const qreal mouseY = mousePosition.y();
0774     qreal newHeight = 0.;
0775 
0776     if (mouseY < topBound) {
0777         newHeight = topBound - d->rectSelectedArea.y();
0778     } else if (mouseY > bottomBound) {
0779         newHeight = bottomBound - d->rectSelectedArea.y();
0780     } else {
0781         newHeight = mouseY - d->rectSelectedArea.y();
0782     }
0783 
0784     if (d->proportionsLocked) {
0785         const auto newWidth = newHeight * d->proportions;
0786         if (newWidth < topBound || newWidth > bottomBound)
0787             return;
0788         d->rectSelectedArea.setWidth(newWidth);
0789     }
0790     d->rectSelectedArea.setHeight(newHeight);
0791 
0792     updateSelectedAreaSize(true);
0793 }
0794 
0795 void AreaSelectionWidget::updateSelectedAreaOnDragLeft(const QPoint &mousePosition)
0796 {
0797     Q_D(AreaSelectionWidget);
0798 
0799     // the drag handle size in the bounds calculations ensure that the
0800     // box does not get too small to reach the drag handles with the mouse
0801     const qreal leftBound = d->rectDisplayArea.x() - d->outOfBoundsDisplayAreaMargin;
0802     const qreal rightBound = d->rectSelectedArea.x() + d->rectSelectedArea.width() - d->DRAG_HANDLE_SIZE;
0803     const qreal mouseX = mousePosition.x();
0804     qreal newX = 0.;
0805 
0806     if (mouseX < leftBound) {
0807         newX = leftBound;
0808     } else if (mouseX > rightBound) {
0809         newX = rightBound;
0810     } else {
0811         newX = mouseX;
0812     }
0813 
0814     if (d->proportionsLocked) {
0815         const auto newY = d->rectSelectedArea.y() + (newX - d->rectSelectedArea.x()) / d->proportions;
0816         if (newY < leftBound || newY > rightBound)
0817             return;
0818         d->rectSelectedArea.setY(newY);
0819     }
0820     d->rectSelectedArea.setX(newX);
0821 
0822     updateSelectedAreaSize(false);
0823 }
0824 
0825 void AreaSelectionWidget::updateSelectedAreaOnDragRight(const QPoint &mousePosition)
0826 {
0827     Q_D(AreaSelectionWidget);
0828 
0829     // the drag handle size in the bounds calculations ensure that the
0830     // box does not get too small to reach the drag handles with the mouse
0831     const qreal leftBound = d->rectSelectedArea.x() + d->DRAG_HANDLE_SIZE;
0832     const qreal rightBound = d->rectDisplayArea.x() + d->rectDisplayArea.width() + d->outOfBoundsDisplayAreaMargin;
0833     const qreal mouseX = mousePosition.x();
0834     qreal newWidth = 0.;
0835 
0836     if (mouseX < leftBound) {
0837         newWidth = leftBound - d->rectSelectedArea.x();
0838     } else if (mouseX > rightBound) {
0839         newWidth = rightBound - d->rectSelectedArea.x();
0840     } else {
0841         newWidth = mouseX - d->rectSelectedArea.x();
0842     }
0843 
0844     if (d->proportionsLocked) {
0845         const auto newHeight = newWidth / d->proportions;
0846         if (newHeight < leftBound || newHeight > rightBound)
0847             return;
0848         d->rectSelectedArea.setHeight(newWidth / d->proportions);
0849     }
0850     d->rectSelectedArea.setWidth(newWidth);
0851 
0852     updateSelectedAreaSize(true);
0853 }
0854 
0855 void AreaSelectionWidget::updateSelectedAreaOnDragTop(const QPoint &mousePosition)
0856 {
0857     Q_D(AreaSelectionWidget);
0858 
0859     // the drag handle size in the bounds calculations ensure that the
0860     // box does not get too small to reach the drag handles with the mouse
0861     const qreal topBound = d->rectDisplayArea.y() - d->outOfBoundsDisplayAreaMargin;
0862     const qreal bottomBound = d->rectSelectedArea.y() + d->rectSelectedArea.height() - d->DRAG_HANDLE_SIZE;
0863     const qreal mouseY = mousePosition.y();
0864     qreal newY = 0.;
0865 
0866     if (mouseY < topBound) {
0867         newY = topBound;
0868     } else if (mouseY > bottomBound) {
0869         newY = bottomBound;
0870     } else {
0871         newY = mouseY;
0872     }
0873 
0874     if (d->proportionsLocked) {
0875         const auto newX = d->rectSelectedArea.x() + (newY - d->rectSelectedArea.y()) * d->proportions;
0876         if (newX < topBound || newX > bottomBound)
0877             return;
0878         d->rectSelectedArea.setX(newX);
0879     }
0880     d->rectSelectedArea.setY(newY);
0881 
0882     updateSelectedAreaSize(false);
0883 }
0884 
0885 void AreaSelectionWidget::updateSelectedAreaSize(bool fixPositionInsteadOfSize)
0886 {
0887     Q_D(AreaSelectionWidget);
0888 
0889     // the selected area might be too large but
0890     // we expect it to be within valid bounds
0891 
0892     qreal selectedWidth = d->rectSelectedArea.width();
0893     qreal selectedHeight = d->rectSelectedArea.height();
0894     qreal displayWidth = d->rectDisplayArea.width();
0895     qreal displayHeight = d->rectDisplayArea.height();
0896 
0897     if (selectedWidth > displayWidth) {
0898         if (fixPositionInsteadOfSize) {
0899             qreal newX = d->rectSelectedArea.x() + selectedWidth - displayWidth;
0900             d->rectSelectedArea.setX(newX);
0901 
0902         } else {
0903             d->rectSelectedArea.setWidth(displayWidth);
0904         }
0905     }
0906 
0907     if (selectedHeight > displayHeight) {
0908         if (fixPositionInsteadOfSize) {
0909             qreal newY = d->rectSelectedArea.y() + selectedHeight - displayHeight;
0910             d->rectSelectedArea.setY(newY);
0911 
0912         } else {
0913             d->rectSelectedArea.setHeight(displayHeight);
0914         }
0915     }
0916 }
0917 
0918 #include "moc_areaselectionwidget.cpp"