diff --git a/glabels/LabelEditor.cpp b/glabels/LabelEditor.cpp index c965383..5acc43b 100644 --- a/glabels/LabelEditor.cpp +++ b/glabels/LabelEditor.cpp @@ -38,6 +38,7 @@ #include "model/Markup.h" #include "model/Settings.h" +#include #include #include #include @@ -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 diff --git a/glabels/LabelEditor.h b/glabels/LabelEditor.h index afbf1b6..0e31d1b 100644 --- a/glabels/LabelEditor.h +++ b/glabels/LabelEditor.h @@ -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; ///////////////////////////////////// diff --git a/glabels/MainWindow.cpp b/glabels/MainWindow.cpp index e7b7c2c..e52f9b3 100644 --- a/glabels/MainWindow.cpp +++ b/glabels/MainWindow.cpp @@ -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(sender()); + auto p = action->data().value(); + + 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) ) ); } diff --git a/glabels/MainWindow.h b/glabels/MainWindow.h index fc4c0a5..d2a2807 100644 --- a/glabels/MainWindow.h +++ b/glabels/MainWindow.h @@ -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(); diff --git a/model/Model.cpp b/model/Model.cpp index a1bd456..325a91f 100644 --- a/model/Model.cpp +++ b/model/Model.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include @@ -1480,67 +1479,130 @@ 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 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(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 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(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 diff --git a/model/Model.h b/model/Model.h index b3e5a97..52cec1f 100644 --- a/model/Model.h +++ b/model/Model.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -207,8 +208,12 @@ 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 ///////////////////////////////// diff --git a/model/ModelImageObject.cpp b/model/ModelImageObject.cpp index bb4ce8a..e95f7ef 100644 --- a/model/ModelImageObject.cpp +++ b/model/ModelImageObject.cpp @@ -73,6 +73,8 @@ namespace glabels { smDefaultImage = new QImage( ":images/checkerboard.png" ); } + + mLockAspectRatio = true; } diff --git a/model/Point.h b/model/Point.h index 4750ab5..8b8a5c0 100644 --- a/model/Point.h +++ b/model/Point.h @@ -24,6 +24,8 @@ #include "Distance.h" +#include + namespace glabels { @@ -52,4 +54,7 @@ namespace glabels } +Q_DECLARE_METATYPE( glabels::model::Point ) + + #endif // model_Point_h diff --git a/translations/glabels_C.ts b/translations/glabels_C.ts index 350c2e3..df9a6f8 100644 --- a/translations/glabels_C.ts +++ b/translations/glabels_C.ts @@ -1328,6 +1328,10 @@ Resize + + Drop + + glabels::MainWindow