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