Skip to content

Commit

Permalink
[FEATURE][layouts] Support pasting pictures directly into layouts
Browse files Browse the repository at this point in the history
Embeds the pasted picture into the layout.

Note that Qt image clipboard handling seems a bit... crap. So success
may vary.
  • Loading branch information
nyalldawson committed Apr 17, 2020
1 parent 4ee3d22 commit 226bc42
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 9 deletions.
Expand Up @@ -12,7 +12,7 @@
class QgsLayoutCustomDropHandler : QObject
{
%Docstring
Abstract base class that may be implemented to handle new types of data to be dropped in QGIS layouts.
Abstract base class that may be implemented to handle new types of data to be dropped or pasted in QGIS layouts.

.. versionadded:: 3.0
%End
Expand Down Expand Up @@ -51,6 +51,28 @@ the drop occurred.
The base class implementation does nothing.

.. versionadded:: 3.12
%End

virtual bool handlePaste( QgsLayoutDesignerInterface *iface, QPointF pastePoint, const QMimeData *data, QList< QgsLayoutItem * > &pastedItems /Out/ );
%Docstring
Called when the specified MIME ``data`` has been pasted onto a QGIS layout. If ``True``
is returned, then the handler has accepted this data and it should not
be further processed (e.g. by other QgsLayoutCustomDropHandler).

The ``pastePoint`` point specifies the location (in layout coordinates) at which
the paste occurred.

The base class implementation does nothing.

:param iface: pointer to the layout designer interface
:param pastePoint: layout point at which the paste should occur
:param data: MIME data to paste

:return: - ``True`` if the handler accepted and processed the paste operation
- pastedItems: should be filled with any newly created items as a result of the paste


.. versionadded:: 3.14
%End
};

Expand Down
43 changes: 37 additions & 6 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -91,6 +91,8 @@
#include <QPageSetupDialog>
#include <QWidgetAction>
#include <QProgressBar>
#include <QClipboard>

#ifdef Q_OS_MACX
#include <ApplicationServices/ApplicationServices.h>
#endif
Expand Down Expand Up @@ -3748,19 +3750,48 @@ void QgsLayoutDesignerDialog::populateLayoutsMenu()

void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
const QPoint viewPoint = mView->mapFromGlobal( QCursor::pos() );
//TODO - use a better way of determining whether paste was triggered by keystroke
//or menu item
QList< QgsLayoutItem * > items;
if ( ( pt.x() < 0 ) || ( pt.y() < 0 ) )
QPointF layoutPoint;
if ( ( viewPoint.x() < 0 ) || ( viewPoint.y() < 0 ) )
{
//action likely triggered by menu, paste items in center of screen
items = mView->pasteItems( QgsLayoutView::PasteModeCenter );
layoutPoint = mView->mapToScene( mView->viewport()->rect().center() );
}
else
{
//action likely triggered by keystroke, paste items at cursor position
items = mView->pasteItems( QgsLayoutView::PasteModeCursor );
layoutPoint = mView->mapToScene( viewPoint );
}

QList< QgsLayoutItem * > items;

// give custom paste handlers first shot at processing this
QClipboard *clipboard = QApplication::clipboard();
const QMimeData *data = clipboard->mimeData();
const QVector<QPointer<QgsLayoutCustomDropHandler >> handlers = QgisApp::instance()->customLayoutDropHandlers();
bool handled = false;
for ( QgsLayoutCustomDropHandler *handler : handlers )
{
if ( handler && handler->handlePaste( iface(), layoutPoint, data, items ) )
{
handled = true;
break;
}
}

if ( !handled )
{
if ( ( viewPoint.x() < 0 ) || ( viewPoint.y() < 0 ) )
{
//action likely triggered by menu, paste items in center of screen
items = mView->pasteItems( QgsLayoutView::PasteModeCenter );
}
else
{
//action likely triggered by keystroke, paste items at cursor position
items = mView->pasteItems( QgsLayoutView::PasteModeCursor );
}
}

