QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
qgslabelingengineruleswidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslabelingengineruleswidget.cpp
3 ------------------------
4 begin : September 2024
5 copyright : (C) 2024 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17#include "qgsapplication.h"
21#include "qgsgui.h"
22
23#include <QMenu>
24#include <QAction>
25#include <QDialogButtonBox>
26#include <QPushButton>
27
28//
29// QgsLabelingEngineRulesModel
30//
31
33 : QAbstractItemModel( parent )
34{
35
36}
37
38Qt::ItemFlags QgsLabelingEngineRulesModel::flags( const QModelIndex &index ) const
39{
41 if ( !rule )
42 return Qt::ItemFlags();
43
44 Qt::ItemFlags res = Qt::ItemIsSelectable;
45 if ( rule->isAvailable() )
46 {
47 res |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
48 }
49
50 if ( index.column() == 0 )
51 {
52 res |= Qt::ItemIsUserCheckable;
53 }
54 return res;
55}
56
58
59QModelIndex QgsLabelingEngineRulesModel::parent( const QModelIndex &child ) const
60{
61 Q_UNUSED( child )
62 return QModelIndex();
63}
64
65int QgsLabelingEngineRulesModel::rowCount( const QModelIndex &parent ) const
66{
67 if ( parent.isValid() )
68 return 0;
69
70 return static_cast< int >( mRules.size() );
71}
72
73int QgsLabelingEngineRulesModel::columnCount( const QModelIndex &parent ) const
74{
75 Q_UNUSED( parent )
76 return 1;
77}
78
79QVariant QgsLabelingEngineRulesModel::data( const QModelIndex &index, int role ) const
80{
82 if ( !rule )
83 return QVariant();
84
85 switch ( role )
86 {
87 case Qt::DisplayRole:
88 case Qt::EditRole:
89 {
90 return rule->name().isEmpty() ? rule->displayType() : rule->name();
91 }
92
93 case Qt::ToolTipRole:
94 {
95 if ( !rule->isAvailable() )
96 return tr( "This rule is not available for use on this system." );
97
98 return rule->description();
99 }
100
101 case Qt::CheckStateRole:
102 {
103 if ( index.column() != 0 )
104 return QVariant();
105 return rule->active() ? Qt::Checked : Qt::Unchecked;
106 }
107
108 default:
109 break;
110 }
111
112 return QVariant();
113}
114
115QModelIndex QgsLabelingEngineRulesModel::index( int row, int column, const QModelIndex &parent ) const
116{
117 if ( hasIndex( row, column, parent ) )
118 {
119 return createIndex( row, column, row );
120 }
121
122 return QModelIndex();
123}
124
125bool QgsLabelingEngineRulesModel::removeRows( int row, int count, const QModelIndex &parent )
126{
127 if ( row < 0 || row >= static_cast< int >( mRules.size() ) )
128 return false;
129
130 beginRemoveRows( parent, row, row + count - 1 );
131 for ( int i = 0; i < count; i++ )
132 {
133 if ( row < static_cast< int >( mRules.size() ) )
134 {
135 mRules.erase( mRules.begin() + row );
136 }
137 }
138 endRemoveRows();
139 return true;
140}
141
142bool QgsLabelingEngineRulesModel::setData( const QModelIndex &index, const QVariant &value, int role )
143{
144 if ( !index.isValid() )
145 return false;
146
148 if ( !rule )
149 return false;
150
151 switch ( role )
152 {
153 case Qt::CheckStateRole:
154 {
155 rule->setActive( value.toInt() == Qt::Checked );
156 emit dataChanged( index, index, { role } );
157 return true;
158 }
159
160 case Qt::EditRole:
161 {
162 if ( index.column() == 0 )
163 {
164 rule->setName( value.toString() );
165 emit dataChanged( index, index );
166 return true;
167 }
168 break;
169 }
170
171 default:
172 break;
173 }
174
175 return false;
176}
177
178void QgsLabelingEngineRulesModel::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
179{
180 beginResetModel();
181 mRules.clear();
182 for ( const QgsAbstractLabelingEngineRule *rule : rules )
183 {
184 mRules.emplace_back( rule->clone() );
185 }
186 endResetModel();
187}
188
189void QgsLabelingEngineRulesModel::addRule( std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
190{
191 beginInsertRows( QModelIndex(), static_cast< int >( mRules.size() ), static_cast< int >( mRules.size() ) );
192 mRules.emplace_back( std::move( rule ) );
193 endInsertRows();
194}
195
197{
198 if ( !index.isValid() )
199 return nullptr;
200
201 if ( index.row() < 0 || index.row() >= static_cast< int >( mRules.size() ) )
202 return nullptr;
203
204 return mRules[ index.row() ].get();
205}
206
207void QgsLabelingEngineRulesModel::changeRule( const QModelIndex &index, std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
208{
209 if ( !index.isValid() )
210 return;
211
212 if ( index.row() < 0 || index.row() >= static_cast< int >( mRules.size() ) )
213 return;
214
215 mRules[index.row() ] = std::move( rule );
216 emit dataChanged( index, index );
217}
218
219QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesModel::rules() const
220{
221 QList<QgsAbstractLabelingEngineRule *> res;
222 res.reserve( static_cast< int >( mRules.size() ) );
223 for ( auto &it : mRules )
224 {
225 res.append( it->clone() );
226 }
227 return res;
228}
229
230
231//
232// QgsLabelingEngineRulesWidget
233//
234
236 : QgsPanelWidget( parent )
237{
238 setupUi( this );
239 setPanelTitle( tr( "Labeling Rules" ) );
240
241 mModel = new QgsLabelingEngineRulesModel( this );
242 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsLabelingEngineRulesWidget::changed );
243 viewRules->setModel( mModel );
244 viewRules->setHeaderHidden( true );
245
246 mAddRuleMenu = new QMenu( this );
247 connect( mAddRuleMenu, &QMenu::aboutToShow, this, &QgsLabelingEngineRulesWidget::createTypesMenu );
248
249 btnAddRule->setMenu( mAddRuleMenu );
250 btnAddRule->setPopupMode( QToolButton::InstantPopup );
251
252 connect( btnEditRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::editSelectedRule );
253 connect( btnRemoveRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::removeRules );
254
255 connect( viewRules, &QAbstractItemView::doubleClicked, this, &QgsLabelingEngineRulesWidget::editRule );
256}
257
258void QgsLabelingEngineRulesWidget::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
259{
260 mModel->setRules( rules );
261}
262
263QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesWidget::rules() const
264{
265 return mModel->rules();
266}
267
268void QgsLabelingEngineRulesWidget::createTypesMenu()
269{
270 mAddRuleMenu->clear();
271
272 const QStringList ruleIds = QgsApplication::labelingEngineRuleRegistry()->ruleIds();
273 QList< QAction * > actions;
274 for ( const QString &id : ruleIds )
275 {
276 if ( ! QgsApplication::labelingEngineRuleRegistry()->isAvailable( id ) )
277 continue;
278
279 QAction *action = new QAction( QgsApplication::labelingEngineRuleRegistry()->create( id )->displayType() );
280 connect( action, &QAction::triggered, this, [this, id ]
281 {
282 createRule( id );
283 } );
284 actions << action;
285 }
286 std::sort( actions.begin(), actions.end(), []( const QAction * a, const QAction * b ) -> bool
287 {
288 return QString::localeAwareCompare( a->text(), b->text() ) < 0;
289 } );
290 mAddRuleMenu->addActions( actions );
291}
292
293void QgsLabelingEngineRulesWidget::createRule( const QString &id )
294{
295 std::unique_ptr< QgsAbstractLabelingEngineRule > rule( QgsApplication::labelingEngineRuleRegistry()->create( id ) );
296 if ( rule )
297 {
298 rule->setName( rule->displayType() );
299 mModel->addRule( rule );
300 const QModelIndex newRuleIndex = mModel->index( mModel->rowCount() - 1, 0, QModelIndex() );
301 viewRules->selectionModel()->setCurrentIndex( newRuleIndex, QItemSelectionModel::SelectionFlag::ClearAndSelect );
302 editRule( newRuleIndex );
303 }
304}
305
306void QgsLabelingEngineRulesWidget::editSelectedRule()
307{
308 const QItemSelection selection = viewRules->selectionModel()->selection();
309 for ( const QItemSelectionRange &range : selection )
310 {
311 if ( range.isValid() )
312 {
313 const QModelIndex index = range.indexes().value( 0 );
314 editRule( index );
315 return;
316 }
317 }
318}
319
320void QgsLabelingEngineRulesWidget::editRule( const QModelIndex &index )
321{
322 const QgsAbstractLabelingEngineRule *rule = mModel->ruleAtIndex( index );
323 if ( !rule || !rule->isAvailable() )
324 return;
325
326 // TODO -- move to a registry when there's a need
327 QgsLabelingEngineRuleWidget *widget = nullptr;
328 if ( rule->id() == "minimumDistanceLabelToFeature" )
329 {
330 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToFeatureWidget();
331 }
332 else if ( rule->id() == "minimumDistanceLabelToLabel" )
333 {
334 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToLabelWidget();
335 }
336 else if ( rule->id() == "maximumDistanceLabelToFeature" )
337 {
338 widget = new QgsLabelingEngineRuleMaximumDistanceLabelToFeatureWidget();
339 }
340 else if ( rule->id() == "avoidLabelOverlapWithFeature" )
341 {
342 widget = new QgsLabelingEngineRuleAvoidLabelOverlapWithFeatureWidget();
343 }
344
345 if ( !widget )
346 return;
347
349 if ( panel && panel->dockMode() )
350 {
351 widget->setPanelTitle( rule->name().isEmpty() ? tr( "Configure Rule" ) : rule->name() );
352 widget->setRule( rule );
353 connect( widget, &QgsLabelingEngineRuleWidget::changed, this, [ = ]
354 {
355 std::unique_ptr< QgsAbstractLabelingEngineRule > updatedRule( widget->rule() );
356 mModel->changeRule( index, updatedRule );
357 emit changed();
358 } );
359 panel->openPanel( widget );
360 }
361 else
362 {
363 QgsLabelingEngineRuleDialog dialog( widget, this );
364 dialog.setRule( rule );
365 if ( dialog.exec() )
366 {
367 std::unique_ptr< QgsAbstractLabelingEngineRule > updatedRule( dialog.rule() );
368 mModel->changeRule( index, updatedRule );
369 emit changed();
370 }
371 }
372}
373
374void QgsLabelingEngineRulesWidget::removeRules()
375{
376 const QItemSelection selection = viewRules->selectionModel()->selection();
377 QList< int > rows;
378 for ( const QItemSelectionRange &range : selection )
379 {
380 if ( range.isValid() )
381 {
382 for ( int row = range.top(); row <= range.bottom(); ++row )
383 {
384 if ( !rows.contains( row ) )
385 rows << row;
386 }
387 }
388 }
389
390 std::sort( rows.begin(), rows.end() );
391 std::reverse( rows.begin(), rows.end() );
392 for ( int row : std::as_const( rows ) )
393 {
394 mModel->removeRow( row );
395 }
396 emit changed();
397}
398
399//
400// QgsLabelingEngineRulesDialog
401//
402
403QgsLabelingEngineRulesDialog::QgsLabelingEngineRulesDialog( QWidget *parent, Qt::WindowFlags flags )
404 : QDialog( parent, flags )
405{
406 setWindowTitle( tr( "Configure Rules" ) );
407 setObjectName( QStringLiteral( "QgsLabelingEngineRulesDialog" ) );
408
409 mWidget = new QgsLabelingEngineRulesWidget();
410
411 QVBoxLayout *layout = new QVBoxLayout( this );
412 layout->addWidget( mWidget );
413
414 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this );
415 layout->addWidget( mButtonBox );
416
417 setLayout( layout );
419
420 connect( mButtonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked, this, &QDialog::accept );
421 connect( mButtonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked, this, &QDialog::reject );
422}
423
424void QgsLabelingEngineRulesDialog::setRules( const QList< QgsAbstractLabelingEngineRule *> &rules )
425{
426 mWidget->setRules( rules );
427}
428
429QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesDialog::rules() const
430{
431 return mWidget->rules();
432}
Abstract base class for labeling engine rules.
void setActive(bool active)
Sets whether the rule is active.
virtual bool isAvailable() const
Returns true if the rule is available for use within the current QGIS environment.
bool active() const
Returns true if the rule is active.
QString name() const
Returns the name for this instance of the rule.
virtual QString displayType() const =0
Returns a user-friendly, translated string representing the rule type.
virtual QString description() const
Returns a user-friendly description of the rule.
void setName(const QString &name)
Sets the name for this instance of the rule.
virtual QString id() const =0
Returns a string uniquely identifying the rule subclass.
static QgsLabelingEngineRuleRegistry * labelingEngineRuleRegistry()
Gets the registry of available labeling engine rules.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:208
A simple dialog for customizing a labeling engine rule.
QStringList ruleIds() const
Returns a list of the rule IDs for rules present in the registry.
Base class for widgets which allow control over the properties of QgsAbstractLabelingEngineRule subcl...
virtual void setRule(const QgsAbstractLabelingEngineRule *rule)=0
Sets the rule to show in the widget.
virtual QgsAbstractLabelingEngineRule * rule()=0
Returns the rule defined by the current settings in the widget.
void changed()
Emitted whenever the configuration of the rule is changed.
QList< QgsAbstractLabelingEngineRule * > rules() const
Returns the rules shown in the dialog.
void setRules(const QList< QgsAbstractLabelingEngineRule * > &rules)
Sets the rules to show in the dialog.
QgsLabelingEngineRulesDialog(QWidget *parent=nullptr, Qt::WindowFlags flags=QgsGuiUtils::ModalDialogFlags)
Constructor for QgsLabelingEngineRulesDialog.
A model for configuration of a list of labeling engine rules.
QModelIndex index(int row, int column, const QModelIndex &parent) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void setRules(const QList< QgsAbstractLabelingEngineRule * > &rules)
Sets the rules to include in the model.
QModelIndex parent(const QModelIndex &child) const override
QgsLabelingEngineRulesModel(QObject *parent=nullptr)
Constructor for QgsLabelingEngineRulesModel.
QList< QgsAbstractLabelingEngineRule * > rules() const
Returns the rules shown in the widget.
QgsAbstractLabelingEngineRule * ruleAtIndex(const QModelIndex &index) const
Returns the rule at the specified model index.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
void addRule(std::unique_ptr< QgsAbstractLabelingEngineRule > &rule)
Adds a rule to the model.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
~QgsLabelingEngineRulesModel() override
void changeRule(const QModelIndex &index, std::unique_ptr< QgsAbstractLabelingEngineRule > &rule)
Swaps the rule at the specified index for a new rule.
A widget which allows configuration of a list of labeling engine rules.
void setRules(const QList< QgsAbstractLabelingEngineRule * > &rules)
Sets the rules to show in the widget.
QgsLabelingEngineRulesWidget(QWidget *parent=nullptr)
Constructor for QgsLabelingEngineRulesWidget.
QList< QgsAbstractLabelingEngineRule * > rules() const
Returns the rules shown in the widget.
void changed()
Emitted when the rules configured in the widget are changed.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.