Skip to content
Permalink
Browse files

Rule-based labeling GUI enhancements

- new column with label text
- copy / paste / delete rules (also with shortcuts)
- no label+description - only description (per rule)
- BONUS: copy/paste works also between rule-based renderer and labeling

This code has been funded by Tuscany Region (Italy) - SITA (CIG: 63526840AE) and commissioned to Gis3W s.a.s.
  • Loading branch information
wonder-sk committed Sep 24, 2015
1 parent 9970b45 commit e5cca7551f86054d8c9c9b351a77d1337a18019a
@@ -7,6 +7,7 @@
#include "qgsvectorlayer.h"
#include "qgsvectorlayerlabeling.h"

#include <QClipboard>
#include <QMessageBox>

QgsRuleBasedLabelingWidget::QgsRuleBasedLabelingWidget( QgsVectorLayer* layer, QgsMapCanvas* canvas, QWidget* parent )
@@ -22,11 +23,25 @@ QgsRuleBasedLabelingWidget::QgsRuleBasedLabelingWidget( QgsVectorLayer* layer, Q
btnEditRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.png" ) ) );
btnRemoveRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );

mCopyAction = new QAction( tr( "Copy" ), this );
mCopyAction->setShortcut( QKeySequence( QKeySequence::Copy ) );
mPasteAction = new QAction( tr( "Paste" ), this );
mPasteAction->setShortcut( QKeySequence( QKeySequence::Paste ) );
mDeleteAction = new QAction( tr( "Remove Rule" ), this );
mDeleteAction->setShortcut( QKeySequence( QKeySequence::Delete ) );

viewRules->addAction( mDeleteAction );
viewRules->addAction( mCopyAction );
viewRules->addAction( mPasteAction );

connect( viewRules, SIGNAL( doubleClicked( const QModelIndex & ) ), this, SLOT( editRule( const QModelIndex & ) ) );

connect( btnAddRule, SIGNAL( clicked() ), this, SLOT( addRule() ) );
connect( btnEditRule, SIGNAL( clicked() ), this, SLOT( editRule() ) );
connect( btnRemoveRule, SIGNAL( clicked() ), this, SLOT( removeRule() ) );
connect( mCopyAction, SIGNAL( triggered( bool ) ), this, SLOT( copy() ) );
connect( mPasteAction, SIGNAL( triggered( bool ) ), this, SLOT( paste() ) );
connect( mDeleteAction, SIGNAL( triggered( bool ) ), this, SLOT( removeRule() ) );

if ( mLayer->labeling() && mLayer->labeling()->type() == "rule-based" )
{
@@ -118,6 +133,30 @@ void QgsRuleBasedLabelingWidget::removeRule()
// TODO mModel->clearFeatureCounts();
}

void QgsRuleBasedLabelingWidget::copy()
{
QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
QgsDebugMsg( QString( "%1" ).arg( indexlist.count() ) );

if ( indexlist.isEmpty() )
return;

QMimeData* mime = mModel->mimeData( indexlist );
QApplication::clipboard()->setMimeData( mime );
}

void QgsRuleBasedLabelingWidget::paste()
{
const QMimeData* mime = QApplication::clipboard()->mimeData();
QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
QModelIndex index;
if ( indexlist.isEmpty() )
index = mModel->index( mModel->rowCount(), 0 );
else
index = indexlist.first();
mModel->dropMimeData( mime, Qt::CopyAction, index.row(), index.column(), index.parent() );
}

