Drag n drop (#223)
Added drop interface for image files, images, and text, dragged from other applications. (#153,#223) - Lock aspect ration on new image objects by default - Communicate context menu click location to paste actions
This commit is contained in:
+84
-2
@@ -38,6 +38,7 @@
|
||||
#include "model/Markup.h"
|
||||
#include "model/Settings.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QtMath>
|
||||
#include <QtDebug>
|
||||
@@ -105,6 +106,7 @@ namespace glabels
|
||||
|
||||
setMouseTracking( true );
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setAcceptDrops( true );
|
||||
|
||||
connect( model::Settings::instance(), SIGNAL(changed()), this, SLOT(onSettingsChanged()) );
|
||||
onSettingsChanged();
|
||||
@@ -590,7 +592,7 @@ namespace glabels
|
||||
//
|
||||
if ( mState == IdleState )
|
||||
{
|
||||
emit contextMenuActivate();
|
||||
emit contextMenuActivate( model::Point( xWorld, yWorld ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -621,7 +623,7 @@ namespace glabels
|
||||
/*
|
||||
* Emit signal regardless of mode
|
||||
*/
|
||||
emit pointerMoved( xWorld, yWorld );
|
||||
emit pointerMoved( model::Point( xWorld, yWorld ) );
|
||||
|
||||
|
||||
/*
|
||||
@@ -1027,6 +1029,85 @@ namespace glabels
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle drag enter event
|
||||
//
|
||||
void LabelEditor::dragEnterEvent( QDragEnterEvent *event )
|
||||
{
|
||||
if ( event->mimeData()->hasUrls() ||
|
||||
event->mimeData()->hasImage() ||
|
||||
event->mimeData()->hasText() )
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle drag move event
|
||||
//
|
||||
void LabelEditor::dragMoveEvent( QDragMoveEvent *event )
|
||||
{
|
||||
if ( event->mimeData()->hasUrls() ||
|
||||
event->mimeData()->hasImage() ||
|
||||
event->mimeData()->hasText() )
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Handle drop event
|
||||
//
|
||||
void LabelEditor::dropEvent( QDropEvent *event )
|
||||
{
|
||||
/*
|
||||
* Transform to label coordinates
|
||||
*/
|
||||
QTransform transform;
|
||||
|
||||
transform.scale( mScale, mScale );
|
||||
transform.translate( mX0.pt(), mY0.pt() );
|
||||
|
||||
QPointF pWorld = transform.inverted().map( event->position() );
|
||||
auto xWorld = model::Distance::pt( pWorld.x() );
|
||||
auto yWorld = model::Distance::pt( pWorld.y() );
|
||||
auto p = model::Point( xWorld, yWorld );
|
||||
|
||||
if ( event->mimeData()->hasUrls() )
|
||||
{
|
||||
mUndoRedoModel->checkpoint( tr("Drop") );
|
||||
mModel->pasteAsUrls( event->mimeData(), p );
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
else if ( event->mimeData()->hasImage() )
|
||||
{
|
||||
mUndoRedoModel->checkpoint( tr("Drop") );
|
||||
mModel->pasteAsImage( event->mimeData(), p );
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
else if ( event->mimeData()->hasText() )
|
||||
{
|
||||
mUndoRedoModel->checkpoint( tr("Drop") );
|
||||
mModel->pasteAsText( event->mimeData(), p );
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Draw Background Layer
|
||||
///
|
||||
@@ -1283,4 +1364,5 @@ namespace glabels
|
||||
emit zoomChanged();
|
||||
}
|
||||
|
||||
|
||||
} // namespace glabels
|
||||
|
||||
@@ -57,9 +57,9 @@ namespace glabels
|
||||
// Signals
|
||||
/////////////////////////////////////
|
||||
signals:
|
||||
void contextMenuActivate();
|
||||
void contextMenuActivate( model::Point p );
|
||||
void zoomChanged();
|
||||
void pointerMoved( const model::Distance& x, const model::Distance& y );
|
||||
void pointerMoved( model::Point p );
|
||||
void pointerExited();
|
||||
void modeChanged();
|
||||
|
||||
@@ -126,6 +126,9 @@ namespace glabels
|
||||
void leaveEvent( QEvent* event ) override;
|
||||
void keyPressEvent( QKeyEvent* event ) override;
|
||||
void paintEvent( QPaintEvent* event ) override;
|
||||
void dragEnterEvent( QDragEnterEvent *event ) override;
|
||||
void dragMoveEvent( QDragMoveEvent *event ) override;
|
||||
void dropEvent( QDropEvent *event ) override;
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
+30
-9
@@ -196,8 +196,8 @@ namespace glabels
|
||||
connect( model::Settings::instance(), SIGNAL(changed()), this, SLOT(onSettingsChanged()) );
|
||||
connect( QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardChanged()) );
|
||||
#if 0
|
||||
connect( mLabelEditor, SIGNAL(pointerMoved(double, double)),
|
||||
this, SLOT(onPointerMoved(double, double)) );
|
||||
connect( mLabelEditor, SIGNAL(pointerMoved(model::Point)),
|
||||
this, SLOT(onPointerMoved(modelPoint)) );
|
||||
connect( mLabelEditor, SIGNAL(pointerExited()), this, SLOT(onPointerExit()) );
|
||||
#endif
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace glabels
|
||||
manageActions();
|
||||
setTitle();
|
||||
|
||||
connect( mLabelEditor, SIGNAL(contextMenuActivate()), this, SLOT(onContextMenuActivate()) );
|
||||
connect( mLabelEditor, SIGNAL(contextMenuActivate(model::Point)), this, SLOT(onContextMenuActivate(model::Point)) );
|
||||
connect( mModel, SIGNAL(nameChanged()), this, SLOT(onNameChanged()) );
|
||||
connect( mModel, SIGNAL(modifiedChanged()), this, SLOT(onModifiedChanged()) );
|
||||
connect( mModel, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged()) );
|
||||
@@ -611,7 +611,7 @@ namespace glabels
|
||||
contextPasteAction = new QAction( tr("&Paste"), this );
|
||||
contextPasteAction->setIcon( Icons::EditPaste() );
|
||||
contextPasteAction->setStatusTip( tr("Paste the clipboard") );
|
||||
connect( contextPasteAction, SIGNAL(triggered()), this, SLOT(editPaste()) );
|
||||
connect( contextPasteAction, SIGNAL(triggered()), this, SLOT(editContextPaste()) );
|
||||
|
||||
contextDeleteAction = new QAction( tr("&Delete"), this );
|
||||
contextDeleteAction->setIcon( QIcon::fromTheme( "edit-delete" ) );
|
||||
@@ -1345,7 +1345,21 @@ namespace glabels
|
||||
void MainWindow::editPaste()
|
||||
{
|
||||
mUndoRedoModel->checkpoint( tr("Paste") );
|
||||
mModel->paste();
|
||||
mModel->paste( model::Point() );
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Edit->Paste Action (from context menu)
|
||||
///
|
||||
void MainWindow::editContextPaste()
|
||||
{
|
||||
// Extract original context menu click location
|
||||
auto *action = qobject_cast<QAction *>(sender());
|
||||
auto p = action->data().value<model::Point>();
|
||||
|
||||
mUndoRedoModel->checkpoint( tr("Paste") );
|
||||
mModel->paste( p );
|
||||
}
|
||||
|
||||
|
||||
@@ -1708,8 +1722,13 @@ namespace glabels
|
||||
///
|
||||
/// Context Menu Activation
|
||||
///
|
||||
void MainWindow::onContextMenuActivate()
|
||||
void MainWindow::onContextMenuActivate( model::Point p )
|
||||
{
|
||||
// Save click location for potential paste action
|
||||
QVariant variant;
|
||||
variant.setValue( p );
|
||||
contextPasteAction->setData( variant );
|
||||
|
||||
if ( mModel->isSelectionEmpty() )
|
||||
{
|
||||
noSelectionContextMenu->popup( QCursor::pos() );
|
||||
@@ -1736,10 +1755,12 @@ namespace glabels
|
||||
///
|
||||
/// Pointer moved: update Cursor Information in Status Bar
|
||||
///
|
||||
void MainWindow::onPointerMoved( double x, double y )
|
||||
void MainWindow::onPointerMoved( model::Point p )
|
||||
{
|
||||
/* TODO: convert x,y to locale units and set precision accordingly. */
|
||||
cursorInfoLabel->setText( QString( "%1, %2" ).arg(x).arg(y) );
|
||||
/* TODO: set precision accordingly. */
|
||||
auto units = model::Settings::units();
|
||||
cursorInfoLabel->setText( QString( "%1, %2" ).arg( p.x().toString(units) )
|
||||
.arg( p.y().toString(units) ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace glabels
|
||||
void editCut();
|
||||
void editCopy();
|
||||
void editPaste();
|
||||
void editContextPaste();
|
||||
void editDelete();
|
||||
void editSelectAll();
|
||||
void editUnSelectAll();
|
||||
@@ -150,10 +151,10 @@ namespace glabels
|
||||
void helpReportBug();
|
||||
void helpAbout();
|
||||
|
||||
void onContextMenuActivate();
|
||||
void onContextMenuActivate( model::Point );
|
||||
|
||||
void onZoomChanged();
|
||||
void onPointerMoved( double, double );
|
||||
void onPointerMoved( model::Point );
|
||||
void onPointerExit();
|
||||
|
||||
void onNameChanged();
|
||||
|
||||
+101
-39
@@ -33,7 +33,6 @@
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QtDebug>
|
||||
|
||||
|
||||
@@ -1480,68 +1479,131 @@ namespace glabels
|
||||
const QClipboard *clipboard = QApplication::clipboard();
|
||||
const QMimeData *mimeData = clipboard->mimeData();
|
||||
|
||||
if ( mimeData->hasFormat( MIME_TYPE ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ( mimeData->hasImage() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ( mimeData->hasText() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return mimeData->hasFormat( MIME_TYPE ) ||
|
||||
mimeData->hasUrls() ||
|
||||
mimeData->hasImage() ||
|
||||
mimeData->hasText();
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Paste from clipboard
|
||||
///
|
||||
void Model::paste()
|
||||
void Model::paste( Point p )
|
||||
{
|
||||
const QClipboard *clipboard = QApplication::clipboard();
|
||||
const QMimeData *mimeData = clipboard->mimeData();
|
||||
|
||||
if ( mimeData->hasFormat( MIME_TYPE ) )
|
||||
{
|
||||
// Native objects
|
||||
QByteArray buffer = mimeData->data( MIME_TYPE );
|
||||
QList <ModelObject*> objects = XmlLabelParser::deserializeObjects( buffer, this );
|
||||
|
||||
unselectAll();
|
||||
foreach ( ModelObject* object, objects )
|
||||
{
|
||||
addObject( object );
|
||||
selectObject( object );
|
||||
}
|
||||
pasteAsNativeObjects( mimeData, p );
|
||||
}
|
||||
else if ( mimeData->hasUrls() )
|
||||
{
|
||||
pasteAsUrls( mimeData, p );
|
||||
}
|
||||
else if ( mimeData->hasImage() )
|
||||
{
|
||||
// Create object from clipboard image
|
||||
auto* object = new ModelImageObject();
|
||||
object->setImage( qvariant_cast<QImage>(mimeData->imageData()) );
|
||||
object->setSize( object->naturalSize() );
|
||||
object->setPosition( (w()-object->w())/2.0, (h()-object->h())/2.0 );
|
||||
addObject( object );
|
||||
unselectAll();
|
||||
selectObject( object );
|
||||
pasteAsImage( mimeData, p );
|
||||
}
|
||||
else if ( mimeData->hasText() )
|
||||
{
|
||||
// Create object from clipboard text
|
||||
auto* object = new ModelTextObject();
|
||||
object->setText( mimeData->text() );
|
||||
object->setSize( object->naturalSize() );
|
||||
object->setPosition( (w()-object->w())/2.0, (h()-object->h())/2.0 );
|
||||
pasteAsText( mimeData, p );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Paste as native objects
|
||||
///
|
||||
void Model::pasteAsNativeObjects( const QMimeData* mimeData, Point p )
|
||||
{
|
||||
QByteArray buffer = mimeData->data( MIME_TYPE );
|
||||
QList <ModelObject*> objects = XmlLabelParser::deserializeObjects( buffer, this );
|
||||
|
||||
unselectAll();
|
||||
foreach ( ModelObject* object, objects )
|
||||
{
|
||||
object->setPositionRelative( p.x(), p.y() );
|
||||
addObject( object );
|
||||
unselectAll();
|
||||
selectObject( object );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Paste as URLs ( currently only supports local image files )
|
||||
///
|
||||
void Model::pasteAsUrls( const QMimeData* mimeData, Point p )
|
||||
{
|
||||
auto x = p.x();
|
||||
auto y = p.y();
|
||||
auto xOffset = Distance::pt( 10 );
|
||||
auto yOffset = Distance::pt( 10 );
|
||||
|
||||
unselectAll();
|
||||
for ( auto url : mimeData->urls() )
|
||||
{
|
||||
if ( url.isLocalFile() )
|
||||
{
|
||||
auto name = url.toLocalFile();
|
||||
QImage image( name );
|
||||
if ( !image.isNull() )
|
||||
{
|
||||
auto* object = new ModelImageObject();
|
||||
object->setImage( name, image );
|
||||
object->setSize( object->naturalSize() );
|
||||
object->setPosition( x, y );
|
||||
addObject( object );
|
||||
selectObject( object );
|
||||
|
||||
x = fmod( x + xOffset, w() );
|
||||
y = fmod( y + yOffset, h() );
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Cannot paste" << name
|
||||
<< ": does not exist or currently unsupported file type.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Cannot paste" << url.toString()
|
||||
<< ": currently unsupported file location.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Paste as image
|
||||
///
|
||||
void Model::pasteAsImage( const QMimeData* mimeData, Point p )
|
||||
{
|
||||
auto* object = new ModelImageObject();
|
||||
object->setImage( qvariant_cast<QImage>(mimeData->imageData()) );
|
||||
object->setSize( object->naturalSize() );
|
||||
object->setPosition( p.x(), p.y() );
|
||||
addObject( object );
|
||||
unselectAll();
|
||||
selectObject( object );
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Paste as text
|
||||
void Model::pasteAsText( const QMimeData* mimeData, Point p )
|
||||
{
|
||||
auto* object = new ModelTextObject();
|
||||
object->setText( mimeData->text() );
|
||||
object->setSize( object->naturalSize() );
|
||||
object->setPosition( p.x(), p.y() );
|
||||
addObject( object );
|
||||
unselectAll();
|
||||
selectObject( object );
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Draw label objects
|
||||
///
|
||||
|
||||
+6
-1
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QMimeData>
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
|
||||
@@ -207,7 +208,11 @@ namespace glabels
|
||||
void copySelection();
|
||||
void cutSelection();
|
||||
bool canPaste();
|
||||
void paste();
|
||||
void paste( Point p );
|
||||
void pasteAsNativeObjects( const QMimeData* mimeData, Point p );
|
||||
void pasteAsUrls( const QMimeData* mimeData, Point p );
|
||||
void pasteAsImage( const QMimeData* mimeData, Point p );
|
||||
void pasteAsText( const QMimeData* mimeData, Point p );
|
||||
|
||||
/////////////////////////////////
|
||||
// Drawing operations
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace glabels
|
||||
{
|
||||
smDefaultImage = new QImage( ":images/checkerboard.png" );
|
||||
}
|
||||
|
||||
mLockAspectRatio = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "Distance.h"
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
|
||||
namespace glabels
|
||||
{
|
||||
@@ -52,4 +54,7 @@ namespace glabels
|
||||
}
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE( glabels::model::Point )
|
||||
|
||||
|
||||
#endif // model_Point_h
|
||||
|
||||
@@ -1328,6 +1328,10 @@
|
||||
<source>Resize</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>glabels::MainWindow</name>
|
||||
|
||||
Reference in New Issue
Block a user