QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 "moc_qgslabelingengineruleswidget.cpp"
18#include "qgsapplication.h"
22#include "qgsgui.h"
23
24#include <QMenu>
25#include <QAction>
26#include <QDialogButtonBox>
27#include <QPushButton>
28
29//
30// QgsLabelingEngineRulesModel
31//
32
34 : QAbstractItemModel( parent )
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 createRule( id );
282 } );
283 actions << action;
284 }
285 std::sort( actions.begin(), actions.end(), []( const QAction *a, const QAction *b ) -> bool {
286 return QString::localeAwareCompare( a->text(), b->text() ) < 0;
287 } );
288 mAddRuleMenu->addActions( actions );
289}
290
291void QgsLabelingEngineRulesWidget::createRule( const QString &id )
292{
293 std::unique_ptr<QgsAbstractLabelingEngineRule> rule( QgsApplication::labelingEngineRuleRegistry()->create( id ) );
294 if ( rule )
295 {
296 rule->setName( rule->displayType() );
297 mModel->addRule( rule );
298 const QModelIndex newRuleIndex = mModel->index( mModel->rowCount() - 1, 0, QModelIndex() );
299 viewRules->selectionModel()->setCurrentIndex( newRuleIndex, QItemSelectionModel::SelectionFlag::ClearAndSelect );
300 editRule( newRuleIndex );
301 }
302}
303
304void QgsLabelingEngineRulesWidget::editSelectedRule()
305{
306 const QItemSelection selection = viewRules->selectionModel()->selection();
307 for ( const QItemSelectionRange &range : selection )
308 {
309 if ( range.isValid() )
310 {
311 const QModelIndex index = range.indexes().value( 0 );
312 editRule( index );
313 return;
314 }
315 }
316}
317
318void QgsLabelingEngineRulesWidget::editRule( const QModelIndex &index )
319{
320 const QgsAbstractLabelingEngineRule *rule = mModel->ruleAtIndex( index );
321 if ( !rule || !rule->isAvailable() )
322 return;
323
324 // TODO -- move to a registry when there's a need
325 QgsLabelingEngineRuleWidget *widget = nullptr;
326 if ( rule->id() == "minimumDistanceLabelToFeature" )
327 {
328 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToFeatureWidget();
329 }
330 else if ( rule->id() == "minimumDistanceLabelToLabel" )
331 {
332 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToLabelWidget();
333 }
334 else if ( rule->id() == "maximumDistanceLabelToFeature" )
335 {
336 widget = new QgsLabelingEngineRuleMaximumDistanceLabelToFeatureWidget();
337 }
338 else if ( rule->id() == "avoidLabelOverlapWithFeature" )
339 {
340 widget = new QgsLabelingEngineRuleAvoidLabelOverlapWithFeatureWidget();
341 }
342
343 if ( !widget )
344 return;
345
347 if ( panel && panel->dockMode() )
348 {
349 widget->setPanelTitle( rule->name().isEmpty() ? tr( "Configure Rule" ) : rule->name() );
350 widget->setRule( rule );
351 connect( widget, &QgsLabelingEngineRuleWidget::changed, this, [=] {
352 std::unique_ptr<QgsAbstractLabelingEngineRule> updatedRule( widget->rule() );
353 mModel->changeRule( index, updatedRule );
354 emit changed();
355 } );
356 panel->openPanel( widget );
357 }
358 else
359 {
360 QgsLabelingEngineRuleDialog dialog( widget, this );
361 dialog.setRule( rule );
362 if ( dialog.exec() )
363 {
364 std::unique_ptr<QgsAbstractLabelingEngineRule> updatedRule( dialog.rule() );
365 mModel->changeRule( index, updatedRule );
366 emit changed();
367 }
368 }
369}
370
371void QgsLabelingEngineRulesWidget::removeRules()
372{
373 const QItemSelection selection = viewRules->selectionModel()->selection();
374 QList<int> rows;
375 for ( const QItemSelectionRange &range : selection )
376 {
377 if ( range.isValid() )
378 {
379 for ( int row = range.top(); row <= range.bottom(); ++row )
380 {
381 if ( !rows.contains( row ) )
382 rows << row;
383 }
384 }
385 }
386
387 std::sort( rows.begin(), rows.end() );
388 std::reverse( rows.begin(), rows.end() );
389 for ( int row : std::as_const( rows ) )
390 {
391 mModel->removeRow( row );
392 }
393 emit changed();
394}
395
396//
397// QgsLabelingEngineRulesDialog
398//
399
400QgsLabelingEngineRulesDialog::QgsLabelingEngineRulesDialog( QWidget *parent, Qt::WindowFlags flags )
401 : QDialog( parent, flags )
402{
403 setWindowTitle( tr( "Configure Rules" ) );
404 setObjectName( QStringLiteral( "QgsLabelingEngineRulesDialog" ) );
405
406 mWidget = new QgsLabelingEngineRulesWidget();
407
408 QVBoxLayout *layout = new QVBoxLayout( this );
409 layout->addWidget( mWidget );
410
411 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this );
412 layout->addWidget( mButtonBox );
413
414 setLayout( layout );
416
417 connect( mButtonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked, this, &QDialog::accept );
418 connect( mButtonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked, this, &QDialog::reject );
419}
420
421void QgsLabelingEngineRulesDialog::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
422{
423 mWidget->setRules( rules );
424}
425
426QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesDialog::rules() const
427{
428 return mWidget->rules();
429}
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:210
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.