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