QgsRuleBasedLabeling::Rule* QgsRuleBasedLabelingWidget::currentRule()
{
QItemSelectionModel* sel = viewRules->selectionModel();
@@ -175,7 +214,7 @@ QVariant QgsRuleBasedLabelingModel::data( const QModelIndex& index, int role ) c
{
switch ( index.column() )
{
case 0: return rule->label();
case 0: return rule->description();
case 1:
if ( rule->isElse() )
{
@@ -187,6 +226,7 @@ QVariant QgsRuleBasedLabelingModel::data( const QModelIndex& index, int role ) c
}
case 2: return rule->dependsOnScale() ? _formatScale( rule->scaleMaxDenom() ) : QVariant();
case 3: return rule->dependsOnScale() ? _formatScale( rule->scaleMinDenom() ) : QVariant();
case 4: return rule->settings() ? rule->settings()->fieldName : QVariant();
#if 0 // TODO: feature counts?
case 4:
if ( mFeatureCountMap.count( rule ) == 1 )
@@ -248,10 +288,11 @@ QVariant QgsRuleBasedLabelingModel::data( const QModelIndex& index, int role ) c
{
switch ( index.column() )
{
case 0: return rule->label();
case 0: return rule->description();
case 1: return rule->filterExpression();
case 2: return rule->scaleMaxDenom();
case 3: return rule->scaleMinDenom();
case 4: return rule->settings() ? rule->settings()->fieldName : QVariant();
default: return QVariant();
}
}
@@ -269,7 +310,7 @@ QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation ori
{
if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
{
QStringList lst; lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. scale" ) << tr( "Max. scale" ); // << tr( "Count" ) << tr( "Duplicate count" );
QStringList lst; lst << tr( "Label" ) << tr( "Rule" ) << tr( "Min. scale" ) << tr( "Max. scale" ) << tr( "Text" ); // << tr( "Count" ) << tr( "Duplicate count" );
return lst[section];
}
else if ( orientation == Qt::Horizontal && role == Qt::ToolTipRole )
@@ -300,7 +341,7 @@ int QgsRuleBasedLabelingModel::rowCount( const QModelIndex& parent ) const

int QgsRuleBasedLabelingModel::columnCount( const QModelIndex& ) const
{
return 4;
return 5;
}

QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex& parent ) const
@@ -350,8 +391,8 @@ bool QgsRuleBasedLabelingModel::setData( const QModelIndex& index, const QVarian

switch ( index.column() )
{
case 0: // label
rule->setLabel( value.toString() );
case 0: // description
rule->setDescription( value.toString() );
break;
case 1: // filter
rule->setFilterExpression( value.toString() );
@@ -362,6 +403,11 @@ bool QgsRuleBasedLabelingModel::setData( const QModelIndex& index, const QVarian
case 3: // scale max
rule->setScaleMinDenom( value.toInt() );
break;
case 4: // label text
if ( !rule->settings() )
return false;
rule->settings()->fieldName = value.toString();
break;
default:
return false;
}
@@ -382,6 +428,22 @@ QStringList QgsRuleBasedLabelingModel::mimeTypes() const
return types;
}

// manipulate DOM before dropping it so that rules are more useful
void _renderer2labelingRules( QDomElement& ruleElem )
{
// labeling rules recognize only "description"
if ( ruleElem.hasAttribute( "label" ) )
ruleElem.setAttribute( "description", ruleElem.attribute( "label" ) );

// run recursively
QDomElement childRuleElem = ruleElem.firstChildElement( "rule" );
while ( !childRuleElem.isNull() )
{
_renderer2labelingRules( childRuleElem );
childRuleElem = childRuleElem.nextSiblingElement( "rule" );
}
}

QMimeData*QgsRuleBasedLabelingModel::mimeData( const QModelIndexList& indexes ) const
{
QMimeData *mimeData = new QMimeData();
@@ -401,6 +463,7 @@ QMimeData*QgsRuleBasedLabelingModel::mimeData( const QModelIndexList& indexes )
QDomDocument doc;

QDomElement rootElem = doc.createElement( "rule_mime" );
rootElem.setAttribute( "type", "labeling" ); // for determining whether rules are from renderer or labeling
QDomElement rulesElem = rule->save( doc );
rootElem.appendChild( rulesElem );
doc.appendChild( rootElem );
@@ -449,6 +512,8 @@ bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData* data, Qt::DropAct
if ( rootElem.tagName() != "rule_mime" )
continue;
QDomElement ruleElem = rootElem.firstChildElement( "rule" );
if ( rootElem.attribute( "type" ) == "renderer" )
_renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
QgsRuleBasedLabeling::Rule* rule = QgsRuleBasedLabeling::Rule::create( ruleElem );

insertRule( parent, row + rows, rule );
@@ -526,7 +591,6 @@ QgsLabelingRulePropsDialog::QgsLabelingRulePropsDialog( QgsRuleBasedLabeling::Ru

editFilter->setText( mRule->filterExpression() );
editFilter->setToolTip( mRule->filterExpression() );
editLabel->setText( mRule->label() );
editDescription->setText( mRule->description() );
editDescription->setToolTip( mRule->description() );

@@ -653,7 +717,6 @@ void QgsLabelingRulePropsDialog::buildExpression()
void QgsLabelingRulePropsDialog::accept()
{
mRule->setFilterExpression( editFilter->text() );
mRule->setLabel( editLabel->text() );
mRule->setDescription( editDescription->text() );
// caution: rule uses scale denom, scale widget uses true scales
mRule->setScaleMinDenom( groupScale->isChecked() ? mScaleRangeWidget->minimumScaleDenom() : 0 );
@@ -82,6 +82,8 @@ class QgsRuleBasedLabelingWidget : public QWidget, private Ui::QgsRuleBasedLabel
void editRule();
void editRule( const QModelIndex& index );
void removeRule();
void copy();
void paste();

protected:
QgsRuleBasedLabeling::Rule* currentRule();
@@ -92,6 +94,10 @@ class QgsRuleBasedLabelingWidget : public QWidget, private Ui::QgsRuleBasedLabel

QgsRuleBasedLabeling::Rule* mRootRule;
QgsRuleBasedLabelingModel* mModel;

QAction* mCopyAction;
QAction* mPasteAction;
QAction* mDeleteAction;
};


@@ -42,10 +42,10 @@ QList<QgsAbstractLabelProvider*> QgsRuleBasedLabelProvider::subProviders()

////////////////////

QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings* settings, int scaleMinDenom, int scaleMaxDenom, const QString& filterExp, const QString& label, const QString& description, bool elseRule )
QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings* settings, int scaleMinDenom, int scaleMaxDenom, const QString& filterExp, const QString& description, bool elseRule )
: mParent( 0 ), mSettings( settings )
, mScaleMinDenom( scaleMinDenom ), mScaleMaxDenom( scaleMaxDenom )
, mFilterExp( filterExp ), mLabel( label ), mDescription( description )
, mFilterExp( filterExp ), mDescription( description )
, mElseRule( elseRule )
, mIsActive( true )
, mFilter( 0 )
@@ -124,7 +124,7 @@ void QgsRuleBasedLabeling::Rule::removeChildAt( int i )
QgsRuleBasedLabeling::Rule*QgsRuleBasedLabeling::Rule::clone() const
{
QgsPalLayerSettings* s = mSettings ? new QgsPalLayerSettings( *mSettings ) : 0;
Rule* newrule = new Rule( s, mScaleMinDenom, mScaleMaxDenom, mFilterExp, mLabel, mDescription );
Rule* newrule = new Rule( s, mScaleMinDenom, mScaleMaxDenom, mFilterExp, mDescription );
newrule->setActive( mIsActive );
// clone children
Q_FOREACH ( Rule* rule, mChildren )
@@ -143,12 +143,11 @@ QgsRuleBasedLabeling::Rule*QgsRuleBasedLabeling::Rule::create( const QDomElement
}

QString filterExp = ruleElem.attribute( "filter" );
QString label = ruleElem.attribute( "label" );
QString description = ruleElem.attribute( "description" );
int scaleMinDenom = ruleElem.attribute( "scalemindenom", "0" ).toInt();
int scaleMaxDenom = ruleElem.attribute( "scalemaxdenom", "0" ).toInt();
//QString ruleKey = ruleElem.attribute( "key" );
Rule* rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
Rule* rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, description );

//if ( !ruleKey.isEmpty() )
// rule->mRuleKey = ruleKey;
@@ -187,8 +186,6 @@ QDomElement QgsRuleBasedLabeling::Rule::save( QDomDocument& doc ) const
ruleElem.setAttribute( "scalemindenom", mScaleMinDenom );
if ( mScaleMaxDenom != 0 )
ruleElem.setAttribute( "scalemaxdenom", mScaleMaxDenom );
if ( !mLabel.isEmpty() )
ruleElem.setAttribute( "label", mLabel );
if ( !mDescription.isEmpty() )
ruleElem.setAttribute( "description", mDescription );
if ( !mIsActive )
@@ -25,7 +25,7 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
{
public:
//! takes ownership of settings
Rule( QgsPalLayerSettings* settings, int scaleMinDenom = 0, int scaleMaxDenom = 0, const QString& filterExp = QString(), const QString& label = QString(), const QString& description = QString(), bool elseRule = false );
Rule( QgsPalLayerSettings* settings, int scaleMinDenom = 0, int scaleMaxDenom = 0, const QString& filterExp = QString(), const QString& description = QString(), bool elseRule = false );
~Rule();

//! The result of registering a rule
@@ -37,7 +37,6 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
};

QgsPalLayerSettings* settings() const { return mSettings; }
QString label() const { return mLabel; }
bool dependsOnScale() const { return mScaleMinDenom != 0 || mScaleMaxDenom != 0; }
int scaleMinDenom() const { return mScaleMinDenom; }
int scaleMaxDenom() const { return mScaleMaxDenom; }
@@ -68,7 +67,6 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
//! set new settings (or NULL). Deletes old settings if any.
void setSettings( QgsPalLayerSettings* settings );

void setLabel( QString label ) { mLabel = label; }
/**
* Set the minimum denominator for which this rule shall apply.
* E.g. 1000 if it shall be evaluated between 1:1000 and 1:100'000
@@ -198,7 +196,7 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
Rule* mParent; // parent rule (NULL only for root rule)
QgsPalLayerSettings* mSettings;
int mScaleMinDenom, mScaleMaxDenom;
QString mFilterExp, mLabel, mDescription;
QString mFilterExp, mDescription;
bool mElseRule;
RuleList mChildren;
RuleList mElseRules;
@@ -1003,6 +1003,7 @@ QMimeData *QgsRuleBasedRendererV2Model::mimeData( const QModelIndexList &indexes
QgsSymbolV2Map symbols;

QDomElement rootElem = doc.createElement( "rule_mime" );
rootElem.setAttribute( "type", "renderer" ); // for determining whether rules are from renderer or labeling
QDomElement rulesElem = rule->save( doc, symbols );
rootElem.appendChild( rulesElem );
QDomElement symbolsElem = QgsSymbolLayerV2Utils::saveSymbols( symbols, "symbols", doc );
@@ -1018,6 +1019,24 @@ QMimeData *QgsRuleBasedRendererV2Model::mimeData( const QModelIndexList &indexes
return mimeData;
}


// manipulate DOM before dropping it so that rules are more useful
void _labeling2rendererRules( QDomElement& ruleElem )
{
// labeling rules recognize only "description"
if ( ruleElem.hasAttribute( "description" ) )
ruleElem.setAttribute( "label", ruleElem.attribute( "description" ) );

// run recursively
QDomElement childRuleElem = ruleElem.firstChildElement( "rule" );
while ( !childRuleElem.isNull() )
{
_labeling2rendererRules( childRuleElem );
childRuleElem = childRuleElem.nextSiblingElement( "rule" );
}
}


bool QgsRuleBasedRendererV2Model::dropMimeData( const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
@@ -1053,11 +1072,15 @@ bool QgsRuleBasedRendererV2Model::dropMimeData( const QMimeData *data,
QDomElement rootElem = doc.documentElement();
if ( rootElem.tagName() != "rule_mime" )
continue;
if ( rootElem.attribute( "type" ) == "labeling" )
rootElem.appendChild( doc.createElement( "symbols" ) );
QDomElement symbolsElem = rootElem.firstChildElement( "symbols" );
if ( symbolsElem.isNull() )
continue;
QgsSymbolV2Map symbolMap = QgsSymbolLayerV2Utils::loadSymbols( symbolsElem );
QDomElement ruleElem = rootElem.firstChildElement( "rule" );
if ( rootElem.attribute( "type" ) == "labeling" )
_labeling2rendererRules( ruleElem );
QgsRuleBasedRendererV2::Rule* rule = QgsRuleBasedRendererV2::Rule::create( ruleElem, symbolMap );

insertRule( parent, row + rows, rule );
@@ -22,12 +22,12 @@
<item row="0" column="0">
<widget class="QLabel" name="label_1">
<property name="text">
<string>Label</string>
<string>Description</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="editLabel"/>
<widget class="QLineEdit" name="editDescription"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
@@ -63,16 +63,6 @@
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="editDescription"/>
</item>
</layout>
</item>
<item>
@@ -136,11 +126,10 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>editLabel</tabstop>
<tabstop>editDescription</tabstop>
<tabstop>editFilter</tabstop>
<tabstop>btnExpressionBuilder</tabstop>
<tabstop>btnTestFilter</tabstop>
<tabstop>editDescription</tabstop>
<tabstop>groupScale</tabstop>
<tabstop>groupSettings</tabstop>
<tabstop>buttonBox</tabstop>
@@ -16,6 +16,9 @@
</property>
<item>
<widget class="QTreeView" name="viewRules">
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>

0 comments on commit e5cca75

Please sign in to comment.
You can’t perform that action at this time.