File indexing completed on 2022-11-29 20:22:33

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2007 Luca Gugelmann <lucag@student.ethz.ch>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-only
0005  */
0006 
0007 #include "regiongrabber.h"
0008 
0009 #include <QApplication>
0010 #include <QDesktopWidget>
0011 #include <QLocale>
0012 #include <QToolTip>
0013 #include <QtGui/QMouseEvent>
0014 #include <QtGui/QPainter>
0015 #include <QScreen>
0016 
0017 #include <KLocalizedString>
0018 #include <KWindowSystem>
0019 
0020 RegionGrabber::RegionGrabber()
0021     : QWidget(nullptr)
0022     , selection()
0023     , mouseDown(false)
0024     , newSelection(false)
0025     , handleSize(10)
0026     , mouseOverHandle(nullptr)
0027     , idleTimer()
0028     , showHelp(true)
0029     , grabbing(false)
0030     , TLHandle(0, 0, handleSize, handleSize)
0031     , TRHandle(0, 0, handleSize, handleSize)
0032     , BLHandle(0, 0, handleSize, handleSize)
0033     , BRHandle(0, 0, handleSize, handleSize)
0034     , LHandle(0, 0, handleSize, handleSize)
0035     , THandle(0, 0, handleSize, handleSize)
0036     , RHandle(0, 0, handleSize, handleSize)
0037     , BHandle(0, 0, handleSize, handleSize)
0038 {
0039     handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle << &LHandle << &THandle << &RHandle << &BHandle;
0040     setMouseTracking(true);
0041     setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
0042     int timeout = KWindowSystem::compositingActive() ? 200 : 50;
0043     QTimer::singleShot(timeout, this, SLOT(init()));
0044     connect(&idleTimer, &QTimer::timeout, this, &RegionGrabber::displayHelp);
0045     idleTimer.start(3000);
0046 }
0047 
0048 RegionGrabber::~RegionGrabber()
0049 {
0050 }
0051 
0052 void RegionGrabber::init()
0053 {
0054     pixmap = QApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId());
0055     showFullScreen(); // krazy:exclude=qmethods        -- Necessary for proper screenshot capture.
0056     resize(pixmap.size());
0057     move(0, 0);
0058     setCursor(Qt::CrossCursor);
0059     grabKeyboard();
0060 }
0061 
0062 void RegionGrabber::displayHelp()
0063 {
0064     showHelp = true;
0065     update();
0066 }
0067 
0068 void RegionGrabber::paintEvent(QPaintEvent *e)
0069 {
0070     Q_UNUSED(e);
0071     if (grabbing) // grabWindow() should just get the background
0072         return;
0073 
0074     QPainter painter(this);
0075 
0076     QPalette pal(QToolTip::palette());
0077     QFont font = QToolTip::font();
0078 
0079     QColor handleColor = pal.color(QPalette::Active, QPalette::Highlight);
0080     handleColor.setAlpha(160);
0081     QColor overlayColor(0, 0, 0, 160);
0082     QColor textColor = pal.color(QPalette::Active, QPalette::Text);
0083     QColor textBackgroundColor = pal.color(QPalette::Active, QPalette::Base);
0084     painter.drawPixmap(0, 0, pixmap);
0085     painter.setFont(font);
0086 
0087     QRect r = selection.normalized().adjusted(0, 0, -1, -1);
0088     if (!selection.isNull()) {
0089         QRegion grey(rect());
0090         grey = grey.subtracted(r);
0091         painter.setPen(handleColor);
0092         painter.setBrush(overlayColor);
0093         painter.setClipRegion(grey);
0094         painter.drawRect(-1, -1, rect().width() + 1, rect().height() + 1);
0095         painter.setClipRect(rect());
0096         painter.setBrush(Qt::NoBrush);
0097         painter.drawRect(r);
0098     }
0099 
0100     if (showHelp) {
0101         painter.setPen(textColor);
0102         painter.setBrush(textBackgroundColor);
0103         QString helpText = i18n("Select a region using the mouse. To take the snapshot, press the Enter key. Press Esc to quit.");
0104         QRect textRect = painter.boundingRect(rect().adjusted(2, 2, -2, -2), Qt::TextWordWrap, helpText);
0105         textRect.adjust(-2, -2, 4, 2);
0106         painter.drawRect(textRect);
0107         textRect.moveTopLeft(QPoint(3, 3));
0108         painter.drawText(textRect, helpText);
0109     }
0110 
0111     if (selection.isNull()) {
0112         return;
0113     }
0114 
0115     // The grabbed region is everything which is covered by the drawn
0116     // rectangles (border included). This means that there is no 0px
0117     // selection, since a 0px wide rectangle will always be drawn as a line.
0118     QString txt = QString("%1x%2").arg(selection.width() == 0 ? 2 : selection.width()).arg(selection.height() == 0 ? 2 : selection.height());
0119     QRect textRect = painter.boundingRect(rect(), Qt::AlignLeft, txt);
0120     QRect boundingRect = textRect.adjusted(-4, 0, 0, 0);
0121 
0122     if (textRect.width() < r.width() - 2 * handleSize && textRect.height() < r.height() - 2 * handleSize && (r.width() > 100 && r.height() > 100)) { // center, unsuitable for small selections
0123         boundingRect.moveCenter(r.center());
0124         textRect.moveCenter(r.center());
0125     } else if (r.y() - 3 > textRect.height() && r.x() + textRect.width() < rect().right()) { // on top, left aligned
0126         boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3));
0127         textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3));
0128     } else if (r.x() - 3 > textRect.width()) { // left, top aligned
0129         boundingRect.moveTopRight(QPoint(r.x() - 3, r.y()));
0130         textRect.moveTopRight(QPoint(r.x() - 5, r.y()));
0131     } else if (r.bottom() + 3 + textRect.height() < rect().bottom() && r.right() > textRect.width()) { // at bottom, right aligned
0132         boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3));
0133         textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3));
0134     } else if (r.right() + textRect.width() + 3 < rect().width()) { // right, bottom aligned
0135         boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom()));
0136         textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom()));
0137     }
0138     // if the above didn't catch it, you are running on a very tiny screen...
0139     painter.setPen(textColor);
0140     painter.setBrush(textBackgroundColor);
0141     painter.drawRect(boundingRect);
0142     painter.drawText(textRect, txt);
0143 
0144     if ((r.height() > handleSize * 2 && r.width() > handleSize * 2) || !mouseDown) {
0145         updateHandles();
0146         painter.setPen(handleColor);
0147         handleColor.setAlpha(60);
0148         painter.setBrush(handleColor);
0149         painter.drawRects(handleMask().begin(),handleMask().rectCount());
0150     }
0151 }
0152 
0153 void RegionGrabber::resizeEvent(QResizeEvent *e)
0154 {
0155     Q_UNUSED(e);
0156     if (selection.isNull())
0157         return;
0158     QRect r = selection;
0159     r.setTopLeft(limitPointToRect(r.topLeft(), rect()));
0160     r.setBottomRight(limitPointToRect(r.bottomRight(), rect()));
0161     if (r.width() <= 1 || r.height() <= 1) // this just results in ugly drawing...
0162         r = QRect();
0163     selection = r;
0164 }
0165 
0166 void RegionGrabber::mousePressEvent(QMouseEvent *e)
0167 {
0168     showHelp = false;
0169     idleTimer.stop();
0170     if (e->button() == Qt::LeftButton) {
0171         mouseDown = true;
0172         dragStartPoint = e->pos();
0173         selectionBeforeDrag = selection;
0174         if (!selection.contains(e->pos())) {
0175             newSelection = true;
0176             selection = QRect();
0177             showHelp = true;
0178         } else {
0179             setCursor(Qt::ClosedHandCursor);
0180         }
0181     } else if (e->button() == Qt::RightButton) {
0182         newSelection = false;
0183         selection = QRect();
0184         setCursor(Qt::CrossCursor);
0185     }
0186     update();
0187 }
0188 
0189 void RegionGrabber::mouseMoveEvent(QMouseEvent *e)
0190 {
0191     if (mouseDown) {
0192         if (newSelection) {
0193             QPoint p = e->pos();
0194             QRect r = rect();
0195             selection = QRect(dragStartPoint, limitPointToRect(p, r)).normalized();
0196         } else if (mouseOverHandle == nullptr) { // moving the whole selection
0197             QRect r = rect().normalized(), s = selectionBeforeDrag.normalized();
0198             QPoint p = s.topLeft() + e->pos() - dragStartPoint;
0199             r.setBottomRight(r.bottomRight() - QPoint(s.width(), s.height()));
0200             if (!r.isNull() && r.isValid())
0201                 selection.moveTo(limitPointToRect(p, r));
0202         } else { // dragging a handle
0203             QRect r = selectionBeforeDrag;
0204             QPoint offset = e->pos() - dragStartPoint;
0205 
0206             if (mouseOverHandle == &TLHandle || mouseOverHandle == &THandle || mouseOverHandle == &TRHandle) { // dragging one of the top handles
0207                 r.setTop(r.top() + offset.y());
0208             }
0209 
0210             if (mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle || mouseOverHandle == &BLHandle) { // dragging one of the left handles
0211                 r.setLeft(r.left() + offset.x());
0212             }
0213 
0214             if (mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle || mouseOverHandle == &BRHandle) { // dragging one of the bottom handles
0215                 r.setBottom(r.bottom() + offset.y());
0216             }
0217 
0218             if (mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle || mouseOverHandle == &BRHandle) { // dragging one of the right handles
0219                 r.setRight(r.right() + offset.x());
0220             }
0221             r = r.normalized();
0222             r.setTopLeft(limitPointToRect(r.topLeft(), rect()));
0223             r.setBottomRight(limitPointToRect(r.bottomRight(), rect()));
0224             selection = r;
0225         }
0226         update();
0227     } else {
0228         if (selection.isNull())
0229             return;
0230         bool found = false;
0231         Q_FOREACH (QRect *r, handles) {
0232             if (r->contains(e->pos())) {
0233                 mouseOverHandle = r;
0234                 found = true;
0235                 break;
0236             }
0237         }
0238         if (!found) {
0239             mouseOverHandle = nullptr;
0240             if (selection.contains(e->pos()))
0241                 setCursor(Qt::OpenHandCursor);
0242             else
0243                 setCursor(Qt::CrossCursor);
0244         } else {
0245             if (mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle)
0246                 setCursor(Qt::SizeFDiagCursor);
0247             if (mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle)
0248                 setCursor(Qt::SizeBDiagCursor);
0249             if (mouseOverHandle == &LHandle || mouseOverHandle == &RHandle)
0250                 setCursor(Qt::SizeHorCursor);
0251             if (mouseOverHandle == &THandle || mouseOverHandle == &BHandle)
0252                 setCursor(Qt::SizeVerCursor);
0253         }
0254     }
0255 }
0256 
0257 void RegionGrabber::mouseReleaseEvent(QMouseEvent *e)
0258 {
0259     mouseDown = false;
0260     newSelection = false;
0261     idleTimer.start();
0262     if (mouseOverHandle == nullptr && selection.contains(e->pos()))
0263         setCursor(Qt::OpenHandCursor);
0264     update();
0265 }
0266 
0267 void RegionGrabber::mouseDoubleClickEvent(QMouseEvent *)
0268 {
0269     grabRect();
0270 }
0271 
0272 void RegionGrabber::keyPressEvent(QKeyEvent *e)
0273 {
0274     if (e->key() == Qt::Key_Escape) {
0275         Q_EMIT regionGrabbed(QPixmap());
0276     } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
0277         grabRect();
0278     } else {
0279         e->ignore();
0280     }
0281 }
0282 
0283 void RegionGrabber::grabRect()
0284 {
0285     QRect r = selection.normalized();
0286     if (!r.isNull() && r.isValid()) {
0287         grabbing = true;
0288         Q_EMIT regionGrabbed(pixmap.copy(r));
0289     }
0290 }
0291 
0292 void RegionGrabber::updateHandles()
0293 {
0294     QRect r = selection.normalized().adjusted(0, 0, -1, -1);
0295     int s2 = handleSize / 2;
0296 
0297     TLHandle.moveTopLeft(r.topLeft());
0298     TRHandle.moveTopRight(r.topRight());
0299     BLHandle.moveBottomLeft(r.bottomLeft());
0300     BRHandle.moveBottomRight(r.bottomRight());
0301 
0302     LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() / 2 - s2));
0303     THandle.moveTopLeft(QPoint(r.x() + r.width() / 2 - s2, r.y()));
0304     RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() / 2 - s2));
0305     BHandle.moveBottomLeft(QPoint(r.x() + r.width() / 2 - s2, r.bottom()));
0306 }
0307 
0308 QRegion RegionGrabber::handleMask() const
0309 {
0310     // note: not normalized QRects are bad here, since they will not be drawn
0311     QRegion mask;
0312     Q_FOREACH (QRect *rect, handles)
0313         mask += QRegion(*rect);
0314     return mask;
0315 }
0316 
0317 QPoint RegionGrabber::limitPointToRect(const QPoint &p, const QRect &r) const
0318 {
0319     QPoint q;
0320     q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right());
0321     q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom());
0322     return q;
0323 }