QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsattributesformview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsattributesformview.cpp
3 ---------------------
4 begin : June 2025
5 copyright : (C) 2025 by Germán Carrillo
6 email : german at opengis dot ch
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"
20#include "qgsattributetypedialog.h"
22#include "qgscodeeditorhtml.h"
25#include "qgsexpressionfinder.h"
26#include "qgsgui.h"
28#include "qgsqmlwidgetwrapper.h"
29#include "qgsscrollarea.h"
31
32#include <QAction>
33#include <QClipboard>
34#include <QDropEvent>
35#include <QFileDialog>
36#include <QFormLayout>
37#include <QHBoxLayout>
38#include <QMenu>
39#include <QMessageBox>
40#include <QMimeData>
41#include <QPlainTextEdit>
42#include <QPushButton>
43#include <QSpinBox>
44#include <QTreeView>
45#include <QWidget>
46
47#include "moc_qgsattributesformview.cpp"
48
50 : QTreeView( parent )
51 , mLayer( layer )
52{
53}
54
56{
57 if ( selectionModel()->selectedRows( 0 ).count() == 0 )
58 return QModelIndex();
59
60 return mModel->mapToSource( selectionModel()->selectedRows( 0 ).at( 0 ) );
61}
62
75
77{
78 // To be used with Relations, fields and actions
79 const auto *model = static_cast< QgsAttributesFormModel * >( mModel->sourceModel() );
80 QModelIndex index = mModel->mapFromSource( model->firstRecursiveMatchingModelIndex( itemType, itemId ) );
81
82 if ( index.isValid() )
83 {
84 // Check if selection is index is already selected (e.g., duplicate fields in
85 // form layout, and selecting one after the other), otherwise set new selection
86 const QModelIndexList selectedIndexes = selectionModel()->selectedRows( 0 );
87 if ( !( selectedIndexes.count() == 1 && selectedIndexes.at( 0 ) == index ) )
88 {
89 selectionModel()->setCurrentIndex( index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
90 }
91 }
92 else
93 {
94 selectionModel()->clearSelection();
95 }
96}
97
99{
100 mModel->setFilterText( text );
101}
102
103const QList<QgsAttributesFormTreeViewIndicator *> QgsAttributesFormBaseView::indicators( const QModelIndex &index ) const
104{
105 QgsAttributesFormItem *item = static_cast< QgsAttributesFormModel *>( mModel->sourceModel() )->itemForIndex( mModel->mapToSource( index ) );
106 return mIndicators.value( item );
107}
108
109const QList<QgsAttributesFormTreeViewIndicator *> QgsAttributesFormBaseView::indicators( QgsAttributesFormItem *item ) const
110{
111 return mIndicators.value( item );
112}
113
115{
116 if ( !mIndicators[item].contains( indicator ) )
117 {
118 mIndicators[item].append( indicator );
119 connect( indicator, &QgsAttributesFormTreeViewIndicator::changed, this, [this] {
120 update();
121 viewport()->repaint();
122 } );
123 update();
124 viewport()->repaint(); //update() does not automatically trigger a repaint()
125 }
126}
127
129{
130 mIndicators[item].removeOne( indicator );
131 update();
132}
133
135{
136 const QList<QgsAttributesFormItem *> keys = mIndicators.keys();
137 for ( QgsAttributesFormItem *key : keys )
138 {
139 qDeleteAll( mIndicators[key] );
140 mIndicators[key].clear();
141 }
142 mIndicators.clear();
143 update();
144}
145
147{
148 return mModel->sourceAttributesFormModel();
149}
150
151
156
157void QgsAttributesAvailableWidgetsView::setModel( QAbstractItemModel *model )
158{
159 mModel = qobject_cast<QgsAttributesFormProxyModel *>( model );
160 if ( !mModel )
161 return;
162
163 QTreeView::setModel( mModel );
164}
165
170
171
173 : QgsAttributesFormBaseView( layer, parent )
174{
175 connect( this, &QTreeView::doubleClicked, this, &QgsAttributesFormLayoutView::onItemDoubleClicked );
176}
177
178void QgsAttributesFormLayoutView::setModel( QAbstractItemModel *model )
179{
180 mModel = qobject_cast<QgsAttributesFormProxyModel *>( model );
181 if ( !mModel )
182 return;
183
184 QTreeView::setModel( mModel );
185
186 const auto *formLayoutModel = static_cast< QgsAttributesFormLayoutModel * >( mModel->sourceModel() );
187 connect( formLayoutModel, &QgsAttributesFormLayoutModel::externalItemDropped, this, &QgsAttributesFormLayoutView::handleExternalDroppedItem );
188 connect( formLayoutModel, &QgsAttributesFormLayoutModel::internalItemDropped, this, &QgsAttributesFormLayoutView::handleInternalDroppedItem );
189}
190
191
192void QgsAttributesFormLayoutView::handleExternalDroppedItem( QModelIndex &index )
193{
194 selectionModel()->setCurrentIndex( mModel->mapFromSource( index ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
195
196 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
197
198 if ( itemType == QgsAttributesFormData::QmlWidget
202 {
203 onItemDoubleClicked( mModel->mapFromSource( index ) );
204 }
205}
206
207void QgsAttributesFormLayoutView::handleInternalDroppedItem( QModelIndex &index )
208{
209 selectionModel()->clearCurrentIndex();
210 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
211 if ( itemType == QgsAttributesFormData::Container )
212 {
213 expandRecursively( mModel->mapFromSource( index ) );
214 }
215}
216
218{
219 const QMimeData *data = event->mimeData();
220
221 if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
222 || data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
223 {
224 // Inner drag and drop actions are always MoveAction
225 if ( event->source() == this )
226 {
227 event->setDropAction( Qt::MoveAction );
228 }
229 }
230 else
231 {
232 event->ignore();
233 }
234
235 QTreeView::dragEnterEvent( event );
236}
237
243{
244 const QMimeData *data = event->mimeData();
245
246 if ( data->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
247 || data->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) )
248 {
249 // Inner drag and drop actions are always MoveAction
250 if ( event->source() == this )
251 {
252 event->setDropAction( Qt::MoveAction );
253 }
254 }
255 else
256 {
257 event->ignore();
258 }
259
260 QTreeView::dragMoveEvent( event );
261}
262
264{
265 if ( !( event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributesformavailablewidgetsrelement" ) )
266 || event->mimeData()->hasFormat( QStringLiteral( "application/x-qgsattributesformlayoutelement" ) ) ) )
267 return;
268
269 if ( event->source() == this )
270 {
271 event->setDropAction( Qt::MoveAction );
272 }
273
274 QTreeView::dropEvent( event );
275}
276
277void QgsAttributesFormLayoutView::onItemDoubleClicked( const QModelIndex &index )
278{
279 QModelIndex sourceIndex = mModel->mapToSource( index );
281 const auto itemType = static_cast<QgsAttributesFormData::AttributesFormItemType>( sourceIndex.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
282 const QString itemName = sourceIndex.data( QgsAttributesFormModel::ItemNameRole ).toString();
283
284 QGroupBox *baseData = new QGroupBox( tr( "Base configuration" ) );
285
286 QFormLayout *baseLayout = new QFormLayout();
287 baseData->setLayout( baseLayout );
288 QCheckBox *showLabelCheckbox = new QCheckBox( QStringLiteral( "Show label" ) );
289 showLabelCheckbox->setChecked( itemData.showLabel() );
290 baseLayout->addRow( showLabelCheckbox );
291 QWidget *baseWidget = new QWidget();
292 baseWidget->setLayout( baseLayout );
293
294 switch ( itemType )
295 {
301 break;
302
304 {
305 QDialog dlg;
306 dlg.setObjectName( "QML Form Configuration Widget" );
308 dlg.setWindowTitle( tr( "Configure QML Widget" ) );
309
310 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
311 QSplitter *qmlSplitter = new QSplitter();
312 QWidget *qmlConfigWiget = new QWidget();
313 QVBoxLayout *layout = new QVBoxLayout( qmlConfigWiget );
314 layout->setContentsMargins( 0, 0, 0, 0 );
315 mainLayout->addWidget( qmlSplitter );
316 qmlSplitter->addWidget( qmlConfigWiget );
317 layout->addWidget( baseWidget );
318
319 QLineEdit *title = new QLineEdit( itemName );
320
321 //qmlCode
322 QgsCodeEditor *qmlCode = new QgsCodeEditor( this );
323 qmlCode->setEditingTimeoutInterval( 250 );
324 qmlCode->setText( itemData.qmlElementEditorConfiguration().qmlCode );
325
326 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
327 QgsFeature previewFeature;
328 mLayer->getFeatures().nextFeature( previewFeature );
329
330 //update preview on text change
331 connect( qmlCode, &QgsCodeEditor::editingTimeout, this, [qmlWrapper, qmlCode, previewFeature] {
332 qmlWrapper->setQmlCode( qmlCode->text() );
333 qmlWrapper->reinitWidget();
334 qmlWrapper->setFeature( previewFeature );
335 } );
336
337 //templates
338 QComboBox *qmlObjectTemplate = new QComboBox();
339 qmlObjectTemplate->addItem( tr( "Free Text…" ) );
340 qmlObjectTemplate->addItem( tr( "Rectangle" ) );
341 qmlObjectTemplate->addItem( tr( "Pie Chart" ) );
342 qmlObjectTemplate->addItem( tr( "Bar Chart" ) );
343 connect( qmlObjectTemplate, qOverload<int>( &QComboBox::activated ), qmlCode, [qmlCode]( int index ) {
344 qmlCode->clear();
345 switch ( index )
346 {
347 case 0:
348 {
349 qmlCode->setText( QString() );
350 break;
351 }
352 case 1:
353 {
354 qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
355 "\n"
356 "Rectangle {\n"
357 " width: 100\n"
358 " height: 100\n"
359 " color: \"steelblue\"\n"
360 " Text{ text: \"A rectangle\" }\n"
361 "}\n" ) );
362 break;
363 }
364 case 2:
365 {
366 qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
367 "import QtCharts 2.0\n"
368 "\n"
369 "ChartView {\n"
370 " width: 400\n"
371 " height: 400\n"
372 "\n"
373 " PieSeries {\n"
374 " id: pieSeries\n"
375 " PieSlice { label: \"First slice\"; value: 25 }\n"
376 " PieSlice { label: \"Second slice\"; value: 45 }\n"
377 " PieSlice { label: \"Third slice\"; value: 30 }\n"
378 " }\n"
379 "}\n" ) );
380 break;
381 }
382 case 3:
383 {
384 qmlCode->setText( QStringLiteral( "import QtQuick 2.0\n"
385 "import QtCharts 2.0\n"
386 "\n"
387 "ChartView {\n"
388 " title: \"Bar series\"\n"
389 " width: 600\n"
390 " height:400\n"
391 " legend.alignment: Qt.AlignBottom\n"
392 " antialiasing: true\n"
393 " ValueAxis{\n"
394 " id: valueAxisY\n"
395 " min: 0\n"
396 " max: 15\n"
397 " }\n"
398 "\n"
399 " BarSeries {\n"
400 " id: mySeries\n"
401 " axisY: valueAxisY\n"
402 " axisX: BarCategoryAxis { categories: [\"2007\", \"2008\", \"2009\", \"2010\", \"2011\", \"2012\" ] }\n"
403 " BarSet { label: \"Bob\"; values: [2, 2, 3, 4, 5, 6] }\n"
404 " BarSet { label: \"Susan\"; values: [5, 1, 2, 4, 1, 7] }\n"
405 " BarSet { label: \"James\"; values: [3, 5, 8, 13, 5, 8] }\n"
406 " }\n"
407 "}\n" ) );
408 break;
409 }
410 default:
411 break;
412 }
413 } );
414
415 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
416 expressionWidget->setButtonVisible( false );
417 expressionWidget->registerExpressionContextGenerator( this );
418 expressionWidget->setLayer( mLayer );
419 QToolButton *addFieldButton = new QToolButton();
420 addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
421
422 QToolButton *editExpressionButton = new QToolButton();
423 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
424 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
425
426 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, qmlCode] {
427 QString expression = expressionWidget->expression().trimmed().replace( '"', QLatin1String( "\\\"" ) );
428 if ( !expression.isEmpty() )
429 qmlCode->insertText( QStringLiteral( "expression.evaluate(\"%1\")" ).arg( expression ) );
430 } );
431
432 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, qmlCode] {
433 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( qmlCode, QStringLiteral( "expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)" ) );
434 expression.replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) );
435 QgsExpressionContext context = createExpressionContext();
436 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
437
438 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
439 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
440 {
441 QString expression = exprDlg.expressionText().trimmed().replace( '"', QLatin1String( "\\\"" ) );
442 if ( !expression.isEmpty() )
443 qmlCode->insertText( QStringLiteral( "expression.evaluate(\"%1\")" ).arg( expression ) );
444 }
445 } );
446
447 layout->addWidget( new QLabel( tr( "Title" ) ) );
448 layout->addWidget( title );
449 QGroupBox *qmlCodeBox = new QGroupBox( tr( "QML Code" ) );
450 qmlCodeBox->setLayout( new QVBoxLayout );
451 qmlCodeBox->layout()->addWidget( qmlObjectTemplate );
452 QWidget *expressionWidgetBox = new QWidget();
453 qmlCodeBox->layout()->addWidget( expressionWidgetBox );
454 expressionWidgetBox->setLayout( new QHBoxLayout );
455 expressionWidgetBox->layout()->setContentsMargins( 0, 0, 0, 0 );
456 expressionWidgetBox->layout()->addWidget( expressionWidget );
457 expressionWidgetBox->layout()->addWidget( addFieldButton );
458 expressionWidgetBox->layout()->addWidget( editExpressionButton );
459 expressionWidgetBox->layout()->addWidget( editExpressionButton );
460 layout->addWidget( qmlCodeBox );
461 layout->addWidget( qmlCode );
462 QScrollArea *qmlPreviewBox = new QgsScrollArea();
463 qmlPreviewBox->setMinimumWidth( 200 );
464 qmlPreviewBox->setWidget( qmlWrapper->widget() );
465 //emit to load preview for the first time
466 emit qmlCode->editingTimeout();
467 qmlSplitter->addWidget( qmlPreviewBox );
468 qmlSplitter->setChildrenCollapsible( false );
469 qmlSplitter->setHandleWidth( 6 );
470 qmlSplitter->setSizes( QList<int>() << 1 << 1 );
471
472 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
473
474 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
475 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
476 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] {
477 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
478 } );
479
480 mainLayout->addWidget( buttonBox );
481
482 if ( dlg.exec() )
483 {
484 QgsAttributesFormData::QmlElementEditorConfiguration qmlEdCfg;
485 qmlEdCfg.qmlCode = qmlCode->text();
486 itemData.setQmlElementEditorConfiguration( qmlEdCfg );
487 itemData.setShowLabel( showLabelCheckbox->isChecked() );
488
489 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
490 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
491 }
492 }
493 break;
494
496 {
497 QDialog dlg;
498 dlg.setObjectName( "HTML Form Configuration Widget" );
500 dlg.setWindowTitle( tr( "Configure HTML Widget" ) );
501
502 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
503 QSplitter *htmlSplitter = new QSplitter();
504 QWidget *htmlConfigWiget = new QWidget();
505 QVBoxLayout *layout = new QVBoxLayout( htmlConfigWiget );
506 layout->setContentsMargins( 0, 0, 0, 0 );
507 mainLayout->addWidget( htmlSplitter );
508 htmlSplitter->addWidget( htmlConfigWiget );
509 htmlSplitter->setChildrenCollapsible( false );
510 htmlSplitter->setHandleWidth( 6 );
511 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
512 layout->addWidget( baseWidget );
513
514 QLineEdit *title = new QLineEdit( itemName );
515
516 //htmlCode
517 QgsCodeEditorHTML *htmlCode = new QgsCodeEditorHTML();
518 htmlCode->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
519 htmlCode->setText( itemData.htmlElementEditorConfiguration().htmlCode );
520
521 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
522 QgsFeature previewFeature;
523 mLayer->getFeatures().nextFeature( previewFeature );
524
525 //update preview on text change
526 connect( htmlCode, &QgsCodeEditorHTML::textChanged, this, [htmlWrapper, htmlCode, previewFeature] {
527 htmlWrapper->setHtmlCode( htmlCode->text() );
528 htmlWrapper->reinitWidget();
529 htmlWrapper->setFeature( previewFeature );
530 } );
531
532 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
533 expressionWidget->setButtonVisible( false );
534 expressionWidget->registerExpressionContextGenerator( this );
535 expressionWidget->setLayer( mLayer );
536 QToolButton *addFieldButton = new QToolButton();
537 addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
538
539 QToolButton *editExpressionButton = new QToolButton();
540 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
541 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
542
543 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, htmlCode] {
544 QString expression = expressionWidget->expression().trimmed().replace( '"', QLatin1String( "\\\"" ) );
545 if ( !expression.isEmpty() )
546 htmlCode->insertText( QStringLiteral( "<script>document.write(expression.evaluate(\"%1\"));</script>" ).arg( expression ) );
547 } );
548
549 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, htmlCode] {
550 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( htmlCode, QStringLiteral( "<script>\\s*document\\.write\\(\\s*expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)\\s*\\)\\s*;?\\s*</script>" ) );
551 expression.replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) );
552 QgsExpressionContext context = createExpressionContext();
553 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
554
555 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
556 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
557 {
558 QString expression = exprDlg.expressionText().trimmed().replace( '"', QLatin1String( "\\\"" ) );
559 if ( !expression.isEmpty() )
560 htmlCode->insertText( QStringLiteral( "<script>document.write(expression.evaluate(\"%1\"));</script>" ).arg( expression ) );
561 }
562 } );
563
564 layout->addWidget( new QLabel( tr( "Title" ) ) );
565 layout->addWidget( title );
566 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "HTML Code" ) );
567 layout->addWidget( expressionWidgetBox );
568 expressionWidgetBox->setLayout( new QHBoxLayout );
569 expressionWidgetBox->layout()->addWidget( expressionWidget );
570 expressionWidgetBox->layout()->addWidget( addFieldButton );
571 expressionWidgetBox->layout()->addWidget( editExpressionButton );
572 layout->addWidget( htmlCode );
573 QScrollArea *htmlPreviewBox = new QgsScrollArea();
574 htmlPreviewBox->setLayout( new QGridLayout );
575 htmlPreviewBox->setMinimumWidth( 200 );
576 htmlPreviewBox->layout()->addWidget( htmlWrapper->widget() );
577 //emit to load preview for the first time
578 emit htmlCode->textChanged();
579 htmlSplitter->addWidget( htmlPreviewBox );
580 htmlSplitter->setChildrenCollapsible( false );
581 htmlSplitter->setHandleWidth( 6 );
582 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
583
584 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
585
586 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
587 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
588 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] {
589 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
590 } );
591
592 mainLayout->addWidget( buttonBox );
593
594 if ( dlg.exec() )
595 {
596 QgsAttributesFormData::HtmlElementEditorConfiguration htmlEdCfg;
597 htmlEdCfg.htmlCode = htmlCode->text();
598 itemData.setHtmlElementEditorConfiguration( htmlEdCfg );
599 itemData.setShowLabel( showLabelCheckbox->isChecked() );
600
601 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
602 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
603 }
604 break;
605 }
606
608 {
609 QDialog dlg;
610 dlg.setObjectName( "Text Form Configuration Widget" );
612 dlg.setWindowTitle( tr( "Configure Text Widget" ) );
613
614 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
615 QSplitter *textSplitter = new QSplitter();
616 QWidget *textConfigWiget = new QWidget();
617 QVBoxLayout *layout = new QVBoxLayout( textConfigWiget );
618 layout->setContentsMargins( 0, 0, 0, 0 );
619 mainLayout->addWidget( textSplitter );
620 textSplitter->addWidget( textConfigWiget );
621 layout->addWidget( baseWidget );
622
623 QLineEdit *title = new QLineEdit( itemName );
624
625 QgsCodeEditorHTML *text = new QgsCodeEditorHTML();
626 text->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
627 text->setText( itemData.textElementEditorConfiguration().text );
628
629 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
630 QgsFeature previewFeature;
631 ( void ) mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( previewFeature );
632
633 //update preview on text change
634 connect( text, &QgsCodeEditorExpression::textChanged, this, [textWrapper, previewFeature, text] {
635 textWrapper->setText( text->text() );
636 textWrapper->reinitWidget();
637 textWrapper->setFeature( previewFeature );
638 } );
639
640 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
641 expressionWidget->setButtonVisible( false );
642 expressionWidget->registerExpressionContextGenerator( this );
643 expressionWidget->setLayer( mLayer );
644 QToolButton *addFieldButton = new QToolButton();
645 addFieldButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
646
647 QToolButton *editExpressionButton = new QToolButton();
648 editExpressionButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) );
649 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
650
651 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, text] {
652 QString expression = expressionWidget->expression().trimmed();
653 if ( !expression.isEmpty() )
654 text->insertText( QStringLiteral( "[%%1%]" ).arg( expression ) );
655 } );
656 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, text] {
657 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( text );
658
659 QgsExpressionContext context = createExpressionContext();
660 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, QStringLiteral( "generic" ), context );
661
662 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
663 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
664 {
665 QString expression = exprDlg.expressionText().trimmed();
666 if ( !expression.isEmpty() )
667 text->insertText( QStringLiteral( "[%%1%]" ).arg( expression ) );
668 }
669 } );
670
671 layout->addWidget( new QLabel( tr( "Title" ) ) );
672 layout->addWidget( title );
673 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "Text" ) );
674 layout->addWidget( expressionWidgetBox );
675 expressionWidgetBox->setLayout( new QHBoxLayout );
676 expressionWidgetBox->layout()->addWidget( expressionWidget );
677 expressionWidgetBox->layout()->addWidget( addFieldButton );
678 expressionWidgetBox->layout()->addWidget( editExpressionButton );
679 layout->addWidget( text );
680 QScrollArea *textPreviewBox = new QgsScrollArea();
681 textPreviewBox->setLayout( new QGridLayout );
682 textPreviewBox->setMinimumWidth( 200 );
683 textPreviewBox->layout()->addWidget( textWrapper->widget() );
684 //emit to load preview for the first time
685 emit text->textChanged();
686 textSplitter->addWidget( textPreviewBox );
687 textSplitter->setChildrenCollapsible( false );
688 textSplitter->setHandleWidth( 6 );
689 textSplitter->setSizes( QList<int>() << 1 << 1 );
690
691 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
692
693 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
694 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
695 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] {
696 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
697 } );
698
699 mainLayout->addWidget( buttonBox );
700
701 if ( dlg.exec() )
702 {
703 QgsAttributesFormData::TextElementEditorConfiguration textEdCfg;
704 textEdCfg.text = text->text();
705 itemData.setTextElementEditorConfiguration( textEdCfg );
706 itemData.setShowLabel( showLabelCheckbox->isChecked() );
707
708 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
709 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
710 }
711 break;
712 }
713
715 {
716 QDialog dlg;
717 dlg.setObjectName( "Spacer Form Configuration Widget" );
719 dlg.setWindowTitle( tr( "Configure Spacer Widget" ) );
720
721 QVBoxLayout *mainLayout = new QVBoxLayout();
722 mainLayout->addWidget( new QLabel( tr( "Title" ) ) );
723 QLineEdit *title = new QLineEdit( itemName );
724 mainLayout->addWidget( title );
725
726 QHBoxLayout *cbLayout = new QHBoxLayout();
727 mainLayout->addLayout( cbLayout );
728 dlg.setLayout( mainLayout );
729 QCheckBox *cb = new QCheckBox { &dlg };
730 cb->setChecked( itemData.spacerElementEditorConfiguration().drawLine );
731 cbLayout->addWidget( new QLabel( tr( "Draw horizontal line" ), &dlg ) );
732 cbLayout->addWidget( cb );
733
734 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
735
736 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
737 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
738 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] {
739 QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#other-widgets" ) );
740 } );
741
742 mainLayout->addWidget( buttonBox );
743
744 if ( dlg.exec() )
745 {
746 QgsAttributesFormData::SpacerElementEditorConfiguration spacerEdCfg;
747 spacerEdCfg.drawLine = cb->isChecked();
748 itemData.setSpacerElementEditorConfiguration( spacerEdCfg );
749 itemData.setShowLabel( false );
750
751 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
752 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
753 }
754
755 break;
756 }
757 }
758}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Manages available widgets when configuring attributes forms.
void setModel(QAbstractItemModel *model) override
Overridden setModel() from base class. Only QgsAttributesFormProxyModel is an acceptable model.
QgsAttributesAvailableWidgetsView(QgsVectorLayer *layer, QWidget *parent=nullptr)
Constructor for QgsAttributesAvailableWidgetsView, with the given parent.
QgsAttributesAvailableWidgetsModel * availableWidgetsModel() const
Access the underlying QgsAttributesAvailableWidgetsModel source model.
void setFilterText(const QString &text)
Sets the filter text to the underlying proxy model.
QgsAttributesFormProxyModel * mModel
QgsAttributesFormModel * sourceModel() const
Returns the underlying QgsAttributesFormModel model where the view gets data from.
void addIndicator(QgsAttributesFormItem *item, QgsAttributesFormTreeViewIndicator *indicator)
Adds the indicator to the given item.
QHash< QgsAttributesFormItem *, QList< QgsAttributesFormTreeViewIndicator * > > mIndicators
Storage of indicators used with the tree view.
void removeIndicator(QgsAttributesFormItem *item, QgsAttributesFormTreeViewIndicator *indicator)
Removes the indicator from the given item.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void removeAllIndicators()
Removes all indicators in the current view.
QgsAttributesFormBaseView(QgsVectorLayer *layer, QWidget *parent=nullptr)
Constructor for QgsAttributesFormBaseView, with the given parent.
const QList< QgsAttributesFormTreeViewIndicator * > indicators(const QModelIndex &index) const
Returns the list of indicators associated with a given index.
void selectFirstMatchingItem(const QgsAttributesFormData::AttributesFormItemType &itemType, const QString &itemId)
Selects the first item that matches a itemType and a itemId.
QModelIndex firstSelectedIndex() const
Returns the source model index corresponding to the first selected row.
Main class to store and transfer editor data contained in a QgsAttributesFormModel.
HtmlElementEditorConfiguration htmlElementEditorConfiguration() const
Returns the HTML editor configuration.
TextElementEditorConfiguration textElementEditorConfiguration() const
Returns the editor configuration for text element.
SpacerElementEditorConfiguration spacerElementEditorConfiguration() const
Returns the spacer element configuration.
void setHtmlElementEditorConfiguration(const HtmlElementEditorConfiguration &htmlElementEditorConfiguration)
Sets the HTML editor configuration.
bool showLabel() const
Returns whether the widget's label is to be shown.
void setShowLabel(bool showLabel)
Sets whether the label for the widget should be shown.
void setQmlElementEditorConfiguration(const QmlElementEditorConfiguration &qmlElementEditorConfiguration)
Sets the QML editor configuration.
void setSpacerElementEditorConfiguration(SpacerElementEditorConfiguration spacerElementEditorConfiguration)
Sets the the spacer element configuration to spacerElementEditorConfiguration.
QmlElementEditorConfiguration qmlElementEditorConfiguration() const
Returns the QML editor configuration.
void setTextElementEditorConfiguration(const TextElementEditorConfiguration &textElementEditorConfiguration)
Sets the editor configuration for text element to textElementEditorConfiguration.
AttributesFormItemType
Custom item types.
@ Container
Container for the form, which may be tab, group or row.
@ Relation
Relation between two vector layers.
@ Field
Vector layer field.
@ SpacerWidget
Spacer widget type,.
@ WidgetType
In the available widgets tree, the type of widget.
@ TextWidget
Text widget type,.
Holds parent-child relations as well as item data contained in a QgsAttributesFormModel.
Manages form layouts when configuring attributes forms via drag and drop designer.
void externalItemDropped(QModelIndex &index)
Informs that items were inserted (via drop) in the model from another model.
void internalItemDropped(QModelIndex &index)
Informs that items were moved (via drop) in the model from the same model.
void dropEvent(QDropEvent *event) override
void dragEnterEvent(QDragEnterEvent *event) override
QgsAttributesFormLayoutView(QgsVectorLayer *layer, QWidget *parent=nullptr)
Constructor for QgsAttributesFormLayoutView, with the given parent.
void setModel(QAbstractItemModel *model) override
Overridden setModel() from base class. Only QgsAttributesFormProxyModel is an acceptable model.
void dragMoveEvent(QDragMoveEvent *event) override
Is called when mouse is moved over attributes tree before a drop event.
Abstract class for tree models allowing for configuration of attributes forms.
@ ItemNameRole
Prior to QGIS 3.44, this was available as FieldNameRole.
@ ItemDataRole
Prior to QGIS 3.44, this was available as DnDTreeRole.
QgsAttributesFormItem * itemForIndex(const QModelIndex &index) const
Returns the underlying item that corresponds to the given index.
Indicator that can be used in an Attributes Form tree view to display icons next to field items.
void changed()
Emitted when the indicator changes state (e.g.
A text editor based on QScintilla2.
void setText(const QString &text) override
void setEditingTimeoutInterval(int timeout)
Sets the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after an e...
void insertText(const QString &text)
Insert text at cursor position, or replace any selected text if user has made a selection.
void editingTimeout()
Emitted when either:
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QString findAndSelectActiveExpression(QgsCodeEditor *editor, const QString &pattern=QString())
Find the expression under the cursor in the given editor and select it.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
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
void reinitWidget()
Clears the content and makes new initialization.
void setHtmlCode(const QString &htmlCode)
Sets the HTML code to htmlCode.
void setFeature(const QgsFeature &feature) override
static QgsProject * instance()
Returns the QgsProject singleton instance.
Wraps a QQuickWidget to display QML code.
void setFeature(const QgsFeature &feature) override
void reinitWidget()
Clears the content and makes new initialization.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
void setText(const QString &text)
Sets the text code to htmlCode.
void reinitWidget()
Clears the content and makes new initialization.
void setFeature(const QgsFeature &feature) override
Represents a vector layer which manages a vector based dataset.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
QWidget * widget()
Access the widget managed by this wrapper.