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