mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[FEATURE] Add copy/paste symbol action to right click menu in categorized/graduated/rule based renderers
Allows symbols to be easily copied and pasted between these nodes
This commit is contained in:
parent
47f83b582f
commit
55efeaeb2f
@ -86,6 +86,11 @@ from the XML file with a matching name.
|
||||
.. versionadded:: 2.9
|
||||
%End
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void pasteSymbolToSelection();
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
void updateUiFromRenderer();
|
||||
|
@ -60,6 +60,11 @@ Toggle the link between classes boundaries
|
||||
void modelDataChanged();
|
||||
void refreshRanges( bool reset = false );
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void pasteSymbolToSelection();
|
||||
|
||||
|
||||
protected:
|
||||
void updateUiFromRenderer( bool updateCount = true );
|
||||
void connectUpdateHandlers();
|
||||
|
@ -81,6 +81,8 @@ to re-synchronize with the variables.
|
||||
protected:
|
||||
|
||||
|
||||
|
||||
|
||||
virtual QList<QgsSymbol *> selectedSymbols();
|
||||
%Docstring
|
||||
Subclasses may provide the capability of changing multiple symbols at once by implementing the following two methods
|
||||
@ -123,9 +125,17 @@ Change marker sizes of selected symbols
|
||||
Change marker angles of selected symbols
|
||||
%End
|
||||
|
||||
|
||||
virtual void copy();
|
||||
virtual void paste();
|
||||
|
||||
virtual void pasteSymbolToSelection();
|
||||
%Docstring
|
||||
Pastes the clipboard symbol over selected items.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
private:
|
||||
virtual void apply();
|
||||
%Docstring
|
||||
|
@ -70,6 +70,14 @@ Constructor for QgsRuleBasedRendererModel, for the specified ``renderer``.
|
||||
void updateRule( const QModelIndex &index );
|
||||
void removeRule( const QModelIndex &index );
|
||||
|
||||
void setSymbol( const QModelIndex &index, QgsSymbol *symbol /Transfer/ );
|
||||
%Docstring
|
||||
Sets the ``symbol`` for the rule at the specified ``index``. Ownership of the symbols is
|
||||
transferred to the renderer.
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
void willAddRules( const QModelIndex &parent, int count ); // call beginInsertRows
|
||||
void finishedAddingRules(); // call endInsertRows
|
||||
|
||||
@ -148,6 +156,8 @@ Opens the dialog for refining a rule using ranges
|
||||
|
||||
virtual void paste();
|
||||
|
||||
virtual void pasteSymbolToSelection();
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
@ -1066,7 +1066,7 @@ static QString _nameForSymbolType( QgsSymbol::SymbolType type )
|
||||
}
|
||||
}
|
||||
|
||||
QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context )
|
||||
QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context )
|
||||
{
|
||||
Q_ASSERT( symbol );
|
||||
QDomElement symEl = doc.createElement( QStringLiteral( "symbol" ) );
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include <QPen>
|
||||
#include <QPainter>
|
||||
#include <QFileDialog>
|
||||
#include <QClipboard>
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
@ -583,6 +584,12 @@ QgsCategorizedSymbolRendererWidget::QgsCategorizedSymbolRendererWidget( QgsVecto
|
||||
connect( mMergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::mergeSelectedCategories );
|
||||
mUnmergeCategoriesAction = new QAction( tr( "Unmerge Categories" ), this );
|
||||
connect( mUnmergeCategoriesAction, &QAction::triggered, this, &QgsCategorizedSymbolRendererWidget::unmergeSelectedCategories );
|
||||
|
||||
connect( mContextMenu, &QMenu::aboutToShow, this, [ = ]
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
|
||||
} );
|
||||
}
|
||||
|
||||
QgsCategorizedSymbolRendererWidget::~QgsCategorizedSymbolRendererWidget()
|
||||
@ -1073,6 +1080,32 @@ void QgsCategorizedSymbolRendererWidget::matchToSymbolsFromXml()
|
||||
}
|
||||
}
|
||||
|
||||
void QgsCategorizedSymbolRendererWidget::pasteSymbolToSelection()
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
if ( !tempSymbol )
|
||||
return;
|
||||
|
||||
const QList<int> selectedCats = selectedCategories();
|
||||
if ( !selectedCats.isEmpty() )
|
||||
{
|
||||
for ( int idx : selectedCats )
|
||||
{
|
||||
if ( mRenderer->categories().at( idx ).symbol()->type() != tempSymbol->type() )
|
||||
continue;
|
||||
|
||||
std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
|
||||
if ( selectedCats.count() > 1 )
|
||||
{
|
||||
//if updating multiple categories, retain the existing category colors
|
||||
newCatSymbol->setColor( mRenderer->categories().at( idx ).symbol()->color() );
|
||||
}
|
||||
mRenderer->updateCategorySymbol( idx, newCatSymbol.release() );
|
||||
}
|
||||
emit widgetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container )
|
||||
{
|
||||
QgsSymbolSelectorWidget *dlg = qobject_cast<QgsSymbolSelectorWidget *>( container );
|
||||
|
@ -148,6 +148,10 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget,
|
||||
*/
|
||||
void matchToSymbolsFromXml();
|
||||
|
||||
protected slots:
|
||||
|
||||
void pasteSymbolToSelection() override;
|
||||
|
||||
private slots:
|
||||
|
||||
void cleanUpSymbolSelector( QgsPanelWidget *container );
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include <QStandardItem>
|
||||
#include <QPen>
|
||||
#include <QPainter>
|
||||
|
||||
#include <QClipboard>
|
||||
// ------------------------------ Model ------------------------------------
|
||||
|
||||
///@cond PRIVATE
|
||||
@ -1402,3 +1402,34 @@ void QgsGraduatedSymbolRendererWidget::dataDefinedSizeLegend()
|
||||
openPanel( panel ); // takes ownership of the panel
|
||||
}
|
||||
}
|
||||
|
||||
void QgsGraduatedSymbolRendererWidget::pasteSymbolToSelection()
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
if ( !tempSymbol )
|
||||
return;
|
||||
|
||||
const QModelIndexList selectedRows = viewGraduated->selectionModel()->selectedRows();
|
||||
for ( const QModelIndex &index : selectedRows )
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
continue;
|
||||
|
||||
const int row = index.row();
|
||||
if ( !mRenderer || mRenderer->ranges().size() <= row )
|
||||
continue;
|
||||
|
||||
if ( mRenderer->ranges().at( row ).symbol()->type() != tempSymbol->type() )
|
||||
continue;
|
||||
|
||||
std::unique_ptr< QgsSymbol > newCatSymbol( tempSymbol->clone() );
|
||||
if ( selectedRows.count() > 1 )
|
||||
{
|
||||
//if updating multiple ranges, retain the existing category colors
|
||||
newCatSymbol->setColor( mRenderer->ranges().at( row ).symbol()->color() );
|
||||
}
|
||||
|
||||
mRenderer->updateRangeSymbol( row, newCatSymbol.release() );
|
||||
}
|
||||
emit widgetChanged();
|
||||
}
|
||||
|
@ -130,6 +130,10 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr
|
||||
void toggleMethodWidgets( int idx );
|
||||
void dataDefinedSizeLegend();
|
||||
|
||||
protected slots:
|
||||
|
||||
void pasteSymbolToSelection() override;
|
||||
|
||||
protected:
|
||||
void updateUiFromRenderer( bool updateCount = true );
|
||||
void connectUpdateHandlers();
|
||||
|
@ -25,10 +25,12 @@
|
||||
#include "qgspanelwidget.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgssymbollayerutils.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QClipboard>
|
||||
|
||||
QgsRendererWidget::QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style )
|
||||
: mLayer( layer )
|
||||
@ -36,10 +38,19 @@ QgsRendererWidget::QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style )
|
||||
{
|
||||
contextMenu = new QMenu( tr( "Renderer Options" ), this );
|
||||
|
||||
mCopyAction = contextMenu->addAction( tr( "Copy" ), this, SLOT( copy() ) );
|
||||
mCopyAction = new QAction( tr( "Copy" ), this );
|
||||
connect( mCopyAction, &QAction::triggered, this, &QgsRendererWidget::copy );
|
||||
mCopyAction->setShortcut( QKeySequence( QKeySequence::Copy ) );
|
||||
mPasteAction = contextMenu->addAction( tr( "Paste" ), this, SLOT( paste() ) );
|
||||
mPasteAction = new QAction( tr( "Paste" ), this );
|
||||
mPasteAction->setShortcut( QKeySequence( QKeySequence::Paste ) );
|
||||
connect( mPasteAction, &QAction::triggered, this, &QgsRendererWidget::paste );
|
||||
|
||||
mCopySymbolAction = new QAction( tr( "Copy Symbol" ), this );
|
||||
contextMenu->addAction( mCopySymbolAction );
|
||||
connect( mCopySymbolAction, &QAction::triggered, this, &QgsRendererWidget::copySymbol );
|
||||
mPasteSymbolAction = new QAction( tr( "Paste Symbol" ), this );
|
||||
contextMenu->addAction( mPasteSymbolAction );
|
||||
connect( mPasteSymbolAction, &QAction::triggered, this, &QgsRendererWidget::pasteSymbolToSelection );
|
||||
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeSymbolColor() ) );
|
||||
@ -55,6 +66,12 @@ QgsRendererWidget::QgsRendererWidget( QgsVectorLayer *layer, QgsStyle *style )
|
||||
contextMenu->addAction( tr( "Change Size…" ), this, SLOT( changeSymbolSize() ) );
|
||||
contextMenu->addAction( tr( "Change Angle…" ), this, SLOT( changeSymbolAngle() ) );
|
||||
}
|
||||
|
||||
connect( contextMenu, &QMenu::aboutToShow, this, [ = ]
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
|
||||
} );
|
||||
}
|
||||
|
||||
void QgsRendererWidget::contextMenuViewCategories( QPoint )
|
||||
@ -258,6 +275,22 @@ void QgsRendererWidget::changeSymbolAngle()
|
||||
}
|
||||
}
|
||||
|
||||
void QgsRendererWidget::pasteSymbolToSelection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QgsRendererWidget::copySymbol()
|
||||
{
|
||||
QList<QgsSymbol *> symbolList = selectedSymbols();
|
||||
if ( symbolList.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( symbolList.at( 0 ) ) );
|
||||
}
|
||||
|
||||
void QgsRendererWidget::showSymbolLevelsDialog( QgsFeatureRenderer *r )
|
||||
{
|
||||
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
|
||||
|
@ -95,6 +95,18 @@ class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget
|
||||
QAction *mCopyAction = nullptr;
|
||||
QAction *mPasteAction = nullptr;
|
||||
|
||||
/**
|
||||
* Copy symbol action.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QAction *mCopySymbolAction = nullptr;
|
||||
|
||||
/**
|
||||
* Paste symbol action.
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QAction *mPasteSymbolAction = nullptr;
|
||||
|
||||
//! Context in which widget is shown
|
||||
QgsSymbolWidgetContext mContext;
|
||||
|
||||
@ -126,9 +138,21 @@ class GUI_EXPORT QgsRendererWidget : public QgsPanelWidget
|
||||
//! Change marker angles of selected symbols
|
||||
void changeSymbolAngle();
|
||||
|
||||
|
||||
virtual void copy() {}
|
||||
virtual void paste() {}
|
||||
|
||||
/**
|
||||
* Pastes the clipboard symbol over selected items.
|
||||
*
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
virtual void pasteSymbolToSelection();
|
||||
|
||||
private slots:
|
||||
|
||||
void copySymbol();
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QClipboard>
|
||||
|
||||
#ifdef ENABLE_MODELTEST
|
||||
#include "modeltest.h"
|
||||
@ -51,6 +52,7 @@ QgsRendererWidget *QgsRuleBasedRendererWidget::create( QgsVectorLayer *layer, Qg
|
||||
|
||||
QgsRuleBasedRendererWidget::QgsRuleBasedRendererWidget( QgsVectorLayer *layer, QgsStyle *style, QgsFeatureRenderer *renderer )
|
||||
: QgsRendererWidget( layer, style )
|
||||
, mContextMenu( new QMenu( this ) )
|
||||
{
|
||||
mRenderer = nullptr;
|
||||
// try to recognize the previous renderer
|
||||
@ -90,7 +92,6 @@ QgsRuleBasedRendererWidget::QgsRuleBasedRendererWidget( QgsVectorLayer *layer, Q
|
||||
mRefineMenu->addAction( tr( "Add Categories to Rule" ), this, SLOT( refineRuleCategories() ) );
|
||||
mRefineMenu->addAction( tr( "Add Ranges to Rule" ), this, SLOT( refineRuleRanges() ) );
|
||||
btnRefineRule->setMenu( mRefineMenu );
|
||||
contextMenu->addMenu( mRefineMenu );
|
||||
|
||||
btnAddRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
|
||||
btnEditRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
|
||||
@ -99,7 +100,7 @@ QgsRuleBasedRendererWidget::QgsRuleBasedRendererWidget( QgsVectorLayer *layer, Q
|
||||
connect( viewRules, &QAbstractItemView::doubleClicked, this, static_cast < void ( QgsRuleBasedRendererWidget::* )( const QModelIndex &index ) > ( &QgsRuleBasedRendererWidget::editRule ) );
|
||||
|
||||
// support for context menu (now handled generically)
|
||||
connect( viewRules, &QWidget::customContextMenuRequested, this, &QgsRuleBasedRendererWidget::contextMenuViewCategories );
|
||||
connect( viewRules, &QWidget::customContextMenuRequested, this, &QgsRuleBasedRendererWidget::showContextMenu );
|
||||
|
||||
connect( viewRules->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsRuleBasedRendererWidget::currentRuleChanged );
|
||||
connect( viewRules->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsRuleBasedRendererWidget::selectedRulesChanged );
|
||||
@ -124,6 +125,11 @@ QgsRuleBasedRendererWidget::QgsRuleBasedRendererWidget( QgsVectorLayer *layer, Q
|
||||
|
||||
restoreSectionWidths();
|
||||
|
||||
connect( mContextMenu, &QMenu::aboutToShow, this, [ = ]
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
mPasteSymbolAction->setEnabled( static_cast< bool >( tempSymbol ) );
|
||||
} );
|
||||
}
|
||||
|
||||
QgsRuleBasedRendererWidget::~QgsRuleBasedRendererWidget()
|
||||
@ -483,6 +489,26 @@ void QgsRuleBasedRendererWidget::paste()
|
||||
mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
|
||||
}
|
||||
|
||||
void QgsRuleBasedRendererWidget::pasteSymbolToSelection()
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
|
||||
if ( !tempSymbol )
|
||||
return;
|
||||
|
||||
const QModelIndexList indexList = viewRules->selectionModel()->selectedRows();
|
||||
for ( const QModelIndex &index : indexList )
|
||||
{
|
||||
if ( QgsRuleBasedRenderer::Rule *rule = mModel->ruleForIndex( index ) )
|
||||
{
|
||||
if ( !rule->symbol() || rule->symbol()->type() != tempSymbol->type() )
|
||||
continue;
|
||||
|
||||
mModel->setSymbol( index, tempSymbol->clone() );
|
||||
}
|
||||
}
|
||||
emit widgetChanged();
|
||||
}
|
||||
|
||||
void QgsRuleBasedRendererWidget::refineRuleCategoriesAccepted( QgsPanelWidget *panel )
|
||||
{
|
||||
QgsCategorizedSymbolRendererWidget *w = qobject_cast<QgsCategorizedSymbolRendererWidget *>( panel );
|
||||
@ -535,6 +561,23 @@ void QgsRuleBasedRendererWidget::liveUpdateRuleFromPanel()
|
||||
ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
|
||||
}
|
||||
|
||||
void QgsRuleBasedRendererWidget::showContextMenu( QPoint )
|
||||
{
|
||||
mContextMenu->clear();
|
||||
mContextMenu->addAction( mCopyAction );
|
||||
mContextMenu->addAction( mPasteAction );
|
||||
|
||||
const QList< QAction * > actions = contextMenu->actions();
|
||||
for ( QAction *act : actions )
|
||||
{
|
||||
mContextMenu->addAction( act );
|
||||
}
|
||||
|
||||
mContextMenu->addMenu( mRefineMenu );
|
||||
|
||||
mContextMenu->exec( QCursor::pos() );
|
||||
}
|
||||
|
||||
|
||||
void QgsRuleBasedRendererWidget::countFeatures()
|
||||
{
|
||||
@ -1275,6 +1318,13 @@ void QgsRuleBasedRendererModel::removeRule( const QModelIndex &index )
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void QgsRuleBasedRendererModel::setSymbol( const QModelIndex &index, QgsSymbol *symbol )
|
||||
{
|
||||
QgsRuleBasedRenderer::Rule *rule = ruleForIndex( index );
|
||||
rule->setSymbol( symbol );
|
||||
emit dataChanged( index, index );
|
||||
}
|
||||
|
||||
void QgsRuleBasedRendererModel::willAddRules( const QModelIndex &parent, int count )
|
||||
{
|
||||
int row = rowCount( parent ); // only consider appending
|
||||
|
@ -85,6 +85,14 @@ class GUI_EXPORT QgsRuleBasedRendererModel : public QAbstractItemModel
|
||||
void updateRule( const QModelIndex &index );
|
||||
void removeRule( const QModelIndex &index );
|
||||
|
||||
/**
|
||||
* Sets the \a symbol for the rule at the specified \a index. Ownership of the symbols is
|
||||
* transferred to the renderer.
|
||||
*
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
void setSymbol( const QModelIndex &index, QgsSymbol *symbol SIP_TRANSFER );
|
||||
|
||||
void willAddRules( const QModelIndex &parent, int count ); // call beginInsertRows
|
||||
void finishedAddingRules(); // call endInsertRows
|
||||
|
||||
@ -162,16 +170,19 @@ class GUI_EXPORT QgsRuleBasedRendererWidget : public QgsRendererWidget, private
|
||||
QAction *mDeleteAction = nullptr;
|
||||
|
||||
QgsRuleBasedRenderer::RuleList mCopyBuffer;
|
||||
QMenu *mContextMenu = nullptr;
|
||||
|
||||
protected slots:
|
||||
void copy() override;
|
||||
void paste() override;
|
||||
void pasteSymbolToSelection() override;
|
||||
|
||||
private slots:
|
||||
void refineRuleCategoriesAccepted( QgsPanelWidget *panel );
|
||||
void refineRuleRangesAccepted( QgsPanelWidget *panel );
|
||||
void ruleWidgetPanelAccepted( QgsPanelWidget *panel );
|
||||
void liveUpdateRuleFromPanel();
|
||||
void showContextMenu( QPoint p );
|
||||
};
|
||||
|
||||
///////
|
||||
|
Loading…
x
Reference in New Issue
Block a user