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"