QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
18#include "qgsapplication.h"
19#include "qgsgui.h"
20#include "qgshelp.h"
24
25#include <QAction>
26#include <QDialogButtonBox>
27#include <QMenu>
28#include <QPushButton>
29
30#include "moc_qgslabelingengineruleswidget.cpp"
31
32//
33// QgsLabelingEngineRulesModel
34//
35
37 : QAbstractItemModel( parent )
38{
39}
40
41Qt::ItemFlags QgsLabelingEngineRulesModel::flags( const QModelIndex &index ) const
42{
44 if ( !rule )
45 return Qt::ItemFlags();
46
47 Qt::ItemFlags res = Qt::ItemIsSelectable;
48 if ( rule->isAvailable() )
49 {
50 res |= Qt::ItemIsEnabled | Qt::ItemIsEditable;
51 }
52
53 if ( index.column() == 0 )
54 {
55 res |= Qt::ItemIsUserCheckable;
56 }
57 return res;
58}
59
61
62QModelIndex QgsLabelingEngineRulesModel::parent( const QModelIndex &child ) const
63{
64 Q_UNUSED( child )
65 return QModelIndex();
66}
67
68int QgsLabelingEngineRulesModel::rowCount( const QModelIndex &parent ) const
69{
70 if ( parent.isValid() )
71 return 0;
72
73 return static_cast<int>( mRules.size() );
74}
75
76int QgsLabelingEngineRulesModel::columnCount( const QModelIndex &parent ) const
77{
78 Q_UNUSED( parent )
79 return 1;
80}
81
82QVariant QgsLabelingEngineRulesModel::data( const QModelIndex &index, int role ) const
83{
85 if ( !rule )
86 return QVariant();
87
88 switch ( role )
89 {
90 case Qt::DisplayRole:
91 case Qt::EditRole:
92 {
93 return rule->name().isEmpty() ? rule->displayType() : rule->name();
94 }
95
96 case Qt::ToolTipRole:
97 {
98 if ( !rule->isAvailable() )
99 return tr( "This rule is not available for use on this system." );
100
101 return rule->description();
102 }
103
104 case Qt::CheckStateRole:
105 {
106 if ( index.column() != 0 )
107 return QVariant();
108 return rule->active() ? Qt::Checked : Qt::Unchecked;
109 }
110
111 default:
112 break;
113 }
114
115 return QVariant();
116}
117
118QModelIndex QgsLabelingEngineRulesModel::index( int row, int column, const QModelIndex &parent ) const
119{
120 if ( hasIndex( row, column, parent ) )
121 {
122 return createIndex( row, column, row );
123 }
124
125 return QModelIndex();
126}
127
128bool QgsLabelingEngineRulesModel::removeRows( int row, int count, const QModelIndex &parent )
129{
130 if ( row < 0 || row >= static_cast<int>( mRules.size() ) )
131 return false;
132
133 beginRemoveRows( parent, row, row + count - 1 );
134 for ( int i = 0; i < count; i++ )
135 {
136 if ( row < static_cast<int>( mRules.size() ) )
137 {
138 mRules.erase( mRules.begin() + row );
139 }
140 }
141 endRemoveRows();
142 return true;
143}
144
145bool QgsLabelingEngineRulesModel::setData( const QModelIndex &index, const QVariant &value, int role )
146{
147 if ( !index.isValid() )
148 return false;
149
151 if ( !rule )
152 return false;
153
154 switch ( role )
155 {
156 case Qt::CheckStateRole:
157 {
158 rule->setActive( value.toInt() == Qt::Checked );
159 emit dataChanged( index, index, { role } );
160 return true;
161 }
162
163 case Qt::EditRole:
164 {
165 if ( index.column() == 0 )
166 {
167 rule->setName( value.toString() );
168 emit dataChanged( index, index );
169 return true;
170 }
171 break;
172 }
173
174 default:
175 break;
176 }
177
178 return false;
179}
180
181void QgsLabelingEngineRulesModel::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
182{
183 beginResetModel();
184 mRules.clear();
185 for ( const QgsAbstractLabelingEngineRule *rule : rules )
186 {
187 mRules.emplace_back( rule->clone() );
188 }
189 endResetModel();
190}
191
192void QgsLabelingEngineRulesModel::addRule( std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
193{
194 beginInsertRows( QModelIndex(), static_cast<int>( mRules.size() ), static_cast<int>( mRules.size() ) );
195 mRules.emplace_back( std::move( rule ) );
196 endInsertRows();
197}
198
200{
201 if ( !index.isValid() )
202 return nullptr;
203
204 if ( index.row() < 0 || index.row() >= static_cast<int>( mRules.size() ) )
205 return nullptr;
206
207 return mRules[index.row()].get();
208}
209
210void QgsLabelingEngineRulesModel::changeRule( const QModelIndex &index, std::unique_ptr<QgsAbstractLabelingEngineRule> &rule )
211{
212 if ( !index.isValid() )
213 return;
214
215 if ( index.row() < 0 || index.row() >= static_cast<int>( mRules.size() ) )
216 return;
217
218 mRules[index.row()] = std::move( rule );
219 emit dataChanged( index, index );
220}
221
222QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesModel::rules() const
223{
224 QList<QgsAbstractLabelingEngineRule *> res;
225 res.reserve( static_cast<int>( mRules.size() ) );
226 for ( auto &it : mRules )
227 {
228 res.append( it->clone() );
229 }
230 return res;
231}
232
233
234//
235// QgsLabelingEngineRulesWidget
236//
237
239 : QgsPanelWidget( parent )
240{
241 setupUi( this );
242 setPanelTitle( tr( "Labeling Rules" ) );
243
244 mModel = new QgsLabelingEngineRulesModel( this );
245 connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsLabelingEngineRulesWidget::changed );
246 viewRules->setModel( mModel );
247 viewRules->setHeaderHidden( true );
248
249 mAddRuleMenu = new QMenu( this );
250 connect( mAddRuleMenu, &QMenu::aboutToShow, this, &QgsLabelingEngineRulesWidget::createTypesMenu );
251
252 btnAddRule->setMenu( mAddRuleMenu );
253 btnAddRule->setPopupMode( QToolButton::InstantPopup );
254
255 connect( btnEditRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::editSelectedRule );
256 connect( btnRemoveRule, &QToolButton::clicked, this, &QgsLabelingEngineRulesWidget::removeRules );
257
258 connect( viewRules, &QAbstractItemView::doubleClicked, this, &QgsLabelingEngineRulesWidget::editRule );
259}
260
261void QgsLabelingEngineRulesWidget::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
262{
263 mModel->setRules( rules );
264}
265
266QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesWidget::rules() const
267{
268 return mModel->rules();
269}
270
271void QgsLabelingEngineRulesWidget::createTypesMenu()
272{
273 mAddRuleMenu->clear();
274
275 const QStringList ruleIds = QgsApplication::labelingEngineRuleRegistry()->ruleIds();
276 QList<QAction *> actions;
277 for ( const QString &id : ruleIds )
278 {
279 if ( !QgsApplication::labelingEngineRuleRegistry()->isAvailable( id ) )
280 continue;
281
282 QAction *action = new QAction( QgsApplication::labelingEngineRuleRegistry()->create( id )->displayType() );
283 connect( action, &QAction::triggered, this, [this, id] {
284 createRule( id );
285 } );
286 actions << action;
287 }
288 std::sort( actions.begin(), actions.end(), []( const QAction *a, const QAction *b ) -> bool {
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, [this, widget, index] {
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 | QDialogButtonBox::Help, 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 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, [] {
423 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#labeling-rules" ) );
424 } );
425}
426
427void QgsLabelingEngineRulesDialog::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
428{
429 mWidget->setRules( rules );
430}
431
432QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineRulesDialog::rules() const
433{
434 return mWidget->rules();
435}
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:221
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:38
QStringList ruleIds() const
Returns a list of the rule IDs for rules present in the registry.
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.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
bool dockMode() const
Returns the dock mode state.
QgsPanelWidget(QWidget *parent=nullptr)
Base class for any widget that can be shown as an inline panel.
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.