From 6b54dff90c45b08c676df2192e3f82006dd77b73 Mon Sep 17 00:00:00 2001 From: Hugo Mercier Date: Sun, 15 Apr 2012 17:20:59 +0200 Subject: [PATCH 1/2] Implement basic copy/paste of styles between layers --- src/app/legend/qgslegend.cpp | 11 +++++ src/app/qgisapp.cpp | 78 +++++++++++++++++++++++++++++++++++- src/app/qgisapp.h | 11 +++++ src/ui/qgisapp.ui | 23 ++++++++++- 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/app/legend/qgslegend.cpp b/src/app/legend/qgslegend.cpp index caa5dd6c39f..00ed3c2113c 100644 --- a/src/app/legend/qgslegend.cpp +++ b/src/app/legend/qgslegend.cpp @@ -42,6 +42,7 @@ #include #include #include +#include const int AUTOSCROLL_MARGIN = 16; @@ -697,6 +698,16 @@ void QgsLegend::handleRightClickEvent( QTreeWidgetItem* item, const QPoint& posi // ends here } + if ( selectedLayers().length() == 1 ) + { + QgisApp* app = QgisApp::instance(); + theMenu.addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) ); + if ( QApplication::clipboard()->mimeData()->hasFormat( "application/qgis.style" ) ) + { + theMenu.addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) ); + } + } + theMenu.addAction( QgisApp::getThemeIcon( "/folder_new.png" ), tr( "&Add New Group" ), this, SLOT( addGroupToCurrentItem() ) ); theMenu.addAction( QgisApp::getThemeIcon( "/mActionExpandTree.png" ), tr( "&Expand All" ), this, SLOT( expandAll() ) ); theMenu.addAction( QgisApp::getThemeIcon( "/mActionCollapseTree.png" ), tr( "&Collapse All" ), this, SLOT( collapseAll() ) ); diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index bd61de09fda..bb199ce17cd 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -824,6 +824,8 @@ void QgisApp::createActions() connect( mActionCutFeatures, SIGNAL( triggered() ), this, SLOT( editCut() ) ); connect( mActionCopyFeatures, SIGNAL( triggered() ), this, SLOT( editCopy() ) ); connect( mActionPasteFeatures, SIGNAL( triggered() ), this, SLOT( editPaste() ) ); + connect( mActionCopyStyle, SIGNAL( triggered() ), this, SLOT( copyStyle() ) ); + connect( mActionPasteStyle, SIGNAL( triggered() ), this, SLOT( pasteStyle() ) ); connect( mActionAddFeature, SIGNAL( triggered() ), this, SLOT( addFeature() ) ); connect( mActionMoveFeature, SIGNAL( triggered() ), this, SLOT( moveFeature() ) ); connect( mActionReshapeFeatures, SIGNAL( triggered() ), this, SLOT( reshapeFeatures() ) ); @@ -4294,7 +4296,6 @@ void QgisApp::editCut( QgsMapLayer * layerContainingSelection ) } } - void QgisApp::editCopy( QgsMapLayer * layerContainingSelection ) { if ( mMapCanvas && mMapCanvas->isDrawing() ) @@ -4373,6 +4374,76 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer ) } } +void QgisApp::copyStyle( QgsMapLayer * sourceLayer ) +{ + QgsMapLayer *selectionLayer = sourceLayer ? sourceLayer : activeLayer(); + if ( selectionLayer ) + { + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" ); + QDomDocument doc( documentType ); + QDomElement rootNode = doc.createElement( "qgis" ); + rootNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) ); + doc.appendChild( rootNode ); + QString errorMsg; + if ( !selectionLayer->writeSymbology( rootNode, doc, errorMsg ) ) + { + QMessageBox::warning( this, + tr( "Error" ), + tr( "Cannot copy style: %1" ) + .arg( errorMsg ), + QMessageBox::Ok ); + return; + } + QClipboard* clipboard = QApplication::clipboard(); + QMimeData *mimeData = new QMimeData(); + mimeData->setData( "application/qgis.style", doc.toByteArray() ); + // transfers the ownership to the clipboard object + clipboard->setMimeData( mimeData ); + } +} + +void QgisApp::pasteStyle( QgsMapLayer * destinationLayer ) +{ + QgsMapLayer *selectionLayer = destinationLayer ? destinationLayer : activeLayer(); + if ( selectionLayer ) + { + QClipboard* clipboard = QApplication::clipboard(); + + const QMimeData* mimeData = clipboard->mimeData(); + if ( mimeData->hasFormat( "application/qgis.style" ) ) + { + QDomDocument doc( "qgis" ); + QString errorMsg; + int errorLine, errorColumn; + if ( !doc.setContent ( mimeData->data( "application/qgis.style" ), false, &errorMsg, &errorLine, &errorColumn ) ) + { + QMessageBox::information( this, + tr( "Error" ), + tr( "Cannot parse style: %1:%2:%3" ) + .arg( errorMsg ) + .arg( errorLine ) + .arg( errorColumn ), + QMessageBox::Ok ); + return; + } + QDomElement rootNode = doc.firstChildElement( "qgis" ); + if ( !selectionLayer->readSymbology( rootNode, errorMsg ) ) + { + QMessageBox::information( this, + tr( "Error" ), + tr( "Cannot read style: %1" ) + .arg( errorMsg ), + QMessageBox::Ok ); + return; + } + + mMapLegend->refreshLayerSymbology( selectionLayer->id(), false ); + } + } +} void QgisApp::pasteTransformations() { @@ -6296,6 +6367,8 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionCutFeatures->setEnabled( false ); mActionCopyFeatures->setEnabled( false ); mActionPasteFeatures->setEnabled( false ); + mActionCopyStyle->setEnabled( false ); + mActionPasteStyle->setEnabled( false ); mActionUndo->setEnabled( false ); mActionRedo->setEnabled( false ); @@ -6326,6 +6399,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionAddToOverview->setEnabled( true ); mActionZoomToLayer->setEnabled( true ); + mActionCopyStyle->setEnabled( true ); + mActionPasteStyle->setEnabled( QApplication::clipboard()->mimeData()->hasFormat( "application/qgis.style" ) ); + /***********Vector layers****************/ if ( layer->type() == QgsMapLayer::VectorLayer ) { diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index e7bb44204ec..e046e1513be 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -429,6 +429,17 @@ class QgisApp : public QMainWindow, private Ui::MainWindow */ void editPaste( QgsMapLayer * destinationLayer = 0 ); + /** + \param sourceLayer The layer where the style will be taken from + (defaults to the active layer on the legend) + */ + void copyStyle( QgsMapLayer * sourceLayer = 0 ); + //! copies style on the clipboard to the active layer + /** + \param destinatioLayer The layer that the clipboard will be pasted to + (defaults to the active layer on the legend) + */ + void pasteStyle( QgsMapLayer * destinationLayer = 0 ); void loadOGRSublayers( QString layertype, QString uri, QStringList list ); void loadGDALSublayers( QString uri, QStringList list ); diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 3ff2a01140c..f261864acde 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1052 - 21 + 25 @@ -152,6 +152,9 @@ + + + @@ -1648,6 +1651,24 @@ Offset Curve + + + + :/images/themes/default/mActionEditCopy.png:/images/themes/default/mActionEditCopy.png + + + Copy style + + + + + + :/images/themes/default/mActionEditPaste.png:/images/themes/default/mActionEditPaste.png + + + Paste style + + From e1c934af13a84fa057694a76f89eaebe86748e93 Mon Sep 17 00:00:00 2001 From: Hugo Mercier Date: Mon, 16 Apr 2012 11:08:25 +0200 Subject: [PATCH 2/2] Make the style copy/paste functionality closer to the geometry copy/paste implementation --- src/app/legend/qgslegend.cpp | 3 ++- src/app/qgisapp.cpp | 18 +++++++--------- src/app/qgsclipboard.cpp | 40 ++++++++++++++++++++++++++++++++++-- src/app/qgsclipboard.h | 31 ++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/src/app/legend/qgslegend.cpp b/src/app/legend/qgslegend.cpp index 00ed3c2113c..4d183b51b97 100644 --- a/src/app/legend/qgslegend.cpp +++ b/src/app/legend/qgslegend.cpp @@ -33,6 +33,7 @@ #include "qgsrasterlayer.h" #include "qgsvectorlayer.h" #include "qgsgenericprojectionselector.h" +#include "qgsclipboard.h" #include #include @@ -702,7 +703,7 @@ void QgsLegend::handleRightClickEvent( QTreeWidgetItem* item, const QPoint& posi { QgisApp* app = QgisApp::instance(); theMenu.addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) ); - if ( QApplication::clipboard()->mimeData()->hasFormat( "application/qgis.style" ) ) + if ( app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) ) { theMenu.addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) ); } diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index bb199ce17cd..c341161a2c1 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -4397,11 +4397,10 @@ void QgisApp::copyStyle( QgsMapLayer * sourceLayer ) QMessageBox::Ok ); return; } - QClipboard* clipboard = QApplication::clipboard(); - QMimeData *mimeData = new QMimeData(); - mimeData->setData( "application/qgis.style", doc.toByteArray() ); - // transfers the ownership to the clipboard object - clipboard->setMimeData( mimeData ); + // Copies data in text form as well, so the XML can be pasted into a text editor + clipboard()->setData( QGSCLIPBOARD_STYLE_MIME, doc.toByteArray(), doc.toString() ); + // Enables the paste menu element + mActionPasteStyle->setEnabled( true ); } } @@ -4410,15 +4409,12 @@ void QgisApp::pasteStyle( QgsMapLayer * destinationLayer ) QgsMapLayer *selectionLayer = destinationLayer ? destinationLayer : activeLayer(); if ( selectionLayer ) { - QClipboard* clipboard = QApplication::clipboard(); - - const QMimeData* mimeData = clipboard->mimeData(); - if ( mimeData->hasFormat( "application/qgis.style" ) ) + if ( clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) ) { QDomDocument doc( "qgis" ); QString errorMsg; int errorLine, errorColumn; - if ( !doc.setContent ( mimeData->data( "application/qgis.style" ), false, &errorMsg, &errorLine, &errorColumn ) ) + if ( !doc.setContent ( clipboard()->data( QGSCLIPBOARD_STYLE_MIME ), false, &errorMsg, &errorLine, &errorColumn ) ) { QMessageBox::information( this, tr( "Error" ), @@ -6400,7 +6396,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionZoomToLayer->setEnabled( true ); mActionCopyStyle->setEnabled( true ); - mActionPasteStyle->setEnabled( QApplication::clipboard()->mimeData()->hasFormat( "application/qgis.style" ) ); + mActionPasteStyle->setEnabled( clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) ); /***********Vector layers****************/ if ( layer->type() == QgsMapLayer::VectorLayer ) diff --git a/src/app/qgsclipboard.cpp b/src/app/qgsclipboard.cpp index 4fe98a339f5..f2a47e8b7f5 100644 --- a/src/app/qgsclipboard.cpp +++ b/src/app/qgsclipboard.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "qgsclipboard.h" #include "qgsfeature.h" @@ -104,9 +105,9 @@ void QgsClipboard::replaceWithCopyOf( const QgsFieldMap& fields, QgsFeatureList& // docs). With a Linux X server, ::Clipboard was required. // The simple solution was to put the text into both clipboards. - // The ::Selection setText() below one may need placing inside so - // #ifdef so that it doesn't get compiled under Windows. +#ifndef Q_OS_WIN cb->setText( textCopy, QClipboard::Selection ); +#endif cb->setText( textCopy, QClipboard::Clipboard ); QgsDebugMsg( QString( "replaced system clipboard with: %1." ).arg( textCopy ) ); @@ -163,3 +164,38 @@ QgsCoordinateReferenceSystem QgsClipboard::crs() { return mCRS; } + +void QgsClipboard::setData( const QString& mimeType, const QByteArray& data, const QString* text ) +{ + QMimeData *mdata = new QMimeData(); + mdata->setData( mimeType, data ); + if ( text ) + { + mdata->setText( *text ); + } + // Transfers ownership to the clipboard object +#ifndef Q_OS_WIN + QApplication::clipboard()->setMimeData( mdata, QClipboard::Selection ); +#endif + QApplication::clipboard()->setMimeData( mdata, QClipboard::Clipboard ); +} + +void QgsClipboard::setData( const QString& mimeType, const QByteArray& data, const QString& text ) +{ + setData( mimeType, data, &text ); +} + +void QgsClipboard::setData( const QString& mimeType, const QByteArray& data ) +{ + setData( mimeType, data, 0 ); +} + +bool QgsClipboard::hasFormat( const QString& mimeType ) +{ + return QApplication::clipboard()->mimeData()->hasFormat( mimeType ); +} + +QByteArray QgsClipboard::data( const QString& mimeType ) +{ + return QApplication::clipboard()->mimeData()->data( mimeType ); +} diff --git a/src/app/qgsclipboard.h b/src/app/qgsclipboard.h index 948a405f94c..729bca67af4 100644 --- a/src/app/qgsclipboard.h +++ b/src/app/qgsclipboard.h @@ -41,6 +41,11 @@ TODO: Make it work */ +/* + * Constants used to describe copy-paste MIME types + */ +#define QGSCLIPBOARD_STYLE_MIME "application/qgis.style" + class QgsClipboard { public: @@ -98,6 +103,32 @@ class QgsClipboard */ QgsCoordinateReferenceSystem crs(); + /* + * Stores a MimeData together with a text into the system clipboard + */ + void setData( const QString& mimeType, const QByteArray& data, const QString* text = 0 ); + /* + * Stores a MimeData together with a text into the system clipboard + */ + void setData( const QString& mimeType, const QByteArray& data, const QString& text ); + /* + * Stores a MimeData into the system clipboard + */ + void setData( const QString& mimeType, const QByteArray& data ); + /* + * Stores a text into the system clipboard + */ + void setText( const QString& text ); + /* + * Proxy to QMimeData::hasFormat + * Tests whether the system clipboard contains data of a given MIME type + */ + bool hasFormat( const QString& mimeType ); + /* + * Retrieve data from the system clipboard. + * No copy is involved, since the return QByteArray is implicitly shared + */ + QByteArray data( const QString& mimeType ); private: /** QGIS-internal vector feature clipboard.