diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,7 +25,6 @@ Gui/SettingsDialog/SaveOptionsPage.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp QuickEditor/QuickEditor.cpp - QuickEditor/QmlResources.qrc ) if(XCB_FOUND) diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp --- a/src/Gui/ExportMenu.cpp +++ b/src/Gui/ExportMenu.cpp @@ -82,7 +82,7 @@ Q_FOREACH (auto service, services) { QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&")); - QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, this); + QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, nullptr); connect(action, &QAction::triggered, [=]() { QList whereIs({ mExportManager->tempSave() }); diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -59,8 +59,8 @@ mClipboardButton(new QToolButton), mSaveButton(new QToolButton), mSaveMenu(new QMenu), - mSaveAsAction(new QAction(this)), - mSaveAction(new QAction(this)), + mSaveAsAction(new QAction(nullptr)), + mSaveAction(new QAction(nullptr)), mMessageWidget(new KMessageWidget), mToolsMenu(new QMenu), mScreenRecorderToolsMenu(new QMenu), diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h --- a/src/PlatformBackends/X11ImageGrabber.h +++ b/src/PlatformBackends/X11ImageGrabber.h @@ -65,7 +65,7 @@ private slots: void KWinDBusScreenshotHelper(quint64 window); - void rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion); + void rectangleSelectionConfirmed(const QPixmap &pixmap); void rectangleSelectionCancelled(); public slots: diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -417,17 +417,13 @@ emit imageGrabFailed(); } -void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion) +void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap) { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); - if (mCapturePointer) { - mPixmap = blendCursorImage(pixmap, region.x(), region.y(), region.width(), region.height()); - } else { - mPixmap = pixmap; - } + mPixmap = pixmap; emit pixmapChanged(mPixmap); } @@ -665,7 +661,7 @@ void X11ImageGrabber::grabRectangularRegion() { - QuickEditor *editor = new QuickEditor(getToplevelPixmap(QRect(), false)); + QuickEditor *editor = new QuickEditor(getToplevelPixmap(QRect(), mCapturePointer)); connect(editor, &QuickEditor::grabDone, this, &X11ImageGrabber::rectangleSelectionConfirmed); connect(editor, &QuickEditor::grabCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); diff --git a/src/QuickEditor/EditorRoot.qml b/src/QuickEditor/EditorRoot.qml deleted file mode 100644 --- a/src/QuickEditor/EditorRoot.qml +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2016 Boudhayan Gupta - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.5 -import QtQuick.Window 2.2 -import QtQuick.Layouts 1.3 -import QtQuick.Controls 1.4 - -Item { - id: editorRoot; - objectName: "editorRoot"; - - // properties and setters - - property var selection: undefined; - property color maskColour: Qt.rgba(0, 0, 0, 0.15); - property color strokeColour: systemPalette.highlight; - property color crossColour: Qt.rgba(strokeColour.r, strokeColour.g, strokeColour.b, 0.7); - property color labelBackgroundColour: Qt.rgba(systemPalette.light.r, systemPalette.light.g, systemPalette.light.b, 0.85); - property bool showMagnifier: false; - property bool toggleMagnifier: false; - property int magZoom: 5; - property int magPixels: 16; - property int magOffset: 32; - - SystemPalette { - id: systemPalette; - } - - function setInitialSelection(xx, yy, ww, hh) { - if (selection) { - selection.destroy(); - } - - selection = cropRectangle.createObject(parent, { - "x": xx, - "y": yy, - "height": hh, - "width": ww - }); - - cropDisplayCanvas.requestPaint(); - } - - function accept() { - if (selection) { - acceptImage(selection.x, selection.y, selection.width, selection.height); - } else { - acceptImage(-1, -1, -1, -1); - } - } - - // key handlers - - focus: true; - - Keys.onReturnPressed: accept() - Keys.onEnterPressed: accept() - - Keys.onEscapePressed: { - cancelImage(); - } - - Keys.onPressed: { - if (event.modifiers & Qt.ShiftModifier) { - toggleMagnifier = true; - cropDisplayCanvas.requestPaint(); - } - } - - Keys.onReleased: { - if (toggleMagnifier && !(event.modifiers & Qt.ShiftModifier)) { - toggleMagnifier = false; - cropDisplayCanvas.requestPaint(); - } - } - - // signals - - signal acceptImage(int x, int y, int width, int height); - signal cancelImage(); - - Image { - id: imageBackground; - objectName: "imageBackground"; - - source: "image://snapshot/rawimage"; - cache: false; - - height: Window.height / Screen.devicePixelRatio; - width: Window.width / Screen.devicePixelRatio; - fillMode: Image.PreserveAspectFit; - } - - Canvas { - id: cropDisplayCanvas; - objectName: "cropDisplayCanvas"; - anchors.fill: imageBackground; - - renderTarget: Canvas.FramebufferObject; - renderStrategy: Canvas.Cooperative; - - onPaint: { - // start by getting a context on the canvas and clearing it - var ctx = cropDisplayCanvas.getContext("2d"); - ctx.clearRect(0, 0, cropDisplayCanvas.width, cropDisplayCanvas.height); - - // set up the colours - ctx.strokeStyle = strokeColour; - ctx.fillStyle = maskColour; - - // draw a sheet over the whole screen - ctx.fillRect(0, 0, cropDisplayCanvas.width, cropDisplayCanvas.height); - - if (selection) { - midHelpText.visible = false; - // display bottom help text only if it does not intersect with the selection - bottomHelpText.visible = (selection.y + selection.height < bottomHelpText.y) || (selection.x > bottomHelpText.x + bottomHelpText.width) || (selection.x + selection.width < bottomHelpText.x); - - // if we have a selection polygon, cut it out - ctx.fillStyle = strokeColour; - ctx.fillRect(selection.x, selection.y, selection.width, selection.height); - ctx.clearRect(selection.x + 1, selection.y + 1, selection.width - 2, selection.height - 2); - - if ((selection.width > 20) && (selection.height > 20)) { - // top-left handle - ctx.beginPath(); - ctx.arc(selection.x, selection.y, 8, 0, 0.5 * Math.PI); - ctx.lineTo(selection.x, selection.y); - ctx.fill(); - - // top-right handle - ctx.beginPath(); - ctx.arc(selection.x + selection.width, selection.y, 8, 0.5 * Math.PI, Math.PI); - ctx.lineTo(selection.x + selection.width, selection.y); - ctx.fill(); - - // bottom-left handle - ctx.beginPath(); - ctx.arc(selection.x + selection.width, selection.y + selection.height, 8, Math.PI, 1.5 * Math.PI); - ctx.lineTo(selection.x + selection.width, selection.y + selection.height); - ctx.fill(); - - // bottom-right handle - ctx.beginPath(); - ctx.arc(selection.x, selection.y + selection.height, 8, 1.5 * Math.PI, 2 * Math.PI); - ctx.lineTo(selection.x, selection.y + selection.height); - ctx.fill(); - - // top-center handle - ctx.beginPath(); - ctx.arc(selection.x + selection.width / 2, selection.y, 5, 0, Math.PI); - ctx.fill(); - - // right-center handle - ctx.beginPath(); - ctx.arc(selection.x + selection.width, selection.y + selection.height / 2, 5, 0.5 * Math.PI, 1.5 * Math.PI); - ctx.fill(); - - // bottom-center handle - ctx.beginPath(); - ctx.arc(selection.x + selection.width / 2, selection.y + selection.height, 5, Math.PI, 2 * Math.PI); - ctx.fill(); - - // left-center handle - ctx.beginPath(); - ctx.arc(selection.x, selection.y + selection.height / 2, 5, 1.5 * Math.PI, 0.5 * Math.PI); - ctx.fill(); - } - - // Set the selection size and finds the most appropriate position: - // - vertically centered inside the selection if the box is not covering the a large part of selection - // - on top of the selection if the selection x position fits the box height plus some margin - // - at the bottom otherwise - // Note that text is drawn starting from the left bottom! - var selectionText = Math.round(selection.width * Screen.devicePixelRatio) + "x" + Math.round(selection.height * Screen.devicePixelRatio); - selectionTextMetrics.font = ctx.font; - selectionTextMetrics.text = selectionText; - var selectionTextRect = selectionTextMetrics.boundingRect; - var selectionBoxX = Math.max(0, selection.x + (selection.width - selectionTextRect.width) / 2); - var selectionBoxY; - if ((selection.width > 100) && (selection.height > 100)) { - // show inside the box - selectionBoxY = selection.y + (selection.height + selectionTextRect.height) / 2; - } else if (selection.y >= selectionTextRect.height + 8) { - // show on top - selectionBoxY = selection.y - 8; - } else { - // show at the bottom - selectionBoxY = selection.y + selection.height + selectionTextRect.height + 4; - } - // Now do the actual box, border and text drawing - ctx.fillStyle = systemPalette.window; - ctx.strokeStyle = systemPalette.windowText; - ctx.fillRect(selectionBoxX - 4, selectionBoxY - selectionTextRect.height - 2, selectionTextRect.width + 10, selectionTextRect.height + 8); - ctx.strokeRect(selectionBoxX - 4, selectionBoxY - selectionTextRect.height - 2, selectionTextRect.width + 10, selectionTextRect.height + 8); - ctx.fillStyle = systemPalette.windowText; - ctx.fillText(selectionText, selectionBoxX, selectionBoxY); - if (selection.zoomCenterX >= 0 && selection.zoomCenterY >= 0 && (showMagnifier ^ toggleMagnifier)) { - var offsetX = magOffset; - var offsetY = magOffset; - var magX = selection.zoomCenterX; - var magY = selection.zoomCenterY; - var magWidth = crossMagnifier.width; - var magHeight = crossMagnifier.height; - - if (magX + offsetX + magWidth >= Window.width / Screen.devicePixelRatio) { - offsetX -= offsetX * 2 + magWidth; - } - - if (magY + offsetY + magHeight >= Window.height / Screen.devicePixelRatio) { - offsetY -= offsetY * 2 + magHeight; - } - - magX += offsetX; - magY += offsetY; - crossMagnifier.visible = true; - crossMagnifier.x = magX; - crossMagnifier.y = magY; - crossBackground.x = -selection.zoomCenterX * Screen.devicePixelRatio * magZoom + magPixels * magZoom; - crossBackground.y = -selection.zoomCenterY * Screen.devicePixelRatio * magZoom + magPixels * magZoom; - ctx.strokeRect(magX, magY, magWidth, magHeight); - } else { - crossMagnifier.visible = false; - } - } else { - midHelpText.visible = true; - bottomHelpText.visible = false; - crossMagnifier.visible = false; - } - } - - TextMetrics { - id: selectionTextMetrics - } - - Rectangle { - id: midHelpText; - objectName: "midHelpText"; - - height: midHelpTextElement.height + 40; - width: midHelpTextElement.width + 40; - radius: 4; - border.color: systemPalette.windowText; - color: labelBackgroundColour; - - visible: false; - anchors.centerIn: parent; - - Label { - id: midHelpTextElement; - text: i18n("Click anywhere to start drawing a selection rectangle,\n" + - "or press Esc to cancel."); - font.pixelSize: Qt.application.font.pixelSize * 1.2; - - anchors.centerIn: parent; - } - } - - Rectangle { - id: bottomHelpText; - objectName: "bottomHelpText"; - - height: bottomHelpTextElement.height + 20; - width: bottomHelpTextElement.width + 20; - radius: 4; - border.color: systemPalette.windowText; - color: labelBackgroundColour; - - visible: false; - anchors.bottom: parent.bottom; - anchors.horizontalCenter: parent.horizontalCenter; - - GridLayout { - id: bottomHelpTextElement; - columns: 2 - anchors.centerIn: parent; - - Label { - text: i18n("Enter, double-click:"); - Layout.alignment: Qt.AlignRight; - } - Label { text: i18n("Take screenshot"); } - - Label { - text: i18n("Shift:"); - Layout.alignment: Qt.AlignRight; - } - Label { text: i18n("Hold to toggle magnifier"); } - - Label { - text: i18n("Right-click:"); - Layout.alignment: Qt.AlignRight; - } - Label { text: i18n("Reset selection"); } - - Label { - text: i18n("Esc:"); - Layout.alignment: Qt.AlignRight; - } - Label { text: i18n("Cancel"); } - } - } - - // Use Rectangle so that the background is white when cursor nearby edge - Rectangle { - id: crossMagnifier; - - height: (magPixels * 2 + 1) * magZoom; - width: height; - border.width: 0; - visible: false; - clip: true - - Image { - id: crossBackground; - source: "image://snapshot/rawimage"; - smooth: false; - height: Window.height * magZoom; - width: Window.width * magZoom; - } - - Rectangle { - x: magPixels * magZoom; - y: 0; - width: magZoom; - height: magPixels * magZoom; - color: crossColour; - } - - Rectangle { - x: magPixels * magZoom; - y: (magPixels + 1) * magZoom; - width: magZoom; - height: magPixels * magZoom; - color: crossColour; - } - - Rectangle { - x: 0; - y: magPixels * magZoom; - width: magPixels * magZoom; - height: magZoom; - color: crossColour; - } - - Rectangle { - x: (magPixels + 1) * magZoom; - y: magPixels * magZoom; - width: magPixels * magZoom; - height: magZoom; - color: crossColour; - } - } - - } - - MouseArea { - anchors.fill: imageBackground; - - property int startx: 0; - property int starty: 0; - - cursorShape: Qt.CrossCursor; - acceptedButtons: Qt.LeftButton | Qt.RightButton; - - onPressed: { - if (selection) { - selection.destroy(); - } - - startx = mouse.x; - starty = mouse.y; - - selection = cropRectangle.createObject(parent, { - "x": startx, - "y": starty, - "height": 0, - "width": 0 - }); - } - - onPositionChanged: { - selection.x = Math.min(startx, mouse.x); - selection.y = Math.min(starty, mouse.y); - selection.width = Math.abs(startx - mouse.x) + 1; - selection.height = Math.abs(starty - mouse.y) + 1; - selection.zoomCenterX = mouse.x; - selection.zoomCenterY = mouse.y; - cropDisplayCanvas.requestPaint(); - } - - onClicked: { - if ((mouse.button == Qt.RightButton) && (selection)) { - selection.destroy(); - cropDisplayCanvas.requestPaint(); - } - } - - onReleased: { - selection.zoomCenterX = -1; - selection.zoomCenterY = -1; - cropDisplayCanvas.requestPaint(); - } - } - - Component { - id: cropRectangle; - - SelectionRectangle { - drawCanvas: cropDisplayCanvas; - imageElement: imageBackground; - - onDoubleClicked: editorRoot.accept() - } - } -} diff --git a/src/QuickEditor/QmlResources.qrc b/src/QuickEditor/QmlResources.qrc deleted file mode 100644 --- a/src/QuickEditor/QmlResources.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - EditorRoot.qml - SelectionRectangle.qml - - diff --git a/src/QuickEditor/QuickEditor.h b/src/QuickEditor/QuickEditor.h --- a/src/QuickEditor/QuickEditor.h +++ b/src/QuickEditor/QuickEditor.h @@ -20,34 +20,90 @@ #ifndef QUICKEDITOR_H #define QUICKEDITOR_H -#include +#include +#include +#include +#include +#include +#include -class QuickEditor : public QObject +class QuickEditor : public QWidget { Q_OBJECT - public: +public: + explicit QuickEditor(const QPixmap &pixmap, QObject* parent = nullptr); - explicit QuickEditor(const QPixmap &pixmap, QObject *parent = 0); - virtual ~QuickEditor(); +private: + enum MouseState : short { + None = 0, // 0000 + Inside = 1, // 0001 + Outside = 2, // 0010 + TopLeft = 5, //101 + Top = 17, // 10001 + TopRight = 9, // 1001 + Right = 33, // 100001 + BottomRight = 6, // 110 + Bottom = 18, // 10010 + BottomLeft = 10, // 1010 + Left = 34, // 100010 + TopLeftOrBottomRight = 4, // 100 + TopRightOrBottomLeft = 8, // 1000 + TopOrBottom = 16, // 10000 + RightOrLeft = 32, // 100000 + }; - signals: + void acceptSelection(); + void keyPressEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void paintEvent(QPaintEvent*) override; + void drawBottomHelpText(QPainter& painter); + void drawDragHandles(QPainter& painter); + void drawMidHelpText(QPainter& painter); + void drawSelectionSizeTooltip(QPainter& painter); + void layoutBottomHelpText(); + void setMouseCursor(const QPointF& pos); + MouseState whereIsTheMouse(const QPointF& pos); - void grabDone(const QPixmap &pixmap, const QRect &cropRegion); - void grabCancelled(); - - private slots: + static const qreal mouseAreaSize; + static const qreal cornerHandleRadius; + static const qreal midHandleRadius; + static const int selectionSizeThreshold; - void acceptImageHandler(int x, int y, int width, int height); + static const int selectionBoxPaddingX; + static const int selectionBoxPaddingY; + static const int selectionBoxMarginY; - private: + static const int bottomHelpBoxPaddingX; + static const int bottomHelpBoxPaddingY; + static const int bottomHelpBoxPairSpacing; + static const int bottomHelpBoxLineHeight; - struct ImageStore; - ImageStore *mImageStore; + QColor mMaskColour; + QColor mStrokeColour; + QColor mLabelBackgroundColour; + QColor mLabelForegroundColour; + QRectF mSelection; + QPointF mStartPos; + QPointF mInitialTopLeft; + QString mMidHelpText; + QFont mMidHelpTextFont; + static constexpr int bottomHelpLength = 4; + QPair mBottomHelpText[bottomHelpLength]; + QFont mBottomHelpTextFont; + QRect mBottomHelpBorderBox; + QPoint mBottomHelpContentPos; + int mBottomHelpGridLeftWidth; + MouseState mMouseDragState; + QPixmap mPixmap; + qreal dprI; - struct QuickEditorPrivate; - Q_DECLARE_PRIVATE(QuickEditor); - QuickEditorPrivate *d_ptr; +signals: + void grabDone(const QPixmap &pixmap); + void grabCancelled(); }; #endif // QUICKEDITOR_H diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -21,130 +21,540 @@ #include "SpectacleConfig.h" -#include -#include +const qreal QuickEditor::mouseAreaSize = 20.0; +const qreal QuickEditor::cornerHandleRadius = 8.0; +const qreal QuickEditor::midHandleRadius = 5.0; +const int QuickEditor::selectionSizeThreshold = 100; -#include -#include -#include -#include -#include -#include +const int QuickEditor::selectionBoxPaddingX = 5; +const int QuickEditor::selectionBoxPaddingY = 4; +const int QuickEditor::selectionBoxMarginY = 2; -struct QuickEditor::ImageStore : public QQuickImageProvider +const int QuickEditor::bottomHelpBoxPaddingX = 12; +const int QuickEditor::bottomHelpBoxPaddingY = 8; +const int QuickEditor::bottomHelpBoxPairSpacing = 6; +const int QuickEditor::bottomHelpBoxLineHeight = 24; + +QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : + mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), + mStrokeColour(palette().highlight().color()), + mLabelBackgroundColour(QColor::fromRgbF( + palette().light().color().redF(), + palette().light().color().greenF(), + palette().light().color().blueF(), + 0.85 + )), + mLabelForegroundColour(palette().windowText().color()), + mMidHelpText(tr("Click and drag to draw a selection rectangle,\nor press Esc to quit")), + mMidHelpTextFont(font()), + mBottomHelpText({ + {QStaticText(tr("Enter, double-click:")), QStaticText(tr("Take screenshot"))}, + {QStaticText(tr("Shift:")), QStaticText(tr("Hold to toggle magnifier"))}, + {QStaticText(tr("Right-click:")), QStaticText(tr("Reset selection"))}, + {QStaticText(tr("Esc:")), QStaticText(tr("Cancel"))}, + }), + mBottomHelpTextFont(font()), + mBottomHelpGridLeftWidth(0), + mMouseDragState(MouseState::None), + mPixmap(pixmap) { - ImageStore(const QPixmap &pixmap) : - QQuickImageProvider(QQuickImageProvider::Pixmap), - mPixmap(pixmap) - {} + Q_UNUSED(parent); + + SpectacleConfig *config = SpectacleConfig::instance(); + if (config->useLightRegionMaskColour()) { + mMaskColour = QColor(255, 255, 255, 100); + } + + setMouseTracking(true); + setAttribute(Qt::WA_StaticContents); + setWindowFlags(Qt::WindowStaysOnTopHint); + setAttribute(Qt::WA_TranslucentBackground); + showFullScreen(); - QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE - { - Q_UNUSED(id); + dprI = 1.0 / devicePixelRatioF(); + setGeometry(0, 0, mPixmap.width() * dprI, mPixmap.height() * dprI); - if (size) { - *size = mPixmap.size(); + if (config->rememberLastRectangularRegion()) { + QRect cropRegion = config->cropRegion(); + if (!cropRegion.isEmpty()) { + mSelection = QRectF( + cropRegion.x() * dprI, + cropRegion.y() * dprI, + cropRegion.width() * dprI, + cropRegion.height() * dprI + ).intersected(geometry()); } + setMouseCursor(QCursor::pos()); + } else { + setCursor(Qt::CrossCursor); + } + + mMidHelpTextFont.setPointSize(12); - if (requestedSize.isEmpty()) { - return mPixmap; + for (auto& pair : mBottomHelpText) { + for (auto item : { pair.first, pair.second }) { + item.prepare(QTransform(), mBottomHelpTextFont); + item.setPerformanceHint(QStaticText::AggressiveCaching); } + } + layoutBottomHelpText(); - return mPixmap.scaled(requestedSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + update(); +} + +void QuickEditor::acceptSelection() +{ + if (!mSelection.isEmpty()) { + const qreal dpr = devicePixelRatioF(); + QRect scaledCropRegion = QRect( + qRound(mSelection.x() * dpr), + qRound(mSelection.y() * dpr), + qRound(mSelection.width() * dpr), + qRound(mSelection.height() * dpr) + ); + SpectacleConfig::instance()->setCropRegion(scaledCropRegion); + emit grabDone(mPixmap.copy(scaledCropRegion)); + delete this; } +} - QPixmap mPixmap; -}; +void QuickEditor::keyPressEvent(QKeyEvent* event) +{ + switch(event->key()) { + case Qt::Key_Escape: + emit grabCancelled(); + delete this; + break; + case Qt::Key_Return: + case Qt::Key_Enter: + acceptSelection(); + default: + break; + } +} -struct QuickEditor::QuickEditorPrivate +void QuickEditor::mousePressEvent(QMouseEvent* event) { - KDeclarative::KDeclarative *mDecl; - QQuickView *mQuickView; - QQmlEngine *mQmlEngine; - QRect mGrabRect; - QSharedPointer mCurrentGrabResult; -}; - -QuickEditor::QuickEditor(const QPixmap &pixmap, QObject *parent) : - QObject(parent), - mImageStore(new ImageStore(pixmap)), - d_ptr(new QuickEditorPrivate) + if (event->button() & Qt::LeftButton) { + const QPointF& pos = event->screenPos(); + mMouseDragState = whereIsTheMouse(pos); + switch(mMouseDragState) { + case MouseState::Outside: + mStartPos = pos; + break; + case MouseState::Inside: + mStartPos = pos; + mInitialTopLeft = mSelection.topLeft(); + setCursor(Qt::ClosedHandCursor); + break; + case MouseState::Top: + case MouseState::Left: + case MouseState::TopLeft: + mStartPos = mSelection.bottomRight(); + break; + case MouseState::Bottom: + case MouseState::Right: + case MouseState::BottomRight: + mStartPos = mSelection.topLeft(); + break; + case MouseState::TopRight: + mStartPos = mSelection.bottomLeft(); + break; + case MouseState::BottomLeft: + mStartPos = mSelection.topRight(); + default: + break; + } + } + event->accept(); +} + +void QuickEditor::mouseMoveEvent(QMouseEvent* event) { - Q_D(QuickEditor); + const QPointF& pos = event->screenPos(); + switch (mMouseDragState) { + case MouseState::None: { + setMouseCursor(pos); + break; + } + case MouseState::TopLeft: + case MouseState::TopRight: + case MouseState::BottomRight: + case MouseState::BottomLeft: { + const bool afterX = pos.x() >= mStartPos.x(); + const bool afterY = pos.y() >= mStartPos.y(); + mSelection.setRect( + afterX ? mStartPos.x() : pos.x(), + afterY ? mStartPos.y() : pos.y(), + qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), + qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) + ); + update(); + break; + } + case MouseState::Outside: { + mSelection.setRect( + qMin(pos.x(), mStartPos.x()), + qMin(pos.y(), mStartPos.y()), + qAbs(pos.x() - mStartPos.x()) + dprI, + qAbs(pos.y() - mStartPos.y()) + dprI + ); + update(); + break; + } + case MouseState::Top: + case MouseState::Bottom: { + const bool afterY = pos.y() >= mStartPos.y(); + mSelection.setRect( + mSelection.x(), + afterY ? mStartPos.y() : pos.y(), + mSelection.width(), + qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) + ); + update(); + break; + } + case MouseState::Right: + case MouseState::Left: { + const bool afterX = pos.x() >= mStartPos.x(); + mSelection.setRect( + afterX ? mStartPos.x() : pos.x(), + mSelection.y(), + qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), + mSelection.height() + ); + update(); + break; + } + case MouseState::Inside: { + // We use some math here to figure out if the diff with which we + // move the rectangle with moves it out of bounds, + // in which case we adjust the diff to not let that happen - d->mQmlEngine = new QQmlEngine(); - d->mDecl = new KDeclarative::KDeclarative; - d->mDecl->setDeclarativeEngine(d->mQmlEngine); + const qreal dpr = devicePixelRatioF(); + // new top left point of the rectangle + QPoint newTopLeft = ((pos - mStartPos + mInitialTopLeft) * dpr).toPoint(); -#if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0) - d->mDecl->setupEngine(d->mQmlEngine); - d->mDecl->setupContext(); -#else - d->mDecl->setupBindings(); -#endif + // the max coordinates of the top left point + const int realMaxX = qRound((width() - mSelection.width()) * dpr); + const int realMaxY = qRound((height() - mSelection.height()) * dpr); - d->mQmlEngine->addImageProvider(QStringLiteral("snapshot"), mImageStore); + if (newTopLeft.x() < 0) { + // tweak startPos to prevent rectangle from getting stuck + mStartPos.setX(mStartPos.x() + newTopLeft.x() * dprI); + newTopLeft.setX(0); + } else { + const int xOffset = newTopLeft.x() - realMaxX; + if (xOffset > 0) { + mStartPos.setX(mStartPos.x() + xOffset * dprI); + newTopLeft.setX(realMaxX); + } + } - d->mQuickView = new QQuickView(d->mQmlEngine, 0); - d->mQuickView->setClearBeforeRendering(false); - d->mQuickView->setSource(QUrl(QStringLiteral("qrc:///QuickEditor/EditorRoot.qml"))); + if (newTopLeft.y() < 0) { + mStartPos.setY(mStartPos.y() + newTopLeft.y() * dprI); + newTopLeft.setY(0); + } else { + const int yOffset = newTopLeft.y() - realMaxY; + if (yOffset > 0) { + mStartPos.setY(mStartPos.y() + yOffset * dprI); + newTopLeft.setY(realMaxY); + } + } - d->mQuickView->setFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); - d->mQuickView->setGeometry(0, 0, pixmap.width(), pixmap.height()); - d->mQuickView->showFullScreen(); + const auto newTopLeftF = QPointF(newTopLeft) * dprI; - // connect up the signals - QQuickItem *rootItem = d->mQuickView->rootObject(); - connect(rootItem, SIGNAL(acceptImage(int, int, int, int)), this, SLOT(acceptImageHandler(int, int, int, int))); - connect(rootItem, SIGNAL(cancelImage()), this, SIGNAL(grabCancelled())); + mSelection.moveTo(newTopLeftF); + update(); + } + default: + break; + } + + event->accept(); +} + +void QuickEditor::mouseReleaseEvent(QMouseEvent* event) +{ + const auto button = event->button(); + if (button == Qt::LeftButton && mMouseDragState == MouseState::Inside) { + setCursor(Qt::OpenHandCursor); + } else if (button == Qt::RightButton) { + mSelection.setWidth(0); + mSelection.setHeight(0); + } + event->accept(); + mMouseDragState = MouseState::None; + update(); +} + +void QuickEditor::mouseDoubleClickEvent(QMouseEvent* event) +{ + event->accept(); + if (event->button() == Qt::LeftButton && mSelection.contains(event->pos())) { + acceptSelection(); + } +} + +void QuickEditor::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing); + QBrush brush(mPixmap); + brush.setTransform(QTransform().scale(dprI, dprI)); + painter.setBackground(brush); + painter.eraseRect(geometry()); + if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { + painter.fillRect(mSelection, mStrokeColour); + const QRectF innerRect = mSelection.adjusted(1, 1, -1, -1); + if (innerRect.width() > 0 && innerRect.height() > 0) { + painter.eraseRect(mSelection.adjusted(1, 1, -1, -1)); + } + + QRectF top(0, 0, width(), mSelection.top()); + QRectF right(mSelection.right(), mSelection.top(), width() - mSelection.right(), mSelection.height()); + QRectF bottom(0, mSelection.bottom(), width(), height() - mSelection.bottom()); + QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); + for (auto& rect : { top, right, bottom, left }) { + painter.fillRect(rect, mMaskColour); + } + + drawSelectionSizeTooltip(painter); + if (mMouseDragState == MouseState::None) { // mouse is up + if ((mSelection.width() > 20) && (mSelection.height() > 20)) { + drawDragHandles(painter); + } - // set up initial config - SpectacleConfig *config = SpectacleConfig::instance(); - if (config->rememberLastRectangularRegion()) { - auto pixelRatio = d->mQuickView->devicePixelRatio(); - QRect cropRegion = config->cropRegion(); - if (!cropRegion.isEmpty()) { - QMetaObject::invokeMethod( - rootItem, "setInitialSelection", - Q_ARG(QVariant, cropRegion.x() / pixelRatio), - Q_ARG(QVariant, cropRegion.y() / pixelRatio), - Q_ARG(QVariant, cropRegion.width() / pixelRatio), - Q_ARG(QVariant, cropRegion.height() / pixelRatio) - ); } + drawBottomHelpText(painter); + } else { + drawMidHelpText(painter); } +} - rootItem->setProperty("showMagnifier", config->showMagnifierChecked()); +void QuickEditor::layoutBottomHelpText() +{ + int mMaxRightWidth = 0; + int contentWidth = 0; + int contentHeight = 0; + mBottomHelpGridLeftWidth = 0; + for (auto& item : mBottomHelpText) { + auto& left = item.first; + auto& right = item.second; + const auto leftSize = left.size().toSize(); + const auto rightSize = right.size().toSize(); + mBottomHelpGridLeftWidth = qMax(mBottomHelpGridLeftWidth, leftSize.width()); + mMaxRightWidth = qMax(mMaxRightWidth, rightSize.width()); + contentWidth = qMax(contentWidth, mBottomHelpGridLeftWidth + mMaxRightWidth + bottomHelpBoxPairSpacing); + contentHeight += bottomHelpBoxLineHeight; + } + mBottomHelpContentPos.setX((width() - contentWidth) / 2); + mBottomHelpContentPos.setY(height() - contentHeight - 8); + mBottomHelpGridLeftWidth += mBottomHelpContentPos.x(); + mBottomHelpBorderBox.setRect( + mBottomHelpContentPos.x() - bottomHelpBoxPaddingX, + mBottomHelpContentPos.y() - bottomHelpBoxPaddingY, + contentWidth + bottomHelpBoxPaddingX * 2, + contentHeight + bottomHelpBoxPaddingY * 2 - 1 + ); +} - if (config->useLightRegionMaskColour()) { - rootItem->setProperty("maskColour", QColor(255, 255, 255, 100)); +void QuickEditor::drawBottomHelpText(QPainter &painter) +{ + if (mSelection.intersects(mBottomHelpBorderBox)) return; + + painter.setBrush(mLabelBackgroundColour); + painter.setPen(mLabelForegroundColour); + painter.setFont(mBottomHelpTextFont); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawRect(mBottomHelpBorderBox); + painter.setRenderHint(QPainter::Antialiasing, true); + + int topOffset = mBottomHelpContentPos.y(); + for (auto& item : mBottomHelpText) { + auto& left = item.first; + auto& right = item.second; + const auto leftSize = left.size().toSize(); + const auto drawY = topOffset + (bottomHelpBoxLineHeight - leftSize.height()) / 2; + painter.drawStaticText(mBottomHelpGridLeftWidth - leftSize.width(), drawY, left); + painter.drawStaticText(mBottomHelpGridLeftWidth + bottomHelpBoxPairSpacing, drawY, right); + topOffset += bottomHelpBoxLineHeight; } } -QuickEditor::~QuickEditor() +void QuickEditor::drawDragHandles(QPainter& painter) { - Q_D(QuickEditor); - delete d->mQuickView; - delete d->mDecl; - delete d->mQmlEngine; + const qreal leftX = mSelection.x(); + const qreal width = mSelection.width(); + const qreal centerX = leftX + width / 2.0; + const qreal rightX = leftX + width; + + const qreal topY = mSelection.y(); + const qreal height = mSelection.height(); + const qreal centerY = topY + height / 2.0; + const qreal bottomY = topY + height; + + // start a path + QPainterPath path; + + const qreal cornerHandleDiameter = 2 * cornerHandleRadius; + // top-left handle + path.moveTo(leftX, topY); + path.arcTo(leftX - cornerHandleRadius, topY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 0, -90); + + // top-right handle + path.moveTo(rightX, topY); + path.arcTo(rightX - cornerHandleRadius, topY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 180, 90); + + // bottom-left handle + path.moveTo(leftX, bottomY); + path.arcTo(leftX - cornerHandleRadius, bottomY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 0, 90); + + // bottom-right handle + path.moveTo(rightX, bottomY); + path.arcTo(rightX - cornerHandleRadius, bottomY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 180, -90); + + const qreal midHandleDiameter = 2 * midHandleRadius; + // top-center handle + path.moveTo(centerX, topY); + path.arcTo(centerX - midHandleRadius, topY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, -180); - delete d_ptr; + // right-center handle + path.moveTo(rightX, centerY); + path.arcTo(rightX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, 180); + + // bottom-center handle + path.moveTo(centerX, bottomY); + path.arcTo(centerX - midHandleRadius, bottomY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, 180); + + // left-center handle + path.moveTo(leftX, centerY); + path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); + + // draw the path + painter.fillPath(path, mStrokeColour); } -void QuickEditor::acceptImageHandler(int x, int y, int width, int height) +void QuickEditor::drawMidHelpText(QPainter &painter) { - Q_D(QuickEditor); + painter.fillRect(geometry(), mMaskColour); + painter.setFont(mMidHelpTextFont); + QRect textSize = painter.boundingRect(QRect(), Qt::AlignCenter, mMidHelpText); + QPoint pos((width() - textSize.width()) / 2, (height() - textSize.height()) / 2); - if ((x == -1) && (y == -1) && (width == -1) && (height == -1)) { - SpectacleConfig::instance()->setCropRegion(QRect()); - emit grabCancelled(); - return; + painter.setBrush(mLabelBackgroundColour); + QPen pen(mLabelForegroundColour); + pen.setWidth(2); + painter.setPen(pen); + painter.drawRoundedRect(QRect(pos.x() - 20, pos.y() - 20, textSize.width() + 40, textSize.height() + 40), 4, 4); + + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawText(QRect(pos, textSize.size()), Qt::AlignCenter, mMidHelpText); +} + +void QuickEditor::drawSelectionSizeTooltip(QPainter &painter) +{ + // Set the selection size and finds the most appropriate position: + // - vertically centered inside the selection if the box is not covering the a large part of selection + // - on top of the selection if the selection x position fits the box height plus some margin + // - at the bottom otherwise + const qreal dpr = devicePixelRatioF(); + QString selectionSizeText = QString(tr("%1x%2")).arg(qRound(mSelection.width() * dpr)).arg(qRound(mSelection.height() * dpr)); + const QRect selectionSizeTextRect = painter.boundingRect(QRect(), 0, selectionSizeText); + + const int selectionBoxWidth = selectionSizeTextRect.width() + selectionBoxPaddingX * 2; + const int selectionBoxHeight = selectionSizeTextRect.height() + selectionBoxPaddingY * 2; + const int selectionBoxX = qBound( + 0, + static_cast(mSelection.x()) + (static_cast(mSelection.width()) - selectionSizeTextRect.width()) / 2 - selectionBoxPaddingX, + width() - selectionBoxWidth + ); + qreal selectionBoxY; + if ((mSelection.width() > selectionSizeThreshold) && (mSelection.height() > selectionSizeThreshold)) { + // show inside the box + selectionBoxY = mSelection.y() + (mSelection.height() - selectionSizeTextRect.height()) / 2; + } else { + // show on top by default + selectionBoxY = mSelection.y() - selectionBoxHeight - selectionBoxMarginY; + if (selectionBoxY < 0) { + // show at the bottom + selectionBoxY = mSelection.y() + mSelection.height() + selectionBoxMarginY; + } + } + + // Now do the actual box, border, and text drawing + painter.setBrush(mLabelBackgroundColour); + painter.setPen(mLabelForegroundColour); + const QRect selectionBoxRect( + selectionBoxX, + selectionBoxY, + selectionBoxWidth, + selectionBoxHeight + ); + + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawRect(selectionBoxRect); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.drawText(selectionBoxRect, Qt::AlignCenter, selectionSizeText); +} + +void QuickEditor::setMouseCursor(const QPointF& pos) +{ + MouseState mouseState = whereIsTheMouse(pos); + if (mouseState == MouseState::Outside) { + setCursor(Qt::CrossCursor); + } else if (MouseState::TopLeftOrBottomRight & mouseState) { + setCursor(Qt::SizeFDiagCursor); + } else if (MouseState::TopRightOrBottomLeft & mouseState) { + setCursor(Qt::SizeBDiagCursor); + } else if (MouseState::TopOrBottom & mouseState) { + setCursor(Qt::SizeVerCursor); + } else if (MouseState::RightOrLeft & mouseState) { + setCursor(Qt::SizeHorCursor); + } else { + setCursor(Qt::OpenHandCursor); } +} - auto pixelRatio = d->mQuickView->devicePixelRatio(); - d->mGrabRect = QRect(x * pixelRatio, y * pixelRatio, width * pixelRatio, height * pixelRatio); - SpectacleConfig::instance()->setCropRegion(d->mGrabRect); +QuickEditor::MouseState QuickEditor::whereIsTheMouse(const QPointF& pos) +{ + if (mSelection.contains(pos)) { + const qreal verSize = qMin(mouseAreaSize, mSelection.height() / 2); + const qreal horSize = qMin(mouseAreaSize, mSelection.width() / 2); + + auto withinThreshold = [](const qreal offset, const qreal size) { + return offset <= size && offset >= 0; + }; + + const bool withinTopEdge = withinThreshold(pos.y() - mSelection.top(), verSize); + const bool withinRightEdge = withinThreshold(mSelection.right() - pos.x(), horSize); + const bool withinBottomEdge = !withinTopEdge && withinThreshold(mSelection.bottom() - pos.y(), verSize); + const bool withinLeftEdge = !withinRightEdge && withinThreshold(pos.x() - mSelection.left(), horSize); - d->mQuickView->hide(); - emit grabDone(mImageStore->mPixmap.copy(d->mGrabRect), d->mGrabRect); + if (withinTopEdge) { + if (withinRightEdge) { + return MouseState::TopRight; + } else if (withinLeftEdge) { + return MouseState::TopLeft; + } else { + return MouseState::Top; + } + } else if (withinBottomEdge) { + if (withinRightEdge) { + return MouseState::BottomRight; + } else if (withinLeftEdge) { + return MouseState::BottomLeft; + } else { + return MouseState::Bottom; + } + } else if (withinRightEdge) { + return MouseState::Right; + } else if (withinLeftEdge) { + return MouseState::Left; + } else { + return MouseState::Inside; + } + } else { + return MouseState::Outside; + } } diff --git a/src/QuickEditor/SelectionRectangle.qml b/src/QuickEditor/SelectionRectangle.qml deleted file mode 100644 --- a/src/QuickEditor/SelectionRectangle.qml +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2016 Boudhayan Gupta - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.5 - -Item { - id: cropRectItem; - objectName: "cropRectItem"; - - property var drawCanvas: null; - property var imageElement: null; - property int minRectSize: 20; - property int mouseAreaSize: 20; - property double zoomCenterX: -1; - property double zoomCenterY: -1; - - function resetZoomCenter() { - zoomCenterX = -1; - zoomCenterY = -1; - drawCanvas.requestPaint(); - } - - signal doubleClicked(); - - onWidthChanged: { - var maxWidth = imageElement.width - x; - if (width > maxWidth) { - width = maxWidth; - } - } - - onHeightChanged: { - var maxHeight = imageElement.height - y; - if (height > maxHeight) { - height = maxHeight; - } - } - - MouseArea { - anchors.fill: parent; - cursorShape: Qt.OpenHandCursor; - - drag.target: parent; - drag.minimumX: 0; - drag.maximumX: imageElement.width - parent.width; - drag.minimumY: 0; - drag.maximumY: imageElement.height - parent.height; - drag.smoothed: true; - - onPressed: { cursorShape = Qt.ClosedHandCursor; } - onPositionChanged: { drawCanvas.requestPaint(); } - onReleased: { cursorShape = Qt.OpenHandCursor; } - onDoubleClicked: { cropRectItem.doubleClicked(); } - } - - MouseArea { - id: hTopLeft; - - property int brxLimit: 0; - property int bryLimit: 0; - - anchors.top: parent.top; - anchors.left: parent.left; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeFDiagCursor; - - onPressed: { - brxLimit = (parent.x + parent.width) - minRectSize; - bryLimit = (parent.y + parent.height) - minRectSize; - parent.zoomCenterX = parent.x; - parent.zoomCenterY = parent.y; - } - - onPositionChanged: { - if ((parent.x + mouse.x) < brxLimit) { - parent.x = parent.x + mouse.x; - parent.width = parent.width - mouse.x; - parent.zoomCenterX = parent.zoomCenterX + mouse.x; - } - - if ((parent.y + mouse.y) < bryLimit) { - parent.y = parent.y + mouse.y; - parent.height = parent.height - mouse.y; - parent.zoomCenterY = parent.zoomCenterY + mouse.y; - } - - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hTopRight; - - property int brxLimit: 0; - property int bryLimit: 0; - - anchors.top: parent.top; - anchors.right: parent.right; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeBDiagCursor; - - onPressed: { - brxLimit = parent.x + mouseAreaSize + minRectSize; - bryLimit = (parent.y + parent.height) - minRectSize; - parent.zoomCenterX = parent.x + parent.width - 1; - parent.zoomCenterY = parent.y; - } - - onPositionChanged: { - if ((parent.x + parent.width + mouse.x) > brxLimit) { - parent.width = parent.width + mouse.x - mouseAreaSize + 1; - parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; - } - - if ((parent.y + mouse.y) < bryLimit) { - parent.y = parent.y + mouse.y; - parent.height = parent.height - mouse.y; - parent.zoomCenterY = parent.zoomCenterY + mouse.y; - } - - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hBottomLeft; - - property int brxLimit: 0; - property int bryLimit: 0; - - anchors.bottom: parent.bottom; - anchors.left: parent.left; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeBDiagCursor; - - onPressed: { - brxLimit = (parent.x + parent.width) - minRectSize; - bryLimit = parent.y + mouseAreaSize + minRectSize; - parent.zoomCenterX = parent.x; - parent.zoomCenterY = parent.y + parent.height - 1; - } - - onPositionChanged: { - if ((parent.x + mouse.x) < brxLimit) { - parent.x = parent.x + mouse.x; - parent.width = parent.width - mouse.x; - parent.zoomCenterX = parent.zoomCenterX + mouse.x; - } - - if ((parent.y + parent.height + mouse.y) > bryLimit) { - parent.height = parent.height + mouse.y - mouseAreaSize + 1; - parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; - } - - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hBottomRight; - - property int brxLimit: 0; - property int bryLimit: 0; - - anchors.bottom: parent.bottom; - anchors.right: parent.right; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeFDiagCursor; - - onPressed: { - brxLimit = parent.x + mouseAreaSize + minRectSize; - bryLimit = parent.y + mouseAreaSize + minRectSize; - parent.zoomCenterX = parent.x + parent.width - 1; - parent.zoomCenterY = parent.y + parent.height - 1; - } - - onPositionChanged: { - if ((parent.x + parent.width + mouse.x) > brxLimit) { - parent.width = parent.width + mouse.x - mouseAreaSize + 1; - parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; - } - - if ((parent.y + parent.height + mouse.y) > bryLimit) { - parent.height = parent.height + mouse.y - mouseAreaSize + 1; - parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; - } - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hTop; - - property int limit: 0; - - anchors.horizontalCenter: parent.horizontalCenter; - anchors.top: parent.top; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeVerCursor; - - onPressed: { - limit = (parent.y + parent.height) - minRectSize; - parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2; - parent.zoomCenterY = parent.y; - } - - onPositionChanged: { - if ((parent.y + mouse.y) < limit) { - parent.y = parent.y + mouse.y; - parent.height = parent.height - mouse.y; - parent.zoomCenterY = parent.zoomCenterY + mouse.y; - } - parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2 + mouse.x + 1; - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hBottom; - - property int limit: 0; - - anchors.horizontalCenter: parent.horizontalCenter; - anchors.bottom: parent.bottom; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeVerCursor; - - onPressed: { - limit = parent.y + mouseAreaSize + minRectSize; - parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2; - parent.zoomCenterY = parent.y + parent.height - 1; - } - - onPositionChanged: { - if ((parent.y + parent.height + mouse.y) > limit) { - parent.height = parent.height + mouse.y - mouseAreaSize + 1; - parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; - } - parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2 + mouse.x + 1; - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hLeft; - - property int limit: 0; - - anchors.verticalCenter: parent.verticalCenter; - anchors.left: parent.left; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeHorCursor; - - onPressed: { - limit = (parent.x + parent.width) - minRectSize; - parent.zoomCenterX = parent.x; - parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2; - } - - onPositionChanged: { - if ((parent.x + mouse.x) < limit) { - parent.x = parent.x + mouse.x; - parent.width = parent.width - mouse.x; - parent.zoomCenterX = parent.zoomCenterX + mouse.x; - } - parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2 + mouse.y + 1; - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } - - MouseArea { - id: hRight; - - property int limit: 0; - - anchors.verticalCenter: parent.verticalCenter; - anchors.right: parent.right; - - width: mouseAreaSize; - height: mouseAreaSize; - cursorShape: Qt.SizeHorCursor; - - onPressed: { - limit = parent.x + mouseAreaSize + minRectSize; - parent.zoomCenterX = parent.x + parent.width - 1; - parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2; - } - - onPositionChanged: { - if ((parent.x + parent.width + mouse.x) > limit) { - parent.width = parent.width + mouse.x - mouseAreaSize + 1; - parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; - } - parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2 + mouse.y + 1; - drawCanvas.requestPaint(); - } - - onReleased: { - resetZoomCenter(); - } - } -}