QGIS API Documentation 3.41.0-Master (3440c17df1d)
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}
38
39Qt::ItemFlags QgsLabelingEngineRulesModel::flags( const QModelIndex &index ) const
40{
42 if ( !rule )
43 return Qt::ItemFlags();
44
45 Qt::ItemFlags res = Qt::ItemIsSelectable;
46 if ( rule->isAvailable() )
47 {
48 res |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
49 }
50
51 if ( index.column() == 0 )
52 {
53 res |= Qt::ItemIsUserCheckable;
54 }
55 return res;
56}
57
59
60QModelIndex QgsLabelingEngineRulesModel::parent( const QModelIndex &child ) const
61{
62 Q_UNUSED( child )
63 return QModelIndex();
64}
65
66int QgsLabelingEngineRulesModel::rowCount( const QModelIndex &parent ) const
67{
68 if ( parent.isValid() )
69 return 0;
70
71 return static_cast< int >( mRules.size() );
72}
73
74int QgsLabelingEngineRulesModel::columnCount( const QModelIndex &parent ) const
75{
76 Q_UNUSED( parent )
77 return 1;
78}
79
80QVariant QgsLabelingEngineRulesModel::data( const QModelIndex &index, int role ) const
81{
83 if ( !rule )
84 return QVariant();
85
86 switch ( role )
87 {
88 case Qt::DisplayRole:
89 case Qt::EditRole:
90 {
91 return rule->name().isEmpty() ? rule->displayType() : rule->name();
92 }
93
94 case Qt::ToolTipRole:
95 {
96 if ( !rule->isAvailable() )
97 return tr( "This rule is not available for use on this system." );
98
99 return rule->description();
100 }
101
102 case Qt::CheckStateRole:
103 {
104 if ( index.column() != 0 )
105 return QVariant();
106 return rule->active() ? Qt::Checked : Qt::Unchecked;
107 }
108
109 default:
110 break;
111 }
112
113 return QVariant();
114}
115
116QModelIndex QgsLabelingEngineRulesModel::index( int row, int column, const QModelIndex &parent ) const
117{
118 if ( hasIndex( row, column, parent ) )
119 {
120 return createIndex( row, column, row );
121 }
122
123 return QModelIndex();
124}
125
126bool QgsLabelingEngineRulesModel::removeRows( int row, int count, const QModelIndex &parent )
127{
128 if ( row < 0 || row >= static_cast< int >( mRules.size() ) )
129 return false;
130
131 beginRemoveRows( parent, row, row + count - 1 );
132 for ( int i = 0; i < count; i++ )
133 {
134 if ( row < static_cast< int >( mRules.size() ) )
135 {
136 mRules.erase( mRules.begin() + row );
137 }
138 }
139 endRemoveRows();
140 return true;
141}
142
143bool QgsLabelingEngineRulesModel::setData( const QModelIndex &index, const QVariant &value, int role )
144{
145 if ( !index.isValid() )
146 return false;
147
149 if ( !rule )
150 return false;
151
152 switch ( role )
153 {
154 case Qt::CheckStateRole:
155 {
156 rule->setActive( value.toInt() == Qt::Checked );
157 emit dataChanged( index, index, { role } );
158 return true;
159 }
160
161 case Qt::EditRole:
162 {
163 if ( index.column() == 0 )
164 {
165 rule->setName( value.toString() );
166 emit dataChanged( index, index );
167 return true;
168 }
169 break;
170 }
171
172 default:
173 break;
174 }
175
176 return false;
177}
178
179void QgsLabelingEngineRulesModel::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
180{
181 beginResetModel();
182 mRules.clear();
183 for ( const QgsAbstractLabelingEngineRule *rule : rules )
184 {
185 mRules.emplace_back( rule->clone() );
186 }
187 endResetModel();
188}
189
190void QgsLabelingEngineRulesModel::addRule( std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
191{
192 beginInsertRows( QModelIndex(), static_cast< int >( mRules.size() ), static_cast< int >( mRules.size() ) );
193 mRules.emplace_back( std::move( rule ) );
194 endInsertRows();
195}
196
198{
199 if ( !index.isValid() )
200 return nullptr;
201
202 if ( index.row() < 0 || index.row() >= static_cast< int >( mRules.size() ) )
203 return nullptr;
204
205 return mRules[ index.row() ].get();
206}
207
208void QgsLabelingEngineRulesModel::changeRule( const QModelIndex &index, std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
209{
210 if ( !index.isValid() )
211 return;
212
213 if ( index.row() < 0 || index.row() >= static_cast< int >( mRules.size() ) )
214 return;
215
216 mRules[index.row() ] = std::move( rule );
217 emit dataChanged( index, index );
218}
219
220QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesModel::rules() const
221{
222 QList<QgsAbstractLabelingEngineRule *> res;
223 res.reserve( static_cast< int >( mRules.size() ) );
224 for ( auto &it : mRules )
225 {
226 res.append( it->clone() );
227 }
228 return res;
229}
230
231
232//
233// QgsLabelingEngineRulesWidget
234//
235
237 : QgsPanelWidget( parent )
238{
239 setupUi( this );
240 setPanelTitle( tr( "Labeling Rules" ) );
241
242 mModel = new QgsLabelingEngineRulesModel( this );
243 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsLabelingEngineRulesWidget::changed );
244 viewRules->setModel( mModel );
245 viewRules->setHeaderHidden( true );
246
247 mAddRuleMenu = new QMenu( this );
248 connect( mAddRuleMenu, &QMenu::aboutToShow, this, &QgsLabelingEngineRulesWidget::createTypesMenu );
249
250 btnAddRule->setMenu( mAddRuleMenu );
251 btnAddRule->setPopupMode( QToolButton::InstantPopup );
252
253 connect( btnEditRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::editSelectedRule );
254 connect( btnRemoveRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::removeRules );
255
256 connect( viewRules, &QAbstractItemView::doubleClicked, this, &QgsLabelingEngineRulesWidget::editRule );
257}
258
259void QgsLabelingEngineRulesWidget::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
260{
261 mModel->setRules( rules );
262}
263
264QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesWidget::rules() const
265{
266 return mModel->rules();
267}
268
269void QgsLabelingEngineRulesWidget::createTypesMenu()
270{
271 mAddRuleMenu->clear();
272
273 const QStringList ruleIds = QgsApplication::labelingEngineRuleRegistry()->ruleIds();
274 QList< QAction * > actions;
275 for ( const QString &id : ruleIds )
276 {
277 if ( ! QgsApplication::labelingEngineRuleRegistry()->isAvailable( id ) )
278 continue;
279
280 QAction *action = new QAction( QgsApplication::labelingEngineRuleRegistry()->create( id )->displayType() );
281 connect( action, &QAction::triggered, this, [this, id ]
282 {
283 createRule( id );
284 } );
285 actions << action;
286 }
287 std::sort( actions.begin(), actions.end(), []( const QAction * a, const QAction * b ) -> bool
288 {
289 return QString::localeAwareCompare( a->text(), b->text() ) < 0;
290 } );
291 mAddRuleMenu->addActions( actions );
292}
293
294void QgsLabelingEngineRulesWidget::createRule( const QString &id )
295{
296 std::unique_ptr< QgsAbstractLabelingEngineRule > rule( QgsApplication::labelingEngineRuleRegistry()->create( id ) );
297 if ( rule )
298 {
299 rule->setName( rule->displayType() );
300 mModel->addRule( rule );
301 const QModelIndex newRuleIndex = mModel->index( mModel->rowCount() - 1, 0, QModelIndex() );
302 viewRules->selectionModel()->setCurrentIndex( newRuleIndex, QItemSelectionModel::SelectionFlag::ClearAndSelect );
303 editRule( newRuleIndex );
304 }
305}
306
307void QgsLabelingEngineRulesWidget::editSelectedRule()
308{
309 const QItemSelection selection = viewRules->selectionModel()->selection();
310 for ( const QItemSelectionRange &range : selection )
311 {
312 if ( range.isValid() )
313 {
314 const QModelIndex index = range.indexes().value( 0 );
315 editRule( index );
316 return;
317 }
318 }
319}
320
321void QgsLabelingEngineRulesWidget::editRule( const QModelIndex &index )
322{
323 const QgsAbstractLabelingEngineRule *rule = mModel->ruleAtIndex( index );
324 if ( !rule || !rule->isAvailable() )
325 return;
326
327 // TODO -- move to a registry when there's a need
328 QgsLabelingEngineRuleWidget *widget = nullptr;
329 if ( rule->id() == "minimumDistanceLabelToFeature" )
330 {
331 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToFeatureWidget();
332 }
333 else if ( rule->id() == "minimumDistanceLabelToLabel" )
334 {
335 widget = new QgsLabelingEngineRuleMinimumDistanceLabelToLabelWidget();
336 }
337 else if ( rule->id() == "maximumDistanceLabelToFeature" )
338 {
339 widget = new QgsLabelingEngineRuleMaximumDistanceLabelToFeatureWidget();
340 }
341 else if ( rule->id() == "avoidLabelOverlapWithFeature" )
342 {
343 widget = new QgsLabelingEngineRuleAvoidLabelOverlapWithFeatureWidget();
344 }
345
346 if ( !widget )
347 return;
348
350 if ( panel && panel->dockMode() )
351 {
352 widget->setPanelTitle( rule->name().isEmpty() ? tr( "Configure Rule" ) : rule->name() );
353 widget->setRule( rule );
354 connect( widget, &QgsLabelingEngineRuleWidget::changed, this, [ = ]
355 {
356 std::unique_ptr< QgsAbstractLabelingEngineRule > updatedRule( widget->rule() );
357 mModel->changeRule( index, updatedRule );
358 emit changed();
359 } );
360 panel->openPanel( widget );
361 }
362 else
363 {
364 QgsLabelingEngineRuleDialog dialog( widget, this );
365 dialog.setRule( rule );
366 if ( dialog.exec() )
367 {
368 std::unique_ptr< QgsAbstractLabelingEngineRule > updatedRule( dialog.rule() );
369 mModel->changeRule( index, updatedRule );
370 emit changed();
371 }
372 }
373}
374
375void QgsLabelingEngineRulesWidget::removeRules()
376{
377 const QItemSelection selection = viewRules->selectionModel()->selection();
378 QList< int > rows;
379 for ( const QItemSelectionRange &range : selection )
380 {
381 if ( range.isValid() )
382 {
383 for ( int row = range.top(); row <= range.bottom(); ++row )
384 {
385 if ( !rows.contains( row ) )
386 rows << row;
387 }
388 }
389 }
390
391 std::sort( rows.begin(), rows.end() );
392 std::reverse( rows.begin(), rows.end() );
393 for ( int row : std::as_const( rows ) )
394 {
395 mModel->removeRow( row );
396 }
397 emit changed();
398}
399
400//
401// QgsLabelingEngineRulesDialog
402//
403
404QgsLabelingEngineRulesDialog::QgsLabelingEngineRulesDialog( QWidget *parent, Qt::WindowFlags flags )
405 : QDialog( parent, flags )
406{
407 setWindowTitle( tr( "Configure Rules" ) );
408 setObjectName( QStringLiteral( "QgsLabelingEngineRulesDialog" ) );
409
410 mWidget = new QgsLabelingEngineRulesWidget();
411
412 QVBoxLayout *layout = new QVBoxLayout( this );
413 layout->addWidget( mWidget );
414
415 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this );
416 layout->addWidget( mButtonBox );
417
418 setLayout( layout );
420
421 connect( mButtonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked, this, &QDialog::accept );
422 connect( mButtonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked, this, &QDialog::reject );
423}
424
425void QgsLabelingEngineRulesDialog::setRules( const QList< QgsAbstractLabelingEngineRule *> &rules )
426{
427 mWidget->setRules( rules );
428}
429
430QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesDialog::rules() const
431{
432 return mWidget->rules();
433}
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:209
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.