whileBlocking( currentLayout() )->deselectAll();
Expand Down
39 changes: 38 additions & 1 deletion src/app/layout/qgslayoutimagedrophandler.cpp
Expand Up @@ -51,7 +51,7 @@ bool QgsLayoutImageDropHandler::handleFileDrop( QgsLayoutDesignerInterface *ifac

QgsLayoutPoint layoutPoint = iface->layout()->convertFromLayoutUnits( point, iface->layout()->units() );

item->setPicturePath( file );
item->setPicturePath( file, QgsLayoutItemPicture::FormatRaster );

// force a resize to the image's actual size
item->setResizeMode( QgsLayoutItemPicture::FrameToImageSize );
Expand All @@ -74,3 +74,40 @@ bool QgsLayoutImageDropHandler::handleFileDrop( QgsLayoutDesignerInterface *ifac

return true;
}

bool QgsLayoutImageDropHandler::handlePaste( QgsLayoutDesignerInterface *iface, QPointF pastePoint, const QMimeData *data, QList<QgsLayoutItem *> &pastedItems )
{
if ( !data->hasImage() )
return false;

QgsLayoutPoint layoutPoint = iface->layout()->convertFromLayoutUnits( pastePoint, iface->layout()->units() );
std::unique_ptr< QgsLayoutItemPicture > item = qgis::make_unique< QgsLayoutItemPicture >( iface->layout() );

const QByteArray imageData = data->data( QStringLiteral( "application/x-qt-image" ) );
if ( imageData.isEmpty() )
return false;

const QByteArray encoded = imageData.toBase64();

QString path( encoded );
path.prepend( QLatin1String( "base64:" ) );

item->setPicturePath( path, QgsLayoutItemPicture::FormatRaster );

// force a resize to the image's actual size
item->setResizeMode( QgsLayoutItemPicture::FrameToImageSize );
// and then move back to standard freeform image sizing
item->setResizeMode( QgsLayoutItemPicture::Zoom );

// we want the drop location to be the center of the placed item, because drag thumbnails are usually centered on the mouse cursor
item->setReferencePoint( QgsLayoutItem::Middle );
item->attemptMove( layoutPoint );

// reset to standard top-left reference point location
item->setReferencePoint( QgsLayoutItem::UpperLeft );

pastedItems << item.get();
iface->layout()->addLayoutItem( item.release() );

return true;
}
1 change: 1 addition & 0 deletions src/app/layout/qgslayoutimagedrophandler.h
Expand Up @@ -27,6 +27,7 @@ class QgsLayoutImageDropHandler : public QgsLayoutCustomDropHandler
QgsLayoutImageDropHandler( QObject *parent = nullptr );

bool handleFileDrop( QgsLayoutDesignerInterface *iface, QPointF point, const QString &file ) override;
bool handlePaste( QgsLayoutDesignerInterface *iface, QPointF pastePoint, const QMimeData *data, QList< QgsLayoutItem * > &pastedItems ) override;
};

#endif // QGSLAYOUTIMAGEDROPHANDLER_H
5 changes: 5 additions & 0 deletions src/gui/layout/qgslayoutcustomdrophandler.cpp
Expand Up @@ -31,3 +31,8 @@ bool QgsLayoutCustomDropHandler::handleFileDrop( QgsLayoutDesignerInterface *, Q
{
return false;
}

bool QgsLayoutCustomDropHandler::handlePaste( QgsLayoutDesignerInterface *, QPointF, const QMimeData *, QList<QgsLayoutItem *> & )
{
return false;
}
25 changes: 24 additions & 1 deletion src/gui/layout/qgslayoutcustomdrophandler.h
Expand Up @@ -21,10 +21,12 @@
#include <QObject>

class QgsLayoutDesignerInterface;
class QMimeData;
class QgsLayoutItem;

/**
* \ingroup gui
* Abstract base class that may be implemented to handle new types of data to be dropped in QGIS layouts.
* Abstract base class that may be implemented to handle new types of data to be dropped or pasted in QGIS layouts.
*
* \since QGIS 3.0
*/
Expand Down Expand Up @@ -63,6 +65,27 @@ class GUI_EXPORT QgsLayoutCustomDropHandler : public QObject
* \since QGIS 3.12
*/
virtual bool handleFileDrop( QgsLayoutDesignerInterface *iface, QPointF dropPoint, const QString &file );

/**
* Called when the specified MIME \a data has been pasted onto a QGIS layout. If TRUE
* is returned, then the handler has accepted this data and it should not
* be further processed (e.g. by other QgsLayoutCustomDropHandler).
*
* The \a pastePoint point specifies the location (in layout coordinates) at which
* the paste occurred.
*
* The base class implementation does nothing.
*
* \param iface pointer to the layout designer interface
* \param pastePoint layout point at which the paste should occur
* \param data MIME data to paste
* \param pastedItems should be filled with any newly created items as a result of the paste
*
* \returns TRUE if the handler accepted and processed the paste operation
*
* \since QGIS 3.14
*/
virtual bool handlePaste( QgsLayoutDesignerInterface *iface, QPointF pastePoint, const QMimeData *data, QList< QgsLayoutItem * > &pastedItems SIP_OUT );
};


Expand Down

0 comments on commit 226bc42

Please sign in to comment.