diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f2b272..2f6e1a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,87 +1,89 @@ # KScreenGenie project project(KScreenGenie) set(KSG_VERSION_MAJOR 2) set(KSG_VERSION_MINOR 0) set(KSG_VERSION_PATCH 1) set(KSG_VERSION "${KSG_VERSION_MAJOR}.${KSG_VERSION_MINOR}.${KSG_VERSION_PATCH}") # minimum requirements cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.6.0") set(PLASMA_MIN_VERSION "5.2.0") find_package(ECM 1.2.0 REQUIRED NO_MODULE) set( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ) # set up kf5 include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(FeatureSummary) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core + Quick Widgets DBus PrintSupport ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons + Declarative Notifications Config I18n KIO XmlGui WidgetsAddons WindowSystem DocTools ) # optional components find_package(KF5Kipi) if (KF5Kipi_FOUND) set(KIPI_FOUND 1) endif () find_package(XCB COMPONENTS XFIXES IMAGE UTIL CURSOR) if (XCB_FOUND) find_package(Qt5X11Extras ${QT_MIN_VERSION} REQUIRED) find_package(KF5Screen ${PLASMA_MIN_VERSION} REQUIRED) endif() # fail build if none of the platform backends can be found if (!XCB_FOUND) message(FATAL_ERROR "No suitable backend platform was found. Currenty supported platforms are: XCB") endif() # hand off to subdirectories add_subdirectory(src) add_subdirectory(desktop) add_subdirectory(icons) add_subdirectory(doc) # summaries feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50fe5af..202fa69 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,85 +1,90 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) # target set( KSG_SRCS_DEFAULT Main.cpp KSCore.cpp - ScreenClipper.cpp PlatformBackends/ImageGrabber.cpp PlatformBackends/DummyImageGrabber.cpp Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/KSSaveConfigDialog.cpp Gui/KSSendToMenu.cpp + Editor/KSImageEditor.cpp + Editor/KSCropRubberBand.cpp ) +qt5_add_resources(KSG_SRCS_RESOURCES Editor/QmlResources/QmlResources.qrc) if(XCB_FOUND) set( KSG_SRCS_X11 PlatformBackends/X11ImageGrabber.cpp ) endif() if(KF5Kipi_FOUND) set( KSG_SRCS_KIPI KipiInterface/KSGKipiInterface.cpp KipiInterface/KSGKipiInfoShared.cpp KipiInterface/KSGKipiImageCollectionShared.cpp KipiInterface/KSGKipiImageCollectionSelector.cpp ) endif() set( KSG_SRCS_ALL ${KSG_SRCS_DEFAULT} ${KSG_SRCS_KIPI} ${KSG_SRCS_X11} + ${KSG_SRCS_RESOURCES} ) add_executable( kscreengenie ${KSG_SRCS_ALL} ) # link libraries target_link_libraries( kscreengenie Qt5::DBus Qt5::PrintSupport + Qt5::Quick KF5::CoreAddons + KF5::Declarative KF5::Notifications KF5::ConfigGui KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::XmlGui KF5::WidgetsAddons ) if(XCB_FOUND) target_link_libraries( kscreengenie XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras KF5::Screen ) endif() if(KF5Kipi_FOUND) target_link_libraries ( kscreengenie KF5::Kipi ) endif() install(TARGETS kscreengenie ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/Editor/KSCropRubberBand.cpp b/src/Editor/KSCropRubberBand.cpp new file mode 100644 index 0000000..c2900f4 --- /dev/null +++ b/src/Editor/KSCropRubberBand.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2007 Luca Gugelmann + * Copyright (C) 2015 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * 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 Library 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. + */ + +#include "KSCropRubberBand.h" + +KSCropRubberBand::KSCropRubberBand(QQuickItem *parent) : + QQuickPaintedItem(parent), + mCropRect(0, 0, 0, 0), + mMouseOverHandle(nullptr) +{ + mTLHandle = mTRHandle = mBLHandle = mBRHandle = QRect(0, 0, 15, 15); + mLHandle = mRHandle = QRect(0, 0, 10, 20); + mTHandle = mBHandle = QRect(0, 0, 20, 10); + mHandles = { &mTLHandle, &mTRHandle, &mBLHandle, &mBRHandle, &mLHandle, &mTHandle, &mRHandle, &mBHandle }; + + setAcceptedMouseButtons(Qt::AllButtons); + setAcceptHoverEvents(true); + setAntialiasing(true); +} + +void KSCropRubberBand::paint(QPainter *painter) +{ + updateHandles(); + painter->save(); + QRegion drawRegion(boundingRect().toRect()); + + // first draw the overlay + + painter->setClipRegion(drawRegion - mCropRect); + painter->setBrush(QBrush(QColor(0, 0, 0, 127))); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawRect(boundingRect()); + + // then draw the border + + QRegion borderRegion(mCropRect); + borderRegion -= mCropRect.adjusted(1, 1, -1, -1); + painter->setClipRegion(borderRegion); + painter->setBrush(QBrush(QColor(0, 0, 0))); + painter->drawRect(mCropRect); + + // draw the handles + + if ((mCropRect.height() > 20) && (mCropRect.width() > 20)) { + drawHandles(painter, QColor(0, 0, 0)); + } + + // done + + painter->restore(); +} + +// property accessors + +int KSCropRubberBand::cropX() const { return mCropRect.x(); } +int KSCropRubberBand::cropY() const { return mCropRect.y(); } +int KSCropRubberBand::cropWidth() const { return mCropRect.width(); } +int KSCropRubberBand::cropHeight() const { return mCropRect.height(); } + +// property mutators + +void KSCropRubberBand::setCropX(const int &x) +{ + mCropRect.setX(x); + update(); +} + +void KSCropRubberBand::setCropY(const int &y) +{ + mCropRect.setY(y); + update(); +} + +void KSCropRubberBand::setCropWidth(const int &width) +{ + mCropRect.setWidth(width); + update(); +} + +void KSCropRubberBand::setCropHeight(const int &height) +{ + mCropRect.setHeight(height); + update(); +} + +// mouse event handlers + +void KSCropRubberBand::mousePressEvent(QMouseEvent *event) +{ + if (!mCropRect.contains(event->pos())) { + mCropRect = QRect(event->pos(), event->pos()); + mMouseOverHandle = &mBRHandle; + } else if (mMouseOverHandle == nullptr) { + mMoveDelta = event->pos() - mCropRect.topLeft(); + setCursor(Qt::ClosedHandCursor); + } + update(); +} + +void KSCropRubberBand::mouseMoveEvent(QMouseEvent *event) +{ + if (mMouseOverHandle == nullptr) { // moving the whole selection + QRect r = boundingRect().toRect(); + r.setBottomRight(r.bottomRight() - QPoint(mCropRect.width(), mCropRect.height()) + QPoint(1, 1)); + if (!(r.isNull() || r.isEmpty()) && r.isValid()) { + const QPoint newTopLeft = limitPointToRect(event->pos() - mMoveDelta, r); + if (newTopLeft == mCropRect.topLeft()) { + mMoveDelta = event->pos() - mCropRect.topLeft(); + } else { + mCropRect.moveTo(newTopLeft); + } + } + } else { // dragging a handle + QRect r = mCropRect; + + if (mMouseOverHandle == &mTLHandle) { + if (event->pos().x() <= r.right() && event->pos().y() <= r.bottom()) { + r.setTopLeft(event->pos()); + } else if (event->pos().x() <= r.right() && event->pos().y() > r.bottom()) { + r.setLeft(event->pos().x()); + r.setTop(r.bottom()); + r.setBottom(event->pos().y()); + mMouseOverHandle = &mBLHandle; + } else if (event->pos().x() > r.right() && event->pos().y() <= r.bottom()) { + r.setTop(event->pos().y()); + r.setLeft(r.right()); + r.setRight(event->pos().x()); + mMouseOverHandle = &mTRHandle; + } else { + r.setTopLeft(r.bottomRight()); + r.setBottomRight(event->pos()); + mMouseOverHandle = &mBRHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mTRHandle) { + if (event->pos().x() >= r.left() && event->pos().y() <= r.bottom()) { + r.setTopRight(event->pos()); + } else if (event->pos().x() >= r.left() && event->pos().y() > r.bottom()) { + r.setRight(event->pos().x()); + r.setTop(r.bottom()); + r.setBottom(event->pos().y()); + mMouseOverHandle = &mBRHandle; + } else if (event->pos().x() < r.left() && event->pos().y() <= r.bottom()) { + r.setTop(event->pos().y()); + r.setRight(r.left()); + r.setLeft(event->pos().x()); + mMouseOverHandle = &mTLHandle; + } else { + r.setTopRight(r.bottomLeft()); + r.setBottomLeft(event->pos()); + mMouseOverHandle = &mBLHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mBLHandle) { + if (event->pos().x() <= r.right() && event->pos().y() >= r.top()) { + r.setBottomLeft(event->pos()); + } else if (event->pos().x() <= r.left() && event->pos().y() < r.top()) { + r.setLeft(event->pos().x()); + r.setBottom(r.top()); + r.setTop(event->pos().y()); + mMouseOverHandle = &mTLHandle; + } else if (event->pos().x() > r.left() && event->pos().y() >= r.top()) { + r.setBottom(event->pos().y()); + r.setLeft(r.right()); + r.setRight(event->pos().x()); + mMouseOverHandle = &mBRHandle; + } else { + r.setBottomLeft(r.topRight()); + r.setTopRight(event->pos()); + mMouseOverHandle = &mTRHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mBRHandle) { + if (event->pos().x() >= r.left() && event->pos().y() >= r.top()) { + r.setBottomRight(event->pos()); + } else if (event->pos().x() >= r.left() && event->pos().y() < r.top()) { + r.setRight(event->pos().x()); + r.setBottom(r.top()); + r.setTop(event->pos().y()); + mMouseOverHandle = &mTRHandle; + } else if (event->pos().x() < r.left() && event->pos().y() >= r.top()) { + r.setBottom(event->pos().y()); + r.setRight(r.left()); + r.setLeft(event->pos().x()); + mMouseOverHandle = &mBLHandle; + } else { + r.setBottomRight(r.topLeft()); + r.setTopLeft(event->pos()); + mMouseOverHandle = &mTLHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mTHandle) { + if (event->pos().y() <= r.bottom()) { + r.setTop(event->pos().y()); + } else { + r.setTop(r.bottom()); + r.setBottom(event->pos().y()); + mMouseOverHandle = &mBHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mRHandle) { + if (event->pos().x() >= r.left()) { + r.setRight(event->pos().x()); + } else { + r.setRight(r.left()); + r.setLeft(event->pos().x()); + mMouseOverHandle = &mLHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mLHandle) { + if (event->pos().x() <= r.right()) { + r.setLeft(event->pos().x()); + } else { + r.setLeft(r.right()); + r.setRight(event->pos().x()); + mMouseOverHandle = &mRHandle; + } + r = r.normalized(); + } else if (mMouseOverHandle == &mBHandle) { + if (event->pos().y() >= r.top()) { + r.setBottom(event->pos().y()); + } else { + r.setBottom(r.top()); + r.setTop(event->pos().y()); + mMouseOverHandle = &mTHandle; + } + r = r.normalized(); + } + + mCropRect = r.normalized(); + setResizeCursors(); + } + + update(); + emitCropRectChangedSignals(); +} + +void KSCropRubberBand::mouseReleaseEvent(QMouseEvent *event) +{ + if (mMouseOverHandle == nullptr && mCropRect.contains(event->pos())) { + setCursor(Qt::OpenHandCursor); + } + + update(); + emitCropRectChangedSignals(); +} + +void KSCropRubberBand::hoverMoveEvent(QHoverEvent *event) +{ + for (auto r: mHandles) { + if (r->contains(event->pos())) { + mMouseOverHandle = r; + setResizeCursors(); + return; + } + } + + mMouseOverHandle = nullptr; + if (mCropRect.contains(event->pos())) { + setCursor(Qt::OpenHandCursor); + } else { + setCursor(Qt::CrossCursor); + } +} + +// utility functions + +void KSCropRubberBand::setResizeCursors() +{ + if (mMouseOverHandle == &mTLHandle || mMouseOverHandle == &mBRHandle) { + setCursor(Qt::SizeFDiagCursor); + } else if (mMouseOverHandle == &mTRHandle || mMouseOverHandle == &mBLHandle) { + setCursor(Qt::SizeBDiagCursor); + } else if (mMouseOverHandle == &mLHandle || mMouseOverHandle == &mRHandle) { + setCursor(Qt::SizeHorCursor); + } else if (mMouseOverHandle == &mTHandle || mMouseOverHandle == &mBHandle) { + setCursor(Qt::SizeVerCursor); + } +} + +inline void KSCropRubberBand::drawTriangle(QPainter *painter, const QColor &color, const QPoint &a, const QPoint &b, const QPoint &c) +{ + painter->save(); + painter->setClipping(false); + + QPainterPath path; + path.moveTo(a); + path.lineTo(b); + path.lineTo(c); + path.lineTo(a); + + painter->setPen(Qt::NoPen); + painter->fillPath(path, QBrush(color)); + + painter->setClipping(true); + painter->restore(); +} + +void KSCropRubberBand::updateHandles() +{ + QRect r = mCropRect; + + mTLHandle.moveTopLeft(r.topLeft()); + mTRHandle.moveTopRight(r.topRight()); + mBLHandle.moveBottomLeft(r.bottomLeft()); + mBRHandle.moveBottomRight(r.bottomRight()); + + mLHandle.moveTopLeft(QPoint(r.x(), r.y() + (r.height() / 2) - (mLHandle.height() / 2))); + mTHandle.moveTopLeft(QPoint(r.x() + (r.width() / 2) - (mTHandle.width() / 2), r.y())); + mRHandle.moveTopRight(QPoint(r.right(), r.y() + (r.height() / 2) - (mRHandle.height() / 2))); + mBHandle.moveBottomLeft(QPoint(r.x() + (r.width() / 2) - (mBHandle.width() / 2), r.bottom())); +} + +void KSCropRubberBand::drawHandles(QPainter *painter, const QColor &color) +{ + drawTriangle(painter, color, mTLHandle.topLeft(), mTLHandle.topRight(), mTLHandle.bottomLeft()); + drawTriangle(painter, color, mTRHandle.topRight(), mTRHandle.topLeft(), mTRHandle.bottomRight()); + drawTriangle(painter, color, mBLHandle.topLeft(), mBLHandle.bottomLeft(), mBLHandle.bottomRight()); + drawTriangle(painter, color, mBRHandle.topRight(), mBRHandle.bottomRight(), mBRHandle.bottomLeft()); + + drawTriangle(painter, color, mTHandle.topLeft(), mTHandle.topRight(), (mTHandle.bottomLeft() + mTHandle.bottomRight()) / 2); + drawTriangle(painter, color, mBHandle.bottomLeft(), mBHandle.bottomRight(), (mBHandle.topLeft() + mBHandle.topRight()) / 2); + drawTriangle(painter, color, mLHandle.topLeft(), mLHandle.bottomLeft(), (mLHandle.topRight() + mLHandle.bottomRight()) / 2); + drawTriangle(painter, color, mRHandle.topRight(), mRHandle.bottomRight(), (mRHandle.topLeft() + mRHandle.bottomLeft()) / 2); +} + +QPoint KSCropRubberBand::limitPointToRect(const QPoint &p, const QRect &r) const +{ + QPoint q; + q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right()); + q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom()); + return q; +} + +void KSCropRubberBand::emitCropRectChangedSignals() +{ + emit cropXChanged(mCropRect.x()); + emit cropYChanged(mCropRect.y()); + emit cropWidthChanged(mCropRect.width()); + emit cropHeightChanged(mCropRect.height()); +} diff --git a/src/Editor/KSCropRubberBand.h b/src/Editor/KSCropRubberBand.h new file mode 100644 index 0000000..72d7025 --- /dev/null +++ b/src/Editor/KSCropRubberBand.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 Luca Gugelmann + * Copyright (C) 2015 Boudhayan Gupta + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2 as + * published by the Free Software Foundation + * + * 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 Library 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. + */ + +#ifndef KSCROPRUBBERBAND_H +#define KSCROPRUBBERBAND_H + +#include +#include +#include +#include +#include + +class KSCropRubberBand : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(int cropX READ cropX WRITE setCropX NOTIFY cropXChanged) + Q_PROPERTY(int cropY READ cropY WRITE setCropY NOTIFY cropYChanged) + Q_PROPERTY(int cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged) + Q_PROPERTY(int cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged) + + public: + + explicit KSCropRubberBand(QQuickItem *parent = 0); + void paint(QPainter *painter) Q_DECL_OVERRIDE; + + int cropX() const; + int cropY() const; + int cropWidth() const; + int cropHeight() const; + + public slots: + + void setCropX(const int &x); + void setCropY(const int &y); + void setCropWidth(const int &width); + void setCropHeight(const int &height); + + signals: + + void cropXChanged(const int x); + void cropYChanged(const int y); + void cropWidthChanged(const int width); + void cropHeightChanged(const int height); + + protected: + + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void hoverMoveEvent(QHoverEvent *event) Q_DECL_OVERRIDE; + + private: + + QPoint limitPointToRect(const QPoint &p, const QRect &r) const; + void setResizeCursors(); + void updateHandles(); + void drawTriangle(QPainter *painter, const QColor &color, const QPoint &a, const QPoint &b, const QPoint &c); + void drawHandles(QPainter *painter, const QColor &color); + void emitCropRectChangedSignals(); + + QRect mCropRect; + QPoint mMoveDelta; + QRect mTLHandle, mTRHandle, mBLHandle, mBRHandle; + QRect mLHandle, mTHandle, mRHandle, mBHandle; + QList mHandles; + QRect *mMouseOverHandle; +}; + +#endif // KSCROPRUBBERBAND_H diff --git a/src/Editor/KSImageEditor.cpp b/src/Editor/KSImageEditor.cpp new file mode 100644 index 0000000..b0855c3 --- /dev/null +++ b/src/Editor/KSImageEditor.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "KSImageEditor.h" + +// the image provider first + +KSImageProvider::KSImageProvider(const QPixmap &pixmap) : + QQuickImageProvider(QQuickImageProvider::Pixmap), + mPixmap(pixmap) +{} + +QPixmap KSImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + Q_UNUSED(id); + + if (size) { + *size = mPixmap.size(); + } + + if (requestedSize.isEmpty()) { + return mPixmap; + } + return mPixmap.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); +} + +QPixmap KSImageProvider::pixmap() const +{ + return mPixmap; +} + +// now the editor + +KSImageEditor::KSImageEditor(const QPixmap &pixmap, QObject *parent) : + QObject(parent), + mQuickView(new QQuickView), + mImageProvider(new KSImageProvider(pixmap)) +{ + QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); +} + +KSImageEditor::~KSImageEditor() +{ + if (mQuickView->visibility() != QQuickView::Hidden) { + mQuickView->hide(); + } + + if (mQuickView) { + delete mQuickView; + } +} + +void KSImageEditor::init() +{ + // set up the KDeclarative bindings for the QQuickView + + KDeclarative::KDeclarative decl; + decl.setDeclarativeEngine(mQuickView->engine()); + decl.setupBindings(); + + // add in the screenshot imageprovider + + mQuickView->engine()->addImageProvider("screenshot", mImageProvider); + + qmlRegisterType("KSComponents", 2, 0, "KSCropRubberBand"); + + // set up the window + + mQuickView->setResizeMode(QQuickView::SizeViewToRootObject); + mQuickView->setFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); + mQuickView->setClearBeforeRendering(true); + mQuickView->setSource(QUrl("qrc:///RegionGrabber.qml")); + + // connect + + connect(mQuickView->engine(), &QQmlEngine::quit, this, &KSImageEditor::grabCanceled); + connect(mQuickView, &QQuickView::statusChanged, this, &KSImageEditor::waitForViewReady); +} + +void KSImageEditor::waitForViewReady(QQuickView::Status status) +{ + switch(status) { + case QQuickView::Ready: { + QQuickItem *rootItem = mQuickView->rootObject(); + + connect(rootItem, SIGNAL(editCanceled()), this, SIGNAL(grabCanceled())); + // fixme: connect(rootItem, SIGNAL(selectionConfirmed(int,int,int,int)), this, SLOT(selectConfirmedHandler(int,int,int,int))); + + mQuickView->showFullScreen(); + return; + } + case QQuickView::Error: + emit grabCanceled(); + return; + case QQuickView::Null: + case QQuickView::Loading: + return; + } +} + +void KSImageEditor::selectConfirmedHandler(int x, int y, int width, int height) +{ + mQuickView->hide(); + + QRect region(x, y, width, height); + emit grabAccepted(mImageProvider->pixmap().copy(region), region); +} + diff --git a/src/Editor/KSImageEditor.h b/src/Editor/KSImageEditor.h new file mode 100644 index 0000000..2ec41a4 --- /dev/null +++ b/src/Editor/KSImageEditor.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef KSIMAGEEDITOR_H +#define KSIMAGEEDITOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "KSCropRubberBand.h" + +class KSImageProvider : public QQuickImageProvider +{ + public: + + explicit KSImageProvider(const QPixmap &Pixmap); + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); + QPixmap pixmap() const; + + private: + + QPixmap mPixmap; +}; + +class KSImageEditor : public QObject +{ + Q_OBJECT + + public: + + explicit KSImageEditor(const QPixmap &pixmap, QObject *parent = 0); + ~KSImageEditor(); + + signals: + + void grabAccepted(const QPixmap &pixmap, const QRect ®ion); + void grabCanceled(); + + private slots: + + + void init(); + void waitForViewReady(QQuickView::Status status); + void selectConfirmedHandler(int x, int y, int width, int height); + + private: + + QQuickView *mQuickView; + KSImageProvider *mImageProvider; +}; + +#endif // KSIMAGEEDITOR_H + diff --git a/src/Editor/QmlResources/HighlightRectangle.qml b/src/Editor/QmlResources/HighlightRectangle.qml new file mode 100644 index 0000000..a8d7cfa --- /dev/null +++ b/src/Editor/QmlResources/HighlightRectangle.qml @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2015 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.4 +import QtQuick.Controls 1.3 + +Item { + id: selectionContainer; + + signal selectionCancelled; + signal selectionConfirmed(int x, int y, int width, int height); + + // resize helper functions + + property int initialTop; + property int initialLeft; + property int initialWidth; + property int initialHeight; + + function saveInitials() { + selectionContainer.initialHeight = selectionContainer.height; + selectionContainer.initialWidth = selectionContainer.width; + selectionContainer.initialTop = selectionContainer.y; + selectionContainer.initialLeft = selectionContainer.x; + } + + function resizeFromTop(curMouseY, oldMouseY) { + var newY = selectionContainer.y + (curMouseY - oldMouseY); + var newHeight = selectionContainer.initialHeight - (newY - selectionContainer.initialTop); + + if ((newY >= 0) && (newHeight > 0)) { + selectionContainer.y = newY; + selectionContainer.height = newHeight; + } + } + + function resizeFromLeft(curMouseX, oldMouseX) { + var newX = selectionContainer.x + (curMouseX - oldMouseX); + var newWidth = selectionContainer.initialWidth - (newX - selectionContainer.initialLeft); + + if ((newX >= 0) && (newWidth > 0)) { + selectionContainer.x = newX; + selectionContainer.width = newWidth; + } + } + + function resizeFromBottom(curMouseY, oldMouseY) { + var newHeight = selectionContainer.height + (curMouseY - oldMouseY); + if (newHeight > 0) { + selectionContainer.height = newHeight; + } + } + + function resizeFromRight(curMouseX, oldMouseX) { + var newWidth = selectionContainer.width + (curMouseX - oldMouseX); + if (newWidth > 0) { + selectionContainer.width = newWidth; + } + } + + // the selection rectangle and its mouse area + + Rectangle { + id: selectionRectangle; + + property int sizeGripBorderWidth: 8; + + color: Qt.rgba(1, 0.9, 0, 0.5); + border.color: Qt.rgba(1, 0.9, 0, 0.7); + border.width: 3; + + anchors.centerIn: parent; + anchors.fill: parent; + } + + MouseArea { + id: moveArea + + anchors.centerIn: parent; + height: parent.height - (2 * selectionRectangle.sizeGripBorderWidth); + width: parent.width - (2 * selectionRectangle.sizeGripBorderWidth); + + cursorShape: Qt.OpenHandCursor + + drag.target: parent + drag.axis: Drag.XAndYAxis + drag.minimumX: 0 + drag.minimumY: 0 + drag.maximumX: parent.parent.width - parent.width + drag.maximumY: parent.parent.height - parent.height + + onPressed: { + cursorShape = Qt.ClosedHandCursor; + } + + onReleased: { + cursorShape = Qt.OpenHandCursor; + } + } + + // resize grips + + MouseArea { + id: sizeGripTopMouseArea; + cursorShape: Qt.SizeVerCursor; + + anchors.top: parent.top; + anchors.horizontalCenter: parent.horizontalCenter; + + height: selectionRectangle.sizeGripBorderWidth; + width: parent.width - (2 * selectionRectangle.sizeGripBorderWidth); + + property int oldMouseY; + + onPressed: { + oldMouseY = mouseY; + saveInitials(); + } + + onPositionChanged: { + resizeFromTop(mouseY, oldMouseY); + } + } + + MouseArea { + id: sizeGripLeftMouseArea; + cursorShape: Qt.SizeHorCursor; + + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + + height: parent.height - (2 * selectionRectangle.sizeGripBorderWidth); + width: selectionRectangle.sizeGripBorderWidth; + + property int oldMouseX; + + onPressed: { + oldMouseX = mouseX; + saveInitials(); + } + + onPositionChanged: { + resizeFromLeft(mouseX, oldMouseX); + } + } + + MouseArea { + id: sizeGripRightMouseArea; + cursorShape: Qt.SizeHorCursor; + + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + + height: parent.height - (2 * selectionRectangle.sizeGripBorderWidth); + width: selectionRectangle.sizeGripBorderWidth; + + property int oldMouseX; + + onPressed: { + oldMouseX = mouseX; + } + + onPositionChanged: { + resizeFromRight(mouseX, oldMouseX); + } + } + + MouseArea { + id: sizeGripBottomMouseArea; + cursorShape: Qt.SizeVerCursor; + + anchors.bottom: parent.bottom; + anchors.horizontalCenter: parent.horizontalCenter; + + height: selectionRectangle.sizeGripBorderWidth; + width: parent.width - (2 * selectionRectangle.sizeGripBorderWidth); + + property int oldMouseY; + + onPressed: { + oldMouseY = mouseY; + } + + onPositionChanged: { + resizeFromBottom(mouseY, oldMouseY); + } + } + + Rectangle { + height: 20; + width: 20; + color: "transparent"; + + anchors.horizontalCenter: parent.left; + anchors.verticalCenter: parent.top; + + MouseArea { + id: sizeGripTopLeftMouseArea + anchors.fill: parent; + cursorShape: Qt.SizeFDiagCursor; + + property int oldMouseX; + property int oldMouseY; + + onPressed: { + oldMouseX = mouseX; + oldMouseY = mouseY; + saveInitials(); + } + + onPositionChanged: { + resizeFromLeft(mouseX, oldMouseX); + resizeFromTop(mouseY, oldMouseY); + } + } + } + + Rectangle { + height: 20; + width: 20; + color: "transparent"; + + anchors.horizontalCenter: parent.right; + anchors.verticalCenter: parent.top; + + MouseArea { + id: sizeGripTopRightMouseArea + anchors.fill: parent; + cursorShape: Qt.SizeBDiagCursor; + + property int oldMouseX; + property int oldMouseY; + + onPressed: { + oldMouseX = mouseX; + oldMouseY = mouseY; + saveInitials(); + } + + onPositionChanged: { + resizeFromRight(mouseX, oldMouseX); + resizeFromTop(mouseY, oldMouseY); + } + } + } + + Rectangle { + height: 20; + width: 20; + color: "transparent"; + + anchors.horizontalCenter: parent.left; + anchors.verticalCenter: parent.bottom; + + MouseArea { + id: sizeGripBottomLeftMouseArea + anchors.fill: parent; + cursorShape: Qt.SizeBDiagCursor; + + property int oldMouseX; + property int oldMouseY; + + onPressed: { + oldMouseX = mouseX; + oldMouseY = mouseY; + saveInitials(); + } + + onPositionChanged: { + resizeFromLeft(mouseX, oldMouseX); + resizeFromBottom(mouseY, oldMouseY); + } + } + } + + Rectangle { + height: 20; + width: 20; + color: "transparent"; + + anchors.horizontalCenter: parent.right; + anchors.verticalCenter: parent.bottom; + + MouseArea { + id: sizeGripBottomRightMouseArea + anchors.fill: parent; + cursorShape: Qt.SizeFDiagCursor; + + property int oldMouseX; + property int oldMouseY; + + onPressed: { + oldMouseX = mouseX; + oldMouseY = mouseY; + } + + onPositionChanged: { + resizeFromRight(mouseX, oldMouseX); + resizeFromBottom(mouseY, oldMouseY); + } + } + } + + // the buttons inside the rectangle + + Row { + id: buttonStore; + anchors.centerIn: selectionRectangle; + + opacity: 0.9; + + Button { action: exitAction; } + Button { action: doneAction; } + } + + Action { + id: exitAction; + text: i18n("Cancel"); + iconName: "dialog-close"; + enabled: true; + onTriggered: selectionCancelled(); + } + + Action { + id: doneAction; + text: i18n("Confirm"); + iconName: "dialog-ok"; + enabled: true; + onTriggered: selectionConfirmed(selectionContainer.x, selectionContainer.y, selectionContainer.width, selectionContainer.height); + } +} + diff --git a/src/Editor/QmlResources/KSDrawRectangleToolBar.qml b/src/Editor/QmlResources/KSDrawRectangleToolBar.qml new file mode 100644 index 0000000..4f28311 --- /dev/null +++ b/src/Editor/QmlResources/KSDrawRectangleToolBar.qml @@ -0,0 +1,147 @@ +import QtQuick 2.4 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 +import QtQuick.Dialogs 1.2 + +ToolBar { + id: drawRectangleToolbar; + + signal fillColorChanged(color newColor); + signal borderColorChanged(color newColor); + signal borderWidthChanged(int newWidth); + signal radiusChanged(int newRadius); + signal rectOpacityChanged(real newOpacity); + + width: toolBarLayout.width + 10; + height: toolBarLayout.height + 10; + opacity: 0.95; + + style: ToolBarStyle { + padding { left: 5; right: 5; top: 5; bottom: 5 } + background: Rectangle { color: "lightgrey"; } + } + + RowLayout { + id: toolBarLayout; + + Label { text: i18n("Rectangle Tool"); font.bold: true; + MouseArea { + anchors.fill: parent; + cursorShape: Qt.OpenHandCursor; + + drag { + target: drawRectangleToolbar; + axis: Drag.XAndYAxis; + minimumX: 0; + maximumX: drawRectangleToolbar.parent.width - drawRectangleToolbar.width; + minimumY: 0; + maximumY: drawRectangleToolbar.parent.height - drawRectangleToolbar.height; + } + + onPressed: { cursorShape = Qt.ClosedHandCursor; } + onReleased: { cursorShape = Qt.OpenHandCursor; } + } + } + Rectangle { width: 24; height: 1; opacity: 0; } + + Label { text: i18n("Fill Color: "); } + Rectangle { + id: fillColor; + color: "black"; + + border { + color: "black"; + width: 1; + } + + height: opacityInput.height; + width: opacityInput.height; + + onColorChanged: { fillColorChanged(color); } + + MouseArea { + anchors.fill: parent; + + onClicked: { + fillColorDialog.open(); + } + } + } + + Label { text: i18n("Border Color: "); } + Rectangle { + id: borderColor; + color: "black"; + + border { + color: "black"; + width: 1; + } + + height: opacityInput.height; + width: opacityInput.height; + + onColorChanged: { borderColorChanged(color); } + + MouseArea { + anchors.fill: parent; + + onClicked: { + borderColorDialog.open(); + } + } + } + + Rectangle { width: 16; height: 1; opacity: 0; } + + Label { text: i18n("Border Width: "); } + SpinBox { + id: borderWidthInput; + maximumValue: 1024; + + onValueChanged: { borderWidthChanged(value); } + } + + Label { text: i18n("Opacity: "); } + SpinBox { + id: opacityInput; + + decimals: 2; + stepSize: 0.1; + minimumValue: 0.0; + maximumValue: 1.0; + value: 1.0; + + onValueChanged: { rectOpacityChanged(value); } + } + + Label { text: i18n("Radius: "); } + SpinBox { + id: radiusInput; + maximumValue: 1024; + + onValueChanged: { radiusChanged(value); } + } + } + + ColorDialog { + id: fillColorDialog; + title: i18n("Please choose a fill color"); + showAlphaChannel: true; + + onAccepted: { + fillColor.color = color; + } + } + + ColorDialog { + id: borderColorDialog; + title: i18n("Please choose a border color"); + showAlphaChannel: true; + + onAccepted: { + borderColor.color = color; + } + } +} diff --git a/src/Editor/QmlResources/KSEditActionsToolBar.qml b/src/Editor/QmlResources/KSEditActionsToolBar.qml new file mode 100644 index 0000000..8e42754 --- /dev/null +++ b/src/Editor/QmlResources/KSEditActionsToolBar.qml @@ -0,0 +1,45 @@ +import QtQuick 2.4 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +ToolBar { + id: editActionsToolbar; + + signal done(); + signal cancel(); + signal toolSelected(string toolName); + + width: toolBarLayout.width + 10; + opacity: 0.9; + + style: ToolBarStyle { + padding { left: 5; right: 5; top: 5; bottom: 5 } + background: Rectangle { color: "lightgrey"; } + } + + ExclusiveGroup { + id: editToolGroup + + Action { id: cropTool; text: i18n("Crop"); iconName: "transform-crop"; checkable: true; checked: true; } + Action { id: rectangleTool; text: i18n("Draw Rectangle"); iconName: "draw-rectangle"; checkable: true; } + Action { id: lineTool; text: i18n("Draw Line"); iconName: "draw-line"; checkable: true; } + Action { id: brushTool; text: i18n("Draw Freehand"); iconName: "draw-freehand"; checkable: true } + Action { id: textTool; text: i18n("Write Text"); iconName: "draw-text"; checkable: true; } + } + + ColumnLayout { + id: toolBarLayout; + + ToolButton { id: cropButton; action: cropTool; onClicked: toolSelected("crop"); } + ToolButton { id: rectangleButton; action: rectangleTool; onClicked: toolSelected("rectangle"); } + ToolButton { id: lineButton; action: lineTool; onClicked: toolSelected("line"); } + ToolButton { id: brushButton; action: brushTool; onClicked: toolSelected("brush"); } + ToolButton { id: textButton; action: textTool; onClicked: toolSelected("text"); } + + Rectangle { width: cropButton.width; height: 1; color: "darkgrey"; } + + ToolButton { text: i18n("Done"); tooltip: i18n("Done"); iconName: "dialog-ok"; onClicked: done(); } + ToolButton { text: i18n("Cancel"); tooltip: i18n("Cancel"); iconName: "dialog-cancel"; onClicked: cancel(); } + } +} diff --git a/src/Editor/QmlResources/KSRectangleTool.qml b/src/Editor/QmlResources/KSRectangleTool.qml new file mode 100644 index 0000000..68b8436 --- /dev/null +++ b/src/Editor/QmlResources/KSRectangleTool.qml @@ -0,0 +1,70 @@ +import QtQuick 2.4 +import "qml:///KSDrawRectangleToolBar.qml" + +MouseArea { + id: selectArea + anchors.fill: parent + cursorShape: Qt.CrossCursor + + property var parentItem: null; + property var highlightItem: null; + property int initialX: 0 + property int initialY: 0 + + property int rectRadius: 0; + property int rectBorderWidth: 0; + property real rectOpacity: 1.0; + property color rectFillColor: "black"; + property color rectBorderColor: "black"; + + onPressed: { + initialX = mouse.x; + initialY = mouse.y; + + highlightItem = highlightRectangle.createObject(parentItem, { + "x" : mouse.x, + "y" : mouse.y, + "color" : rectFillColor, + "radius" : rectRadius, + "opacity" : rectOpacity, + "border.width" : rectBorderWidth, + "border.color" : rectBorderColor + }); + } + + onPositionChanged: { + if (mouse.x < initialX) { + highlightItem.x = mouse.x; + highlightItem.width = (initialX - mouse.x); + } else { + highlightItem.width = (Math.abs (mouse.x - highlightItem.x)); + } + + if (mouse.y < initialY) { + highlightItem.y = mouse.y; + highlightItem.height = (initialY - mouse.y); + } else { + highlightItem.height = (Math.abs (mouse.y - highlightItem.y)); + } + } + + onReleased: { + highlightItem = null; + } + + KSDrawRectangleToolBar { + x: 20; + y: 20; + + onRectOpacityChanged: { selectArea.rectOpacity = newOpacity; } + onFillColorChanged: { selectArea.rectFillColor = newColor; } + onBorderColorChanged: { selectArea.rectBorderColor = newColor; } + onRadiusChanged: { selectArea.rectRadius = newRadius; } + onBorderWidthChanged: { selectArea.rectBorderWidth = newWidth; } + } + + Component { + id: highlightRectangle + Rectangle {} + } +} diff --git a/src/Editor/QmlResources/QmlResources.qrc b/src/Editor/QmlResources/QmlResources.qrc new file mode 100644 index 0000000..28f1d08 --- /dev/null +++ b/src/Editor/QmlResources/QmlResources.qrc @@ -0,0 +1,9 @@ + + + HighlightRectangle.qml + RegionGrabber.qml + KSEditActionsToolBar.qml + KSDrawRectangleToolBar.qml + KSRectangleTool.qml + + diff --git a/src/Editor/QmlResources/RegionGrabber.qml b/src/Editor/QmlResources/RegionGrabber.qml new file mode 100644 index 0000000..da27bb0 --- /dev/null +++ b/src/Editor/QmlResources/RegionGrabber.qml @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.4 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.3 +import QtQuick.Dialogs 1.2 + +import KSComponents 2.0 + +Image { + id: rootItem; + + signal editCanceled; + signal editAccepted; + + property var toolOptionsToolbar: null; + property var currentGraphicElement: null; + + + focus: true; + Keys.onEscapePressed: editCanceled(); + + source: "image://screenshot/image"; + + Component { + id: toolBarComponent; + + ColorDialog { + id: colorDialog + title: "Please choose a color" + onAccepted: { + console.log("You chose: " + colorDialog.color) + Qt.quit() + } + onRejected: { + console.log("Canceled") + Qt.quit() + } + Component.onCompleted: visible = true + } + } + + Item { + id: workArea; + anchors.fill: parent; + + property var tool: null; + } + + Component { id: rectangleTool; KSRectangleTool {} } + + KSEditActionsToolBar { + id: editActionsToolbar; + + anchors.bottom: parent.bottom; + anchors.right: parent.right; + anchors.margins: 10; + + onDone: editAccepted(); + onCancel: editCanceled(); + + onToolSelected: { + if (workArea.tool !== null) { + workArea.tool.destroy(); + } + + if (toolName == "rectangle") { + workArea.tool = rectangleTool.createObject(workArea, {"parentItem" : workArea}); + } + } + } +} diff --git a/src/PlatformBackends/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h index ecb3925..679b039 100644 --- a/src/PlatformBackends/ImageGrabber.h +++ b/src/PlatformBackends/ImageGrabber.h @@ -1,96 +1,96 @@ /* * Copyright (C) 2015 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. */ #ifndef IMAGEGRABBER_H #define IMAGEGRABBER_H #include #include #include #include #include -#include "ScreenClipper.h" +#include "Editor/KSImageEditor.h" class ImageGrabber : public QObject { Q_OBJECT Q_ENUMS(GrabMode) Q_PROPERTY(QPixmap pixmap READ pixmap NOTIFY pixmapChanged) Q_PROPERTY(bool capturePointer READ capturePointer WRITE setCapturePointer NOTIFY capturePointerChanged) Q_PROPERTY(bool captureDecorations READ captureDecorations WRITE setCaptureDecorations NOTIFY captureDecorationsChanged) Q_PROPERTY(GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) public: enum GrabMode { InvalidChoice = -1, FullScreen = 0, CurrentScreen = 1, ActiveWindow = 2, WindowUnderCursor = 3, TransientWithParent = 4, RectangularRegion = 5 }; explicit ImageGrabber(QObject *parent = 0); ~ImageGrabber(); QPixmap pixmap() const; bool capturePointer() const; bool captureDecorations() const; GrabMode grabMode() const; virtual bool onClickGrabSupported() const; void setCapturePointer(const bool newCapturePointer); void setCaptureDecorations(const bool newCaptureDecorations); void setGrabMode(const GrabMode newGrabMode); signals: void pixmapChanged(const QPixmap pixmap); void imageGrabFailed(); void capturePointerChanged(bool capturePointer); void captureDecorationsChanged(bool captureDecorations); void grabModeChanged(GrabMode grabMode); public slots: virtual void doImageGrab(); virtual void doOnClickGrab(); protected: virtual void grabFullScreen() = 0; virtual void grabCurrentScreen() = 0; virtual void grabActiveWindow() = 0; virtual void grabRectangularRegion() = 0; virtual void grabWindowUnderCursor() = 0; virtual void grabTransientWithParent() = 0; virtual QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) = 0; bool mCapturePointer; bool mCaptureDecorations; GrabMode mGrabMode; QPixmap mPixmap; }; #endif // IMAGEGRABBER_H diff --git a/src/PlatformBackends/X11ImageGrabber.cpp b/src/PlatformBackends/X11ImageGrabber.cpp index 78284be..fbd6344 100644 --- a/src/PlatformBackends/X11ImageGrabber.cpp +++ b/src/PlatformBackends/X11ImageGrabber.cpp @@ -1,683 +1,683 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * Contains code from kxutils.cpp, part of KWindowSystem. Copyright * notices reproduced below: * * Copyright (C) 2008 Lubos Lunak (l.lunak@kde.org) * Copyright (C) 2013 Martin Gräßlin * * 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. */ #include "X11ImageGrabber.h" X11ImageGrabber::X11ImageGrabber(QObject *parent) : ImageGrabber(parent), mScreenConfigOperation(nullptr) { mNativeEventFilter = new OnClickEventFilter(this); } X11ImageGrabber::~X11ImageGrabber() { delete mNativeEventFilter; } // for onClick grab OnClickEventFilter::OnClickEventFilter(X11ImageGrabber *grabber) : QAbstractNativeEventFilter(), mImageGrabber(grabber) {} bool OnClickEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *ev = static_cast(message); switch (ev->response_type & ~0x80) { case XCB_BUTTON_RELEASE: // uninstall the eventfilter and release the mouse qApp->removeNativeEventFilter(this); xcb_ungrab_pointer(QX11Info::connection(), XCB_TIME_CURRENT_TIME); // decide whether to grab or abort. regrab the mouse // on mouse-wheel events { xcb_button_release_event_t *ev2 = static_cast(message); qDebug() << ev2->detail; if (ev2->detail == 1) { QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } else if (ev2->detail < 4) { emit mImageGrabber->imageGrabFailed(); } else { QMetaObject::invokeMethod(mImageGrabber, "doOnClickGrab", Qt::QueuedConnection); } } return true; default: return false; } } return false; } bool X11ImageGrabber::onClickGrabSupported() const { return true; } void X11ImageGrabber::doOnClickGrab() { // get the cursor image xcb_cursor_t xcbCursor = XCB_CURSOR_NONE; xcb_cursor_context_t *xcbCursorCtx; xcb_screen_t *xcbAppScreen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); if (xcb_cursor_context_new(QX11Info::connection(), xcbAppScreen, &xcbCursorCtx) >= 0) { QVector cursorNames = { QByteArrayLiteral("cross"), QByteArrayLiteral("crosshair"), QByteArrayLiteral("diamond-cross"), QByteArrayLiteral("cross-reverse") }; for (auto cursorName: cursorNames) { xcb_cursor_t cursor = xcb_cursor_load_cursor(xcbCursorCtx, cursorName.constData()); if (cursor != XCB_CURSOR_NONE) { xcbCursor = cursor; break; } } } // grab the cursor xcb_grab_pointer_cookie_t grabPointerCookie = xcb_grab_pointer_unchecked( QX11Info::connection(), // xcb connection 0, // deliver events to owner? nope QX11Info::appRootWindow(), // window to grab pointer for (root) XCB_EVENT_MASK_BUTTON_RELEASE, // which events do I want XCB_GRAB_MODE_SYNC, // pointer grab mode XCB_GRAB_MODE_ASYNC, // keyboard grab mode (why is this even here) XCB_NONE, // confine pointer to which window (none) xcbCursor, // cursor to change to for the duration of grab XCB_TIME_CURRENT_TIME // do this right now ); CScopedPointer grabPointerReply(xcb_grab_pointer_reply(QX11Info::connection(), grabPointerCookie, NULL)); // if the grab failed, take the screenshot right away if (grabPointerReply->status != XCB_GRAB_STATUS_SUCCESS) { return doImageGrab(); } // fix things if our pointer grab causes a lockup xcb_allow_events(QX11Info::connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); // and install our event filter qApp->installNativeEventFilter(mNativeEventFilter); // done. clean stuff up xcb_cursor_context_free(xcbCursorCtx); xcb_free_cursor(QX11Info::connection(), xcbCursor); } // image conversion routine QPixmap X11ImageGrabber::convertFromNative(xcb_image_t *xcbImage) { QImage::Format format = QImage::Format_Invalid; switch (xcbImage->depth) { case 1: format = QImage::Format_MonoLSB; break; case 16: format = QImage::Format_RGB16; break; case 24: format = QImage::Format_RGB32; break; case 30: { // Qt doesn't have a matching image format. We need to convert manually quint32 *pixels = reinterpret_cast(xcbImage->data); for (uint i = 0; i < (xcbImage->size / 4); i++) { int r = (pixels[i] >> 22) & 0xff; int g = (pixels[i] >> 12) & 0xff; int b = (pixels[i] >> 2) & 0xff; pixels[i] = qRgba(r, g, b, 0xff); } // fall through, Qt format is still Format_ARGB32_Premultiplied } case 32: format = QImage::Format_ARGB32_Premultiplied; break; default: return QPixmap(); // we don't know } QImage image(xcbImage->data, xcbImage->width, xcbImage->height, format); if (image.isNull()) { return QPixmap(); } // work around an abort in QImage::color if (image.format() == QImage::Format_MonoLSB) { image.setColorCount(2); image.setColor(0, QColor(Qt::white).rgb()); image.setColor(1, QColor(Qt::black).rgb()); } // done return QPixmap::fromImage(image); } // utility functions QPixmap X11ImageGrabber::blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) { // first we get the cursor position, compute the co-ordinates of the region // of the screen we're grabbing, and see if the cursor is actually visible in // the region QPoint cursorPos = QCursor::pos(); QRect screenRect(x, y, width, height); if (!(screenRect.contains(cursorPos))) { return pixmap; } // now we can get the image and start processing xcb_connection_t *xcbConn = QX11Info::connection(); xcb_xfixes_get_cursor_image_cookie_t cursorCookie = xcb_xfixes_get_cursor_image_unchecked(xcbConn); CScopedPointer cursorReply(xcb_xfixes_get_cursor_image_reply(xcbConn, cursorCookie, NULL)); if (cursorReply.isNull()) { return pixmap; } quint32 *pixelData = xcb_xfixes_get_cursor_image_cursor_image(cursorReply.data()); if (!pixelData) { return pixmap; } // process the image into a QImage QImage cursorImage = QImage((quint8 *)pixelData, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied); // a small fix for the cursor position for fancier cursors cursorPos -= QPoint(cursorReply->xhot, cursorReply->yhot); // now we translate the cursor point to our screen rectangle cursorPos -= QPoint(x, y); // and do the painting QPixmap blendedPixmap = pixmap; QPainter painter(&blendedPixmap); painter.drawImage(cursorPos, cursorImage); // and done return blendedPixmap; } QPixmap X11ImageGrabber::getWindowPixmap(xcb_window_t window, bool blendPointer) { xcb_connection_t *xcbConn = QX11Info::connection(); // first get geometry information for our drawable xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); // then proceed to get an image CScopedPointer xcbImage( xcb_image_get( xcbConn, window, geomReply->x, geomReply->y, geomReply->width, geomReply->height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP ) ); // if the image is null, this means we need to get the root image window // and run a crop if (xcbImage.isNull()) { return getWindowPixmap(QX11Info::appRootWindow(), blendPointer) .copy(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } // now process the image QPixmap nativePixmap = convertFromNative(xcbImage.data()); if (!(blendPointer)) { return nativePixmap; } // now we blend in a pointer image xcb_get_geometry_cookie_t geomRootCookie = xcb_get_geometry_unchecked(xcbConn, geomReply->root); CScopedPointer geomRootReply(xcb_get_geometry_reply(xcbConn, geomRootCookie, NULL)); xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates_unchecked( xcbConn, window, geomReply->root, geomRootReply->x, geomRootReply->y); CScopedPointer translateReply( xcb_translate_coordinates_reply(xcbConn, translateCookie, NULL)); return blendCursorImage(nativePixmap, translateReply->dst_x,translateReply->dst_y, geomReply->width, geomReply->height); } bool X11ImageGrabber::isKWinAvailable() { if (QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.KWin")) { QDBusInterface interface("org.kde.KWin", "/Effects", "org.kde.kwin.Effects"); QDBusReply reply = interface.call("isEffectLoaded", "screenshot"); return reply.value(); } return false; } void X11ImageGrabber::KWinDBusScreenshotHelper(quint64 window) { mPixmap = getWindowPixmap((xcb_window_t)window, false); emit pixmapChanged(mPixmap); } void X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper(KScreen::ConfigOperation *op) { KScreen::ConfigPtr config = qobject_cast(op)->config(); if (!config) { return grabFullScreen(); } if (!config->screen()) { return grabFullScreen(); } // we'll store the cursor position first QPoint cursorPosition = QCursor::pos(); // next, we'll get all our outputs and figure out which one has the cursor const KScreen::OutputList outputs = config->outputs(); for (auto output: outputs) { if (!(output->isConnected())) { continue; } if (!(output->currentMode())) { continue; } QPoint screenPosition = output->pos(); QSize screenSize = output->currentMode()->size(); QRect screenRect = QRect(screenPosition, screenSize); if (!(screenRect.contains(cursorPosition))) { continue; } // bingo, we've found an output that contains the cursor. Now // to take a shot mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); mPixmap = mPixmap.copy(screenPosition.x(), screenPosition.y(), screenSize.width(), screenSize.height()); emit pixmapChanged(mPixmap); mScreenConfigOperation->disconnect(); mScreenConfigOperation->deleteLater(); mScreenConfigOperation = nullptr; return; } mScreenConfigOperation->disconnect(); mScreenConfigOperation->deleteLater(); mScreenConfigOperation = nullptr; return grabFullScreen(); } void X11ImageGrabber::rectangleSelectionCancelled() { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); emit imageGrabFailed(); } void X11ImageGrabber::rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion) { QObject *sender = QObject::sender(); sender->disconnect(); sender->deleteLater(); if (mCapturePointer) { mPixmap = blendCursorImage(pixmap, region.x(), region.y(), region.width(), region.height()); } else { mPixmap = pixmap; } emit pixmapChanged(mPixmap); } // grabber methods void X11ImageGrabber::grabFullScreen() { mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer); emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabTransientWithParent() { xcb_window_t curWin = getRealWindowUnderCursor(); // do we have a top-level or a transient window? KWindowInfo info(curWin, NET::WMName, NET::WM2TransientFor | NET::WM2WindowClass); if (!(info.valid(true) && (info.transientFor() != XCB_WINDOW_NONE)) || info.windowClassClass().isEmpty() || info.windowClassName().isEmpty()) { return grabWindowUnderCursor(); } // grab the image early mPixmap = getWindowPixmap(QX11Info::appRootWindow(), false); // now that we know we have a transient window, let's // see if the parent has any other transient windows who's // transient for the same app QRegion clipRegion; QStack childrenStack = findAllChildren(findParent(curWin)); while (!(childrenStack.isEmpty())) { xcb_window_t winId = childrenStack.pop(); KWindowInfo tempInfo(winId, 0, NET::WM2TransientFor); if (info.transientFor() == tempInfo.transientFor()) { clipRegion += getApplicationWindowGeometry(winId); } } // now we have a list of all the transient windows for the // parent, time to find the parent QList winList = KWindowSystem::stackingOrder(); for (int i = winList.size() - 1; i >= 0; i--) { KWindowInfo tempInfo(winList[i], NET::WMGeometry | NET::WMFrameExtents, NET::WM2WindowClass); QString winClass(tempInfo.windowClassClass()); QString winName(tempInfo.windowClassName()); if (winClass.contains(info.name(), Qt::CaseInsensitive) || winName.contains(info.name(), Qt::CaseInsensitive)) { if (mCaptureDecorations) { clipRegion += tempInfo.frameGeometry(); } else { clipRegion += tempInfo.geometry(); } break; } } // we can probably go ahead and generate the image now QImage tempImage(mPixmap.size(), QImage::Format_ARGB32); tempImage.fill(Qt::transparent); QPainter tempPainter(&tempImage); tempPainter.setClipRegion(clipRegion); tempPainter.drawPixmap(0, 0, mPixmap); tempPainter.end(); mPixmap = QPixmap::fromImage(tempImage).copy(clipRegion.boundingRect()); // why stop here, when we can render a 20px drop shadow all around it QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect; effect->setOffset(0); effect->setBlurRadius(20); QGraphicsPixmapItem *item = new QGraphicsPixmapItem; item->setPixmap(mPixmap); item->setGraphicsEffect(effect); QImage shadowImage(mPixmap.size() + QSize(40, 40), QImage::Format_ARGB32); shadowImage.fill(Qt::transparent); QPainter shadowPainter(&shadowImage); QGraphicsScene scene; scene.addItem(item); scene.render(&shadowPainter, QRectF(), QRectF(-20, -20, mPixmap.width() + 40, mPixmap.height() + 40)); shadowPainter.end(); // we can finish up now mPixmap = QPixmap::fromImage(shadowImage); if (mCapturePointer) { QPoint topLeft = clipRegion.boundingRect().topLeft() - QPoint(20, 20); mPixmap = blendCursorImage(mPixmap, topLeft.x(), topLeft.y(), mPixmap.width(), mPixmap.height()); } emit pixmapChanged(mPixmap); } void X11ImageGrabber::grabActiveWindow() { xcb_window_t activeWindow = KWindowSystem::activeWindow(); // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect("org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot", "screenshotCreated", this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface("org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot"); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call("screenshotForWindow", (quint64)activeWindow, mask); return; } // otherwise, use the native functionality return grabApplicationWindowHelper(activeWindow); } void X11ImageGrabber::grabWindowUnderCursor() { // if KWin is available, use the KWin DBus interfaces if (mCaptureDecorations && isKWinAvailable()) { QDBusConnection bus = QDBusConnection::sessionBus(); bus.connect("org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot", "screenshotCreated", this, SLOT(KWinDBusScreenshotHelper(quint64))); QDBusInterface interface("org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot"); int mask = 1; if (mCapturePointer) { mask |= 1 << 1; } interface.call("screenshotWindowUnderCursor", mask); return; } // else, go native return grabApplicationWindowHelper(getRealWindowUnderCursor()); } void X11ImageGrabber::grabApplicationWindowHelper(xcb_window_t window) { // if the user doesn't want decorations captured, we're in luck. This is // the easiest bit mPixmap = getWindowPixmap(window, mCapturePointer); if (!mCaptureDecorations || window == QX11Info::appRootWindow()) { emit pixmapChanged(mPixmap); return; } // if the user wants the window decorations, things get a little tricky. // we can't simply get a handle to the window manager frame window and // just grab it, because some compositing window managers (yes, kwin // included) do not render the window onto the frame but keep it in a // separate opengl buffer, so grabbing this window is going to simply // give us a transparent image with the frame and titlebar. // all is not lost. what we need to do is grab the image of the entire // desktop, find the geometry of the window including its frame, and // crop the root image accordingly. KWindowInfo info(window, NET::WMFrameExtents); if (info.valid()) { QRect frameGeom = info.frameGeometry(); mPixmap = getWindowPixmap(QX11Info::appRootWindow(), mCapturePointer).copy(frameGeom); } // fallback is window without the frame emit pixmapChanged(mPixmap); } QRect X11ImageGrabber::getApplicationWindowGeometry(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry_unchecked(xcbConn, window); CScopedPointer geomReply(xcb_get_geometry_reply(xcbConn, geomCookie, NULL)); return QRect(geomReply->x, geomReply->y, geomReply->width, geomReply->height); } void X11ImageGrabber::grabCurrentScreen() { mScreenConfigOperation = new KScreen::GetConfigOperation; connect(mScreenConfigOperation, &KScreen::GetConfigOperation::finished, this, &X11ImageGrabber::KScreenCurrentMonitorScreenshotHelper); } void X11ImageGrabber::grabRectangularRegion() { - ScreenClipper *clipper = new ScreenClipper(getWindowPixmap(QX11Info::appRootWindow(), false)); + KSImageEditor *editor = new KSImageEditor(getWindowPixmap(QX11Info::appRootWindow(), false)); - connect(clipper, &ScreenClipper::regionGrabbed, this, &X11ImageGrabber::rectangleSelectionConfirmed); - connect(clipper, &ScreenClipper::regionCancelled, this, &X11ImageGrabber::rectangleSelectionCancelled); + connect(editor, &KSImageEditor::grabAccepted, this, &X11ImageGrabber::rectangleSelectionConfirmed); + connect(editor, &KSImageEditor::grabCanceled, this, &X11ImageGrabber::rectangleSelectionCancelled); } xcb_window_t X11ImageGrabber::getRealWindowUnderCursor() { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_window_t curWin = QX11Info::appRootWindow(); const QByteArray atomName("WM_STATE"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, 0, atomName.length(), atomName.constData()); xcb_query_pointer_cookie_t pointerCookie = xcb_query_pointer_unchecked(xcbConn, curWin); CScopedPointer atomReply(xcb_intern_atom_reply(xcbConn, atomCookie, NULL)); CScopedPointer pointerReply(xcb_query_pointer_reply(xcbConn, pointerCookie, NULL)); if (atomReply->atom == XCB_ATOM_NONE) { return QX11Info::appRootWindow(); } // now start testing QStack windowStack; windowStack.push(pointerReply->child); while (!windowStack.isEmpty()) { curWin = windowStack.pop(); // next, check if our window has the WM_STATE peoperty set on // the window. if yes, return the window - we have found it xcb_get_property_cookie_t propertyCookie = xcb_get_property_unchecked(xcbConn, 0, curWin, atomReply->atom, XCB_ATOM_ANY, 0, 0); CScopedPointer propertyReply(xcb_get_property_reply(xcbConn, propertyCookie, NULL)); if (propertyReply->type != XCB_ATOM_NONE) { return curWin; } // if we're here, this means the window is not the real window // we should start looking at its children xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, curWin); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { windowStack.push(winChildren[i]); } } // return the window. it has geometry information for a crop return pointerReply->child; } QStack X11ImageGrabber::findAllChildren(xcb_window_t window) { QStack winStack; xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); xcb_window_t *winChildren = xcb_query_tree_children(treeReply.data()); int winChildrenLength = xcb_query_tree_children_length(treeReply.data()); for (int i = winChildrenLength - 1; i >= 0; i--) { winStack.push(winChildren[i]); } return winStack; } xcb_window_t X11ImageGrabber::findParent(xcb_window_t window) { xcb_connection_t *xcbConn = QX11Info::connection(); xcb_query_tree_cookie_t treeCookie = xcb_query_tree_unchecked(xcbConn, window); CScopedPointer treeReply(xcb_query_tree_reply(xcbConn, treeCookie, NULL)); return treeReply->parent; } diff --git a/src/ScreenClipper.cpp b/src/ScreenClipper.cpp deleted file mode 100644 index 4e99f55..0000000 --- a/src/ScreenClipper.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2007 Luca Gugelmann - * Copyright (C) 2015 Boudhayan Gupta - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License version 2 as - * published by the Free Software Foundation - * - * 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 Library 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. - */ - -#include "ScreenClipper.h" - -ScreenClipper::ScreenClipper(const QPixmap &pixmap) : - QRasterWindow(0), - grabbing(false), - mSelection(QRect()), - mMouseOverHandle(0), - mPixmap(pixmap) -{ - mTLHandle = mTRHandle = mBLHandle = mBRHandle = QRect(0, 0, 15, 15); - mLHandle = mRHandle = QRect(0, 0, 10, 20); - mTHandle = mBHandle = QRect(0, 0, 20, 10); - mHandles = { &mTLHandle, &mTRHandle, &mBLHandle, &mBRHandle, &mLHandle, &mTHandle, &mRHandle, &mBHandle }; - - setFlags(Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); - QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); -} - -ScreenClipper::~ScreenClipper() -{} - -void ScreenClipper::init() -{ - setGeometry(0, 0, mPixmap.width(), mPixmap.height()); - setCursor(Qt::CrossCursor); - showFullScreen(); -} - -inline void ScreenClipper::drawTriangle(QPainter *painter, const QColor &color, const QPoint &a, const QPoint &b, const QPoint &c) -{ - painter->save(); - - QPainterPath path; - path.moveTo(a); - path.lineTo(b); - path.lineTo(c); - path.lineTo(a); - - painter->setPen(Qt::NoPen); - painter->fillPath(path, QBrush(color)); - - painter->restore(); -} - -void ScreenClipper::paintEvent(QPaintEvent *e) -{ - Q_UNUSED(e); - if (grabbing) { // grabWindow() should just get the background - return; - } - - // start by initialising the QPainter and drawing the image on it - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.drawPixmap(0, 0, mPixmap); - - // set up the colors and fonts - - QPalette palette(QToolTip::palette()); - - QColor textColor = palette.color(QPalette::Active, QPalette::Text); - QColor textBackgroundColor = palette.color(QPalette::Active, QPalette::Base); - QColor handleColor = palette.color(QPalette::Active, QPalette::Highlight); - QColor overlayColor(0, 0, 0); - - overlayColor.setAlphaF(0.5); - textBackgroundColor.setAlphaF(0.85); - - QFont font = QToolTip::font(); - font.setBold(true); - font.setPointSize(10); - painter.setFont(font); - - // if we don't have a selection yet, just draw a semitransparent - // black rectangle over the whole screen, and render the help text - - if (mSelection.isNull() || mSelection.isEmpty()) { - painter.setClipRegion(QRegion(geometry())); - painter.setPen(Qt::NoPen); - painter.setBrush(overlayColor); - painter.drawRect(geometry()); - - painter.setPen(textColor); - painter.setBrush(textBackgroundColor); - - QString helpText = i18n("Click anywhere on the screen (including on this text) to start drawing a selection rectangle, or press Esc to quit"); - QRect helpTextBoundingBox = painter.boundingRect(geometry(), Qt::TextWordWrap, helpText); - helpTextBoundingBox.moveCenter(geometry().center()); - QRect helpTextRect = helpTextBoundingBox.adjusted(-20, -20, 20, 20); - - painter.setPen(textColor); - painter.setBrush(textBackgroundColor); - painter.drawRoundedRect(helpTextRect, 10, 10); - painter.drawText(helpTextBoundingBox, helpText); - - return; - } - - // if we're here, this means we have a valid selection. let's draw - // the overlay first - - QRegion region = QRegion(geometry()).subtracted(mSelection); - painter.setClipRegion(region); - painter.setPen(Qt::NoPen); - painter.setBrush(overlayColor); - painter.drawRect(geometry()); - - // and the selection rectangle border - - region = QRegion(mSelection).subtracted(mSelection.adjusted(1, 1, -1, -1)); - painter.setBrush(handleColor); - painter.setClipRegion(region); - painter.drawRect(mSelection); - painter.setClipRect(geometry()); - - // draw the handles - - updateHandles(); - if ((mSelection.height() > 20) && (mSelection.width() > 20)) { - drawHandles(&painter, handleColor); - } - - // render the help text - - painter.setPen(textColor); - painter.setBrush(textBackgroundColor); - - QString helpText = i18n("To take the screenshot, double-click or press Enter. Right-click to reset the selection, or press Esc to quit."); - QRect helpTextBoundingBox = painter.boundingRect(geometry(), Qt::TextWordWrap, helpText); - helpTextBoundingBox.moveCenter(geometry().center()); - helpTextBoundingBox.moveTop(geometry().top()); - QRect helpTextRect = helpTextBoundingBox.adjusted(-5, 0, 5, 10); - helpTextBoundingBox.moveCenter(helpTextRect.center()); - - painter.setPen(Qt::NoPen); - painter.drawRect(helpTextRect); - - QPoint a = helpTextRect.topLeft(); - QPoint b = helpTextRect.bottomLeft() + QPoint(0, 1); - QPoint c = QPoint(helpTextRect.left() - helpTextRect.height(), helpTextRect.top()); - drawTriangle(&painter, textBackgroundColor, a, b, c); - - a = helpTextRect.topRight() + QPoint(1, 0); - b = helpTextRect.bottomRight() + QPoint(1, 1); - c = QPoint(helpTextRect.right() + helpTextRect.height(), helpTextRect.top()); - drawTriangle(&painter, textBackgroundColor, a, b, c); - - painter.setPen(textColor); - painter.drawText(helpTextBoundingBox, helpText); - -} - -void ScreenClipper::resizeEvent(QResizeEvent *e) -{ - Q_UNUSED(e); - - if (mSelection.isNull()) { - return; - } - - QRect r = mSelection; - r.setTopLeft(limitPointToRect(r.topLeft(), geometry())); - r.setBottomRight(limitPointToRect(r.bottomRight(), geometry())); - - if (r.width() <= 1 || r.height() <= 1) { // this just results in ugly drawing... - mSelection = QRect(); - } else { - mSelection = mSelection.normalized(); - } -} - -void ScreenClipper::mousePressEvent(QMouseEvent *e) -{ - if (e->button() == Qt::LeftButton) { - if (!mSelection.contains(e->pos())) { - mSelection = QRect(e->pos(), e->pos()); - mMouseOverHandle = &mBRHandle; - } else { - mMoveDelta = e->pos() - mSelection.topLeft(); - setCursor(Qt::ClosedHandCursor); - } - } else if (e->button() == Qt::RightButton) { - mSelection = QRect(); - setCursor(Qt::CrossCursor); - } - update(); -} - -void ScreenClipper::mouseMoveEvent(QMouseEvent *e) -{ - if (e->buttons() & Qt::LeftButton) { - if (mMouseOverHandle == nullptr) { // moving the whole selection - QRect r = geometry(); - r.setBottomRight(r.bottomRight() - QPoint(mSelection.width(), mSelection.height()) + QPoint(1, 1)); - if (!(r.isNull() || r.isEmpty()) && r.isValid()) { - const QPoint newTopLeft = limitPointToRect(e->pos() - mMoveDelta, r); - if (newTopLeft == mSelection.topLeft()) { - mMoveDelta = e->pos() - mSelection.topLeft(); - } else { - mSelection.moveTo(newTopLeft); - } - } - } else { // dragging a handle - QRect r = mSelection; - - if (mMouseOverHandle == &mTLHandle) { - if (e->pos().x() <= r.right() && e->pos().y() <= r.bottom()) { - r.setTopLeft(e->pos()); - } else if (e->pos().x() <= r.right() && e->pos().y() > r.bottom()) { - r.setLeft(e->pos().x()); - r.setTop(r.bottom()); - r.setBottom(e->pos().y()); - mMouseOverHandle = &mBLHandle; - } else if (e->pos().x() > r.right() && e->pos().y() <= r.bottom()) { - r.setTop(e->pos().y()); - r.setLeft(r.right()); - r.setRight(e->pos().x()); - mMouseOverHandle = &mTRHandle; - } else { - r.setTopLeft(r.bottomRight()); - r.setBottomRight(e->pos()); - mMouseOverHandle = &mBRHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mTRHandle) { - if (e->pos().x() >= r.left() && e->pos().y() <= r.bottom()) { - r.setTopRight(e->pos()); - } else if (e->pos().x() >= r.left() && e->pos().y() > r.bottom()) { - r.setRight(e->pos().x()); - r.setTop(r.bottom()); - r.setBottom(e->pos().y()); - mMouseOverHandle = &mBRHandle; - } else if (e->pos().x() < r.left() && e->pos().y() <= r.bottom()) { - r.setTop(e->pos().y()); - r.setRight(r.left()); - r.setLeft(e->pos().x()); - mMouseOverHandle = &mTLHandle; - } else { - r.setTopRight(r.bottomLeft()); - r.setBottomLeft(e->pos()); - mMouseOverHandle = &mBLHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mBLHandle) { - if (e->pos().x() <= r.right() && e->pos().y() >= r.top()) { - r.setBottomLeft(e->pos()); - } else if (e->pos().x() <= r.left() && e->pos().y() < r.top()) { - r.setLeft(e->pos().x()); - r.setBottom(r.top()); - r.setTop(e->pos().y()); - mMouseOverHandle = &mTLHandle; - } else if (e->pos().x() > r.left() && e->pos().y() >= r.top()) { - r.setBottom(e->pos().y()); - r.setLeft(r.right()); - r.setRight(e->pos().x()); - mMouseOverHandle = &mBRHandle; - } else { - r.setBottomLeft(r.topRight()); - r.setTopRight(e->pos()); - mMouseOverHandle = &mTRHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mBRHandle) { - if (e->pos().x() >= r.left() && e->pos().y() >= r.top()) { - r.setBottomRight(e->pos()); - } else if (e->pos().x() >= r.left() && e->pos().y() < r.top()) { - r.setRight(e->pos().x()); - r.setBottom(r.top()); - r.setTop(e->pos().y()); - mMouseOverHandle = &mTRHandle; - } else if (e->pos().x() < r.left() && e->pos().y() >= r.top()) { - r.setBottom(e->pos().y()); - r.setRight(r.left()); - r.setLeft(e->pos().x()); - mMouseOverHandle = &mBLHandle; - } else { - r.setBottomRight(r.topLeft()); - r.setTopLeft(e->pos()); - mMouseOverHandle = &mTLHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mTHandle) { - if (e->pos().y() <= r.bottom()) { - r.setTop(e->pos().y()); - } else { - r.setTop(r.bottom()); - r.setBottom(e->pos().y()); - mMouseOverHandle = &mBHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mRHandle) { - if (e->pos().x() >= r.left()) { - r.setRight(e->pos().x()); - } else { - r.setRight(r.left()); - r.setLeft(e->pos().x()); - mMouseOverHandle = &mLHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mLHandle) { - if (e->pos().x() <= r.right()) { - r.setLeft(e->pos().x()); - } else { - r.setLeft(r.right()); - r.setRight(e->pos().x()); - mMouseOverHandle = &mRHandle; - } - r = r.normalized(); - } else if (mMouseOverHandle == &mBHandle) { - if (e->pos().y() >= r.top()) { - r.setBottom(e->pos().y()); - } else { - r.setBottom(r.top()); - r.setTop(e->pos().y()); - mMouseOverHandle = &mTHandle; - } - r = r.normalized(); - } - - mSelection = r.normalized(); - } - - update(); - return; - } - - if (mSelection.isNull()) { - return; - } - - for (auto r: mHandles) { - if (r->contains(e->pos())) { - mMouseOverHandle = r; - - if (r == &mTLHandle || r == &mBRHandle) { - setCursor(Qt::SizeFDiagCursor); - } else if (r == &mTRHandle || r == &mBLHandle) { - setCursor(Qt::SizeBDiagCursor); - } else if (r == &mLHandle || r == &mRHandle) { - setCursor(Qt::SizeHorCursor); - } else if (r == &mTHandle || r == &mBHandle) { - setCursor(Qt::SizeVerCursor); - } - - return; - } - } - - mMouseOverHandle = nullptr; - - if (mSelection.contains(e->pos())) { - setCursor(Qt::OpenHandCursor); - } else { - setCursor(Qt::CrossCursor); - } -} - -void ScreenClipper::mouseReleaseEvent(QMouseEvent *e) -{ - if (mMouseOverHandle == nullptr && mSelection.contains(e->pos())) { - setCursor(Qt::OpenHandCursor); - } - update(); -} - -void ScreenClipper::mouseDoubleClickEvent(QMouseEvent *) -{ - return grabRect(); -} - -void ScreenClipper::keyPressEvent(QKeyEvent *e) -{ - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - return grabRect(); - } - - if (e->key() == Qt::Key_Escape) { - emit regionCancelled(); - return; - } - - return e->ignore(); -} - -inline void ScreenClipper::grabRect() -{ - if (!mSelection.isNull() && mSelection.isValid()) { - grabbing = true; - emit regionGrabbed(mPixmap.copy(mSelection), mSelection); - return; - } - - emit regionCancelled(); -} - -void ScreenClipper::updateHandles() -{ - QRect r = mSelection; - - mTLHandle.moveTopLeft(r.topLeft()); - mTRHandle.moveTopRight(r.topRight()); - mBLHandle.moveBottomLeft(r.bottomLeft()); - mBRHandle.moveBottomRight(r.bottomRight()); - - mLHandle.moveTopLeft(QPoint(r.x(), r.y() + (r.height() / 2) - (mLHandle.height() / 2))); - mTHandle.moveTopLeft(QPoint(r.x() + (r.width() / 2) - (mTHandle.width() / 2), r.y())); - mRHandle.moveTopRight(QPoint(r.right(), r.y() + (r.height() / 2) - (mRHandle.height() / 2))); - mBHandle.moveBottomLeft(QPoint(r.x() + (r.width() / 2) - (mBHandle.width() / 2), r.bottom())); -} - -void ScreenClipper::drawHandles(QPainter *painter, const QColor &color) -{ - drawTriangle(painter, color, mTLHandle.topLeft(), mTLHandle.topRight(), mTLHandle.bottomLeft()); - drawTriangle(painter, color, mTRHandle.topRight(), mTRHandle.topLeft(), mTRHandle.bottomRight()); - drawTriangle(painter, color, mBLHandle.topLeft(), mBLHandle.bottomLeft(), mBLHandle.bottomRight()); - drawTriangle(painter, color, mBRHandle.topRight(), mBRHandle.bottomRight(), mBRHandle.bottomLeft()); - - drawTriangle(painter, color, mTHandle.topLeft(), mTHandle.topRight(), (mTHandle.bottomLeft() + mTHandle.bottomRight()) / 2); - drawTriangle(painter, color, mBHandle.bottomLeft(), mBHandle.bottomRight(), (mBHandle.topLeft() + mBHandle.topRight()) / 2); - drawTriangle(painter, color, mLHandle.topLeft(), mLHandle.bottomLeft(), (mLHandle.topRight() + mLHandle.bottomRight()) / 2); - drawTriangle(painter, color, mRHandle.topRight(), mRHandle.bottomRight(), (mRHandle.topLeft() + mRHandle.bottomLeft()) / 2); -} - -QPoint ScreenClipper::limitPointToRect(const QPoint &p, const QRect &r) const -{ - QPoint q; - q.setX(p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right()); - q.setY(p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom()); - return q; -} diff --git a/src/ScreenClipper.h b/src/ScreenClipper.h deleted file mode 100644 index 0983e98..0000000 --- a/src/ScreenClipper.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2007 Luca Gugelmann - * Copyright (C) 2015 Boudhayan Gupta - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License version 2 as - * published by the Free Software Foundation - * - * 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 Library 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. - */ - -#ifndef SCREENCLIPPER_H -#define SCREENCLIPPER_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -class ScreenClipper : public QRasterWindow -{ - Q_OBJECT - - public: - - ScreenClipper(const QPixmap &pixmap = QPixmap()); - ~ScreenClipper(); - - protected slots: - - void init(); - - signals: - - void regionGrabbed(const QPixmap &pixmap, const QRect ®ion); - void regionCancelled(); - - protected: - - void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; - void resizeEvent(QResizeEvent *e) Q_DECL_OVERRIDE; - void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE; - void mouseDoubleClickEvent(QMouseEvent *) Q_DECL_OVERRIDE; - void keyPressEvent(QKeyEvent *e) Q_DECL_OVERRIDE; - - private: - - void updateHandles(); - void drawHandles(QPainter *painter, const QColor &color); - void drawTriangle(QPainter *painter, const QColor &color, const QPoint &a, const QPoint &b, const QPoint &c); - void grabRect(); - QPoint limitPointToRect(const QPoint &p, const QRect &r) const; - - bool grabbing; - - // naming convention for handles - // T top, B bottom, R Right, L left - // 2 letters: a corner - // 1 letter: the handle on the middle of the corresponding side - QRect mTLHandle, mTRHandle, mBLHandle, mBRHandle; - QRect mLHandle, mTHandle, mRHandle, mBHandle; - QVector mHandles; - QRect mSelection; - QRect *mMouseOverHandle; - QPoint mMoveDelta; - - QPixmap mPixmap; -}; - -#endif -