mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-10 00:05:25 -04:00
692 lines
22 KiB
C++
692 lines
22 KiB
C++
/***************************************************************************
|
|
qgsrulebasedlabelingwidget.cpp
|
|
---------------------
|
|
begin : September 2015
|
|
copyright : (C) 2015 by Martin Dobias
|
|
email : wonder dot sk at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
#include "qgsrulebasedlabelingwidget.h"
|
|
|
|
#include "qgsapplication.h"
|
|
#include "qgsexpressionbuilderdialog.h"
|
|
#include "qgsfeatureiterator.h"
|
|
#include "qgslabelinggui.h"
|
|
#include "qgsmapcanvas.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsreadwritecontext.h"
|
|
#include "qgsrulebasedlabeling.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsvectorlayerlabeling.h"
|
|
#include "qgslogger.h"
|
|
|
|
#include <QClipboard>
|
|
#include <QMessageBox>
|
|
|
|
|
|
static QList<QgsExpressionContextScope *> _globalProjectAtlasMapLayerScopes( QgsMapCanvas *mapCanvas, const QgsMapLayer *layer )
|
|
{
|
|
QList<QgsExpressionContextScope *> scopes;
|
|
scopes << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
|
|
<< QgsExpressionContextUtils::compositionAtlasScope( nullptr );
|
|
if ( mapCanvas )
|
|
{
|
|
scopes << QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() )
|
|
<< new QgsExpressionContextScope( mapCanvas->expressionContextScope() );
|
|
}
|
|
else
|
|
{
|
|
scopes << QgsExpressionContextUtils::mapSettingsScope( QgsMapSettings() );
|
|
}
|
|
scopes << QgsExpressionContextUtils::layerScope( layer );
|
|
return scopes;
|
|
}
|
|
|
|
|
|
QgsRuleBasedLabelingWidget::QgsRuleBasedLabelingWidget( QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent )
|
|
: QgsPanelWidget( parent )
|
|
, mLayer( layer )
|
|
, mCanvas( canvas )
|
|
|
|
{
|
|
setupUi( this );
|
|
|
|
btnAddRule->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
|
|
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, &QAbstractItemView::doubleClicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )( const QModelIndex & )>( &QgsRuleBasedLabelingWidget::editRule ) );
|
|
|
|
connect( btnAddRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::addRule );
|
|
connect( btnEditRule, &QAbstractButton::clicked, this, static_cast<void ( QgsRuleBasedLabelingWidget::* )()>( &QgsRuleBasedLabelingWidget::editRule ) );
|
|
connect( btnRemoveRule, &QAbstractButton::clicked, this, &QgsRuleBasedLabelingWidget::removeRule );
|
|
connect( mCopyAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::copy );
|
|
connect( mPasteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::paste );
|
|
connect( mDeleteAction, &QAction::triggered, this, &QgsRuleBasedLabelingWidget::removeRule );
|
|
|
|
if ( mLayer->labeling() && mLayer->labeling()->type() == QLatin1String( "rule-based" ) )
|
|
{
|
|
const QgsRuleBasedLabeling *rl = static_cast<const QgsRuleBasedLabeling *>( mLayer->labeling() );
|
|
mRootRule = rl->rootRule()->clone();
|
|
}
|
|
else
|
|
{
|
|
mRootRule = new QgsRuleBasedLabeling::Rule( nullptr );
|
|
}
|
|
|
|
mModel = new QgsRuleBasedLabelingModel( mRootRule );
|
|
viewRules->setModel( mModel );
|
|
|
|
connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsRuleBasedLabelingWidget::widgetChanged );
|
|
connect( mModel, &QAbstractItemModel::rowsInserted, this, &QgsRuleBasedLabelingWidget::widgetChanged );
|
|
connect( mModel, &QAbstractItemModel::rowsRemoved, this, &QgsRuleBasedLabelingWidget::widgetChanged );
|
|
}
|
|
|
|
QgsRuleBasedLabelingWidget::~QgsRuleBasedLabelingWidget()
|
|
{
|
|
delete mRootRule;
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::addRule()
|
|
{
|
|
|
|
QgsRuleBasedLabeling::Rule *newrule = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings );
|
|
|
|
QgsRuleBasedLabeling::Rule *current = currentRule();
|
|
if ( current )
|
|
{
|
|
// add after this rule
|
|
QModelIndex currentIndex = viewRules->selectionModel()->currentIndex();
|
|
mModel->insertRule( currentIndex.parent(), currentIndex.row() + 1, newrule );
|
|
QModelIndex newindex = mModel->index( currentIndex.row() + 1, 0, currentIndex.parent() );
|
|
viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
|
|
}
|
|
else
|
|
{
|
|
// append to root rule
|
|
int rows = mModel->rowCount();
|
|
mModel->insertRule( QModelIndex(), rows, newrule );
|
|
QModelIndex newindex = mModel->index( rows, 0 );
|
|
viewRules->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect );
|
|
}
|
|
editRule();
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted( QgsPanelWidget *panel )
|
|
{
|
|
QgsLabelingRulePropsWidget *widget = qobject_cast<QgsLabelingRulePropsWidget *>( panel );
|
|
widget->apply();
|
|
|
|
QModelIndex index = viewRules->selectionModel()->currentIndex();
|
|
mModel->updateRule( index.parent(), index.row() );
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel()
|
|
{
|
|
ruleWidgetPanelAccepted( qobject_cast<QgsPanelWidget *>( sender() ) );
|
|
}
|
|
|
|
|
|
void QgsRuleBasedLabelingWidget::editRule()
|
|
{
|
|
editRule( viewRules->selectionModel()->currentIndex() );
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::editRule( const QModelIndex &index )
|
|
{
|
|
if ( !index.isValid() )
|
|
return;
|
|
|
|
QgsRuleBasedLabeling::Rule *rule = mModel->ruleForIndex( index );
|
|
|
|
QgsLabelingRulePropsWidget *widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
|
|
widget->setPanelTitle( tr( "Edit rule" ) );
|
|
connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsRuleBasedLabelingWidget::ruleWidgetPanelAccepted );
|
|
connect( widget, &QgsLabelingRulePropsWidget::widgetChanged, this, &QgsRuleBasedLabelingWidget::liveUpdateRuleFromPanel );
|
|
openPanel( widget );
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::removeRule()
|
|
{
|
|
QItemSelection sel = viewRules->selectionModel()->selection();
|
|
Q_FOREACH ( const QItemSelectionRange &range, sel )
|
|
{
|
|
if ( range.isValid() )
|
|
mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
|
|
}
|
|
// make sure that the selection is gone
|
|
viewRules->selectionModel()->clear();
|
|
}
|
|
|
|
void QgsRuleBasedLabelingWidget::copy()
|
|
{
|
|
QModelIndexList indexlist = viewRules->selectionModel()->selectedRows();
|
|
|
|
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();
|
|
QModelIndex idx = sel->currentIndex();
|
|
if ( !idx.isValid() )
|
|
return nullptr;
|
|
return mModel->ruleForIndex( idx );
|
|
}
|
|
|
|
////
|
|
|
|
QgsRuleBasedLabelingModel::QgsRuleBasedLabelingModel( QgsRuleBasedLabeling::Rule *rootRule, QObject *parent )
|
|
: QAbstractItemModel( parent )
|
|
, mRootRule( rootRule )
|
|
{
|
|
}
|
|
|
|
Qt::ItemFlags QgsRuleBasedLabelingModel::flags( const QModelIndex &index ) const
|
|
{
|
|
if ( !index.isValid() )
|
|
return Qt::ItemIsDropEnabled;
|
|
|
|
// allow drop only at first column
|
|
Qt::ItemFlag drop = ( index.column() == 0 ? Qt::ItemIsDropEnabled : Qt::NoItemFlags );
|
|
|
|
Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags );
|
|
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
|
Qt::ItemIsEditable | checkable |
|
|
Qt::ItemIsDragEnabled | drop;
|
|
}
|
|
|
|
QVariant QgsRuleBasedLabelingModel::data( const QModelIndex &index, int role ) const
|
|
{
|
|
if ( !index.isValid() )
|
|
return QVariant();
|
|
|
|
QgsRuleBasedLabeling::Rule *rule = ruleForIndex( index );
|
|
|
|
if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
|
|
{
|
|
switch ( index.column() )
|
|
{
|
|
case 0:
|
|
return rule->description();
|
|
case 1:
|
|
if ( rule->isElse() )
|
|
{
|
|
return "ELSE";
|
|
}
|
|
else
|
|
{
|
|
return rule->filterExpression().isEmpty() ? tr( "(no filter)" ) : rule->filterExpression();
|
|
}
|
|
case 2:
|
|
return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->minimumScale() ) : QVariant();
|
|
case 3:
|
|
return rule->dependsOnScale() ? QgsScaleComboBox::toString( rule->maximumScale() ) : QVariant();
|
|
case 4:
|
|
return rule->settings() ? rule->settings()->fieldName : QVariant();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
else if ( role == Qt::DecorationRole && index.column() == 0 && rule->settings() )
|
|
{
|
|
// TODO return QgsSymbolLayerUtils::symbolPreviewIcon( rule->symbol(), QSize( 16, 16 ) );
|
|
return QVariant();
|
|
}
|
|
else if ( role == Qt::TextAlignmentRole )
|
|
{
|
|
return ( index.column() == 2 || index.column() == 3 ) ? Qt::AlignRight : Qt::AlignLeft;
|
|
}
|
|
else if ( role == Qt::FontRole && index.column() == 1 )
|
|
{
|
|
if ( rule->isElse() )
|
|
{
|
|
QFont italicFont;
|
|
italicFont.setItalic( true );
|
|
return italicFont;
|
|
}
|
|
return QVariant();
|
|
}
|
|
else if ( role == Qt::EditRole )
|
|
{
|
|
switch ( index.column() )
|
|
{
|
|
case 0:
|
|
return rule->description();
|
|
case 1:
|
|
return rule->filterExpression();
|
|
case 2:
|
|
return rule->minimumScale();
|
|
case 3:
|
|
return rule->maximumScale();
|
|
case 4:
|
|
return rule->settings() ? rule->settings()->fieldName : QVariant();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
else if ( role == Qt::CheckStateRole )
|
|
{
|
|
if ( index.column() != 0 )
|
|
return QVariant();
|
|
return rule->active() ? Qt::Checked : Qt::Unchecked;
|
|
}
|
|
else
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant QgsRuleBasedLabelingModel::headerData( int section, Qt::Orientation orientation, int role ) const
|
|
{
|
|
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( "Text" ); // << tr( "Count" ) << tr( "Duplicate count" );
|
|
return lst[section];
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
int QgsRuleBasedLabelingModel::rowCount( const QModelIndex &parent ) const
|
|
{
|
|
if ( parent.column() > 0 )
|
|
return 0;
|
|
|
|
QgsRuleBasedLabeling::Rule *parentRule = ruleForIndex( parent );
|
|
|
|
return parentRule->children().count();
|
|
}
|
|
|
|
int QgsRuleBasedLabelingModel::columnCount( const QModelIndex & ) const
|
|
{
|
|
return 5;
|
|
}
|
|
|
|
QModelIndex QgsRuleBasedLabelingModel::index( int row, int column, const QModelIndex &parent ) const
|
|
{
|
|
if ( hasIndex( row, column, parent ) )
|
|
{
|
|
QgsRuleBasedLabeling::Rule *parentRule = ruleForIndex( parent );
|
|
QgsRuleBasedLabeling::Rule *childRule = parentRule->children()[row];
|
|
return createIndex( row, column, childRule );
|
|
}
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex QgsRuleBasedLabelingModel::parent( const QModelIndex &index ) const
|
|
{
|
|
if ( !index.isValid() )
|
|
return QModelIndex();
|
|
|
|
QgsRuleBasedLabeling::Rule *childRule = ruleForIndex( index );
|
|
QgsRuleBasedLabeling::Rule *parentRule = childRule->parent();
|
|
|
|
if ( parentRule == mRootRule )
|
|
return QModelIndex();
|
|
|
|
// this is right: we need to know row number of our parent (in our grandparent)
|
|
int row = parentRule->parent()->children().indexOf( parentRule );
|
|
|
|
return createIndex( row, 0, parentRule );
|
|
}
|
|
|
|
bool QgsRuleBasedLabelingModel::setData( const QModelIndex &index, const QVariant &value, int role )
|
|
{
|
|
if ( !index.isValid() )
|
|
return false;
|
|
|
|
QgsRuleBasedLabeling::Rule *rule = ruleForIndex( index );
|
|
|
|
if ( role == Qt::CheckStateRole )
|
|
{
|
|
rule->setActive( value.toInt() == Qt::Checked );
|
|
emit dataChanged( index, index );
|
|
return true;
|
|
}
|
|
|
|
if ( role != Qt::EditRole )
|
|
return false;
|
|
|
|
switch ( index.column() )
|
|
{
|
|
case 0: // description
|
|
rule->setDescription( value.toString() );
|
|
break;
|
|
case 1: // filter
|
|
rule->setFilterExpression( value.toString() );
|
|
break;
|
|
case 2: // scale min
|
|
rule->setMinimumScale( value.toDouble() );
|
|
break;
|
|
case 3: // scale max
|
|
rule->setMaximumScale( value.toDouble() );
|
|
break;
|
|
case 4: // label text
|
|
if ( !rule->settings() )
|
|
return false;
|
|
rule->settings()->fieldName = value.toString();
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
emit dataChanged( index, index );
|
|
return true;
|
|
}
|
|
|
|
Qt::DropActions QgsRuleBasedLabelingModel::supportedDropActions() const
|
|
{
|
|
return Qt::MoveAction; // | Qt::CopyAction
|
|
}
|
|
|
|
QStringList QgsRuleBasedLabelingModel::mimeTypes() const
|
|
{
|
|
QStringList types;
|
|
types << QStringLiteral( "application/vnd.text.list" );
|
|
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( QStringLiteral( "label" ) ) )
|
|
ruleElem.setAttribute( QStringLiteral( "description" ), ruleElem.attribute( QStringLiteral( "label" ) ) );
|
|
|
|
// run recursively
|
|
QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
|
|
while ( !childRuleElem.isNull() )
|
|
{
|
|
_renderer2labelingRules( childRuleElem );
|
|
childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
|
|
}
|
|
}
|
|
|
|
QMimeData *QgsRuleBasedLabelingModel::mimeData( const QModelIndexList &indexes ) const
|
|
{
|
|
QMimeData *mimeData = new QMimeData();
|
|
QByteArray encodedData;
|
|
|
|
QDataStream stream( &encodedData, QIODevice::WriteOnly );
|
|
|
|
Q_FOREACH ( const QModelIndex &index, indexes )
|
|
{
|
|
// each item consists of several columns - let's add it with just first one
|
|
if ( !index.isValid() || index.column() != 0 )
|
|
continue;
|
|
|
|
// we use a clone of the existing rule because it has a new unique rule key
|
|
// non-unique rule keys would confuse other components using them (e.g. legend)
|
|
QgsRuleBasedLabeling::Rule *rule = ruleForIndex( index )->clone();
|
|
QDomDocument doc;
|
|
|
|
QDomElement rootElem = doc.createElement( QStringLiteral( "rule_mime" ) );
|
|
rootElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "labeling" ) ); // for determining whether rules are from renderer or labeling
|
|
QDomElement rulesElem = rule->save( doc, QgsReadWriteContext() );
|
|
rootElem.appendChild( rulesElem );
|
|
doc.appendChild( rootElem );
|
|
|
|
delete rule;
|
|
|
|
stream << doc.toString( -1 );
|
|
}
|
|
|
|
mimeData->setData( QStringLiteral( "application/vnd.text.list" ), encodedData );
|
|
return mimeData;
|
|
}
|
|
|
|
bool QgsRuleBasedLabelingModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
|
|
{
|
|
Q_UNUSED( column );
|
|
|
|
if ( action == Qt::IgnoreAction )
|
|
return true;
|
|
|
|
if ( !data->hasFormat( QStringLiteral( "application/vnd.text.list" ) ) )
|
|
return false;
|
|
|
|
if ( parent.column() > 0 )
|
|
return false;
|
|
|
|
QByteArray encodedData = data->data( QStringLiteral( "application/vnd.text.list" ) );
|
|
QDataStream stream( &encodedData, QIODevice::ReadOnly );
|
|
int rows = 0;
|
|
|
|
if ( row == -1 )
|
|
{
|
|
// the item was dropped at a parent - we may decide where to put the items - let's append them
|
|
row = rowCount( parent );
|
|
}
|
|
|
|
while ( !stream.atEnd() )
|
|
{
|
|
QString text;
|
|
stream >> text;
|
|
|
|
QDomDocument doc;
|
|
if ( !doc.setContent( text ) )
|
|
continue;
|
|
QDomElement rootElem = doc.documentElement();
|
|
if ( rootElem.tagName() != QLatin1String( "rule_mime" ) )
|
|
continue;
|
|
QDomElement ruleElem = rootElem.firstChildElement( QStringLiteral( "rule" ) );
|
|
if ( rootElem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "renderer" ) )
|
|
_renderer2labelingRules( ruleElem ); // do some modifications so that we load the rules more nicely
|
|
QgsRuleBasedLabeling::Rule *rule = QgsRuleBasedLabeling::Rule::create( ruleElem, QgsReadWriteContext() );
|
|
|
|
insertRule( parent, row + rows, rule );
|
|
|
|
++rows;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QgsRuleBasedLabelingModel::removeRows( int row, int count, const QModelIndex &parent )
|
|
{
|
|
QgsRuleBasedLabeling::Rule *parentRule = ruleForIndex( parent );
|
|
|
|
if ( row < 0 || row >= parentRule->children().count() )
|
|
return false;
|
|
|
|
beginRemoveRows( parent, row, row + count - 1 );
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( row < parentRule->children().count() )
|
|
{
|
|
parentRule->removeChildAt( row );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "trying to remove invalid index - this should not happen!" );
|
|
}
|
|
}
|
|
|
|
endRemoveRows();
|
|
|
|
return true;
|
|
}
|
|
|
|
QgsRuleBasedLabeling::Rule *QgsRuleBasedLabelingModel::ruleForIndex( const QModelIndex &index ) const
|
|
{
|
|
if ( index.isValid() )
|
|
return static_cast<QgsRuleBasedLabeling::Rule *>( index.internalPointer() );
|
|
return mRootRule;
|
|
}
|
|
|
|
void QgsRuleBasedLabelingModel::insertRule( const QModelIndex &parent, int before, QgsRuleBasedLabeling::Rule *newrule )
|
|
{
|
|
beginInsertRows( parent, before, before );
|
|
|
|
QgsRuleBasedLabeling::Rule *parentRule = ruleForIndex( parent );
|
|
parentRule->insertChild( before, newrule );
|
|
|
|
endInsertRows();
|
|
}
|
|
|
|
void QgsRuleBasedLabelingModel::updateRule( const QModelIndex &parent, int row )
|
|
{
|
|
emit dataChanged( index( row, 0, parent ),
|
|
index( row, columnCount( parent ), parent ) );
|
|
}
|
|
|
|
/////////
|
|
|
|
QgsLabelingRulePropsWidget::QgsLabelingRulePropsWidget( QgsRuleBasedLabeling::Rule *rule, QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *mapCanvas )
|
|
: QgsPanelWidget( parent )
|
|
, mRule( rule )
|
|
, mLayer( layer )
|
|
, mSettings( nullptr )
|
|
, mMapCanvas( mapCanvas )
|
|
{
|
|
setupUi( this );
|
|
|
|
editFilter->setText( mRule->filterExpression() );
|
|
editFilter->setToolTip( mRule->filterExpression() );
|
|
editDescription->setText( mRule->description() );
|
|
editDescription->setToolTip( mRule->description() );
|
|
|
|
if ( mRule->dependsOnScale() )
|
|
{
|
|
groupScale->setChecked( true );
|
|
// caution: rule uses scale denom, scale widget uses true scales
|
|
mScaleRangeWidget->setMaximumScale( std::max( rule->maximumScale(), 0.0 ) );
|
|
mScaleRangeWidget->setMinimumScale( std::max( rule->minimumScale(), 0.0 ) );
|
|
}
|
|
mScaleRangeWidget->setMapCanvas( mMapCanvas );
|
|
|
|
if ( mRule->settings() )
|
|
{
|
|
groupSettings->setChecked( true );
|
|
mSettings = new QgsPalLayerSettings( *mRule->settings() ); // use a clone!
|
|
}
|
|
else
|
|
{
|
|
groupSettings->setChecked( false );
|
|
mSettings = new QgsPalLayerSettings;
|
|
}
|
|
|
|
mLabelingGui = new QgsLabelingGui( nullptr, mMapCanvas, *mSettings, this );
|
|
mLabelingGui->layout()->setContentsMargins( 0, 0, 0, 0 );
|
|
QVBoxLayout *l = new QVBoxLayout;
|
|
l->addWidget( mLabelingGui );
|
|
groupSettings->setLayout( l );
|
|
|
|
mLabelingGui->setLabelMode( QgsLabelingGui::Labels );
|
|
mLabelingGui->setLayer( mLayer );
|
|
|
|
connect( btnExpressionBuilder, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::buildExpression );
|
|
connect( btnTestFilter, &QAbstractButton::clicked, this, &QgsLabelingRulePropsWidget::testFilter );
|
|
connect( editFilter, &QLineEdit::textEdited, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
connect( editDescription, &QLineEdit::textChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
connect( groupScale, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
connect( mScaleRangeWidget, &QgsScaleRangeWidget::rangeChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
connect( groupSettings, &QGroupBox::toggled, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
connect( mLabelingGui, &QgsTextFormatWidget::widgetChanged, this, &QgsLabelingRulePropsWidget::widgetChanged );
|
|
}
|
|
|
|
QgsLabelingRulePropsWidget::~QgsLabelingRulePropsWidget()
|
|
{
|
|
delete mSettings;
|
|
}
|
|
|
|
void QgsLabelingRulePropsWidget::setDockMode( bool dockMode )
|
|
{
|
|
QgsPanelWidget::setDockMode( dockMode );
|
|
mLabelingGui->setDockMode( dockMode );
|
|
}
|
|
|
|
void QgsLabelingRulePropsWidget::testFilter()
|
|
{
|
|
QgsExpression filter( editFilter->text() );
|
|
if ( filter.hasParserError() )
|
|
{
|
|
QMessageBox::critical( this, tr( "Error" ), tr( "Filter expression parsing error:\n" ) + filter.parserErrorString() );
|
|
return;
|
|
}
|
|
|
|
QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
|
|
|
|
if ( !filter.prepare( &context ) )
|
|
{
|
|
QMessageBox::critical( this, tr( "Evaluation error" ), filter.evalErrorString() );
|
|
return;
|
|
}
|
|
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
QgsFeatureIterator fit = mLayer->getFeatures();
|
|
|
|
int count = 0;
|
|
QgsFeature f;
|
|
while ( fit.nextFeature( f ) )
|
|
{
|
|
context.setFeature( f );
|
|
|
|
QVariant value = filter.evaluate( &context );
|
|
if ( value.toInt() != 0 )
|
|
count++;
|
|
if ( filter.hasEvalError() )
|
|
break;
|
|
}
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
QMessageBox::information( this, tr( "Filter" ), tr( "Filter returned %n feature(s)", "number of filtered features", count ) );
|
|
}
|
|
|
|
|
|
void QgsLabelingRulePropsWidget::buildExpression()
|
|
{
|
|
QgsExpressionContext context( _globalProjectAtlasMapLayerScopes( mMapCanvas, mLayer ) );
|
|
|
|
QgsExpressionBuilderDialog dlg( mLayer, editFilter->text(), this, QStringLiteral( "generic" ), context );
|
|
|
|
if ( dlg.exec() )
|
|
editFilter->setText( dlg.expressionText() );
|
|
}
|
|
|
|
void QgsLabelingRulePropsWidget::apply()
|
|
{
|
|
mRule->setFilterExpression( editFilter->text() );
|
|
mRule->setDescription( editDescription->text() );
|
|
mRule->setMinimumScale( groupScale->isChecked() ? mScaleRangeWidget->minimumScale() : 0 );
|
|
mRule->setMaximumScale( groupScale->isChecked() ? mScaleRangeWidget->maximumScale() : 0 );
|
|
mRule->setSettings( groupSettings->isChecked() ? new QgsPalLayerSettings( mLabelingGui->layerSettings() ) : nullptr );
|
|
}
|