QGIS API Documentation 4.1.0-Master (376402f9aeb)
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 <QString>
45#include <QTreeView>
46#include <QWidget>
47
48#include "moc_qgsattributesformview.cpp"
49
50using namespace Qt::StringLiterals;
51
53 : QTreeView( parent )
54 , mLayer( layer )
55{}
56
58{
59 if ( selectionModel()->selectedRows( 0 ).count() == 0 )
60 return QModelIndex();
61
62 return mModel->mapToSource( selectionModel()->selectedRows( 0 ).at( 0 ) );
63}
64
76
78{
79 // To be used with Relations, fields and actions
80 const auto *model = static_cast< QgsAttributesFormModel * >( mModel->sourceModel() );
81 QModelIndex index = mModel->mapFromSource( model->firstRecursiveMatchingModelIndex( itemType, itemId ) );
82
83 if ( index.isValid() )
84 {
85 // Check if selection is index is already selected (e.g., duplicate fields in
86 // form layout, and selecting one after the other), otherwise set new selection
87 const QModelIndexList selectedIndexes = selectionModel()->selectedRows( 0 );
88 if ( !( selectedIndexes.count() == 1 && selectedIndexes.at( 0 ) == index ) )
89 {
90 selectionModel()->setCurrentIndex( index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
91 }
92 }
93 else
94 {
95 selectionModel()->clearSelection();
96 }
97}
98
100{
101 mModel->setFilterText( text );
102}
103
104const QList<QgsAttributesFormTreeViewIndicator *> QgsAttributesFormBaseView::indicators( const QModelIndex &index ) const
105{
106 QgsAttributesFormItem *item = static_cast< QgsAttributesFormModel *>( mModel->sourceModel() )->itemForIndex( mModel->mapToSource( index ) );
107 return mIndicators.value( item );
108}
109
110const QList<QgsAttributesFormTreeViewIndicator *> QgsAttributesFormBaseView::indicators( QgsAttributesFormItem *item ) const
111{
112 return mIndicators.value( item );
113}
114
116{
117 if ( !mIndicators[item].contains( indicator ) )
118 {
119 mIndicators[item].append( indicator );
120 connect( indicator, &QgsAttributesFormTreeViewIndicator::changed, this, [this] {
121 update();
122 viewport()->repaint();
123 } );
124 update();
125 viewport()->repaint(); //update() does not automatically trigger a repaint()
126 }
127}
128
130{
131 mIndicators[item].removeOne( indicator );
132 update();
133}
134
136{
137 const QList<QgsAttributesFormItem *> keys = mIndicators.keys();
138 for ( QgsAttributesFormItem *key : keys )
139 {
140 qDeleteAll( mIndicators[key] );
141 mIndicators[key].clear();
142 }
143 mIndicators.clear();
144 update();
145}
146
148{
149 return mModel->sourceAttributesFormModel();
150}
151
152
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
199 {
200 onItemDoubleClicked( mModel->mapFromSource( index ) );
201 }
202}
203
204void QgsAttributesFormLayoutView::handleInternalDroppedItem( QModelIndex &index )
205{
206 selectionModel()->clearCurrentIndex();
207 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
208 if ( itemType == QgsAttributesFormData::Container )
209 {
210 expandRecursively( mModel->mapFromSource( index ) );
211 }
212}
213
215{
216 const QMimeData *data = event->mimeData();
217
218 if ( data->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || data->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) )
219 {
220 // Inner drag and drop actions are always MoveAction
221 if ( event->source() == this )
222 {
223 event->setDropAction( Qt::MoveAction );
224 }
225 }
226 else
227 {
228 event->ignore();
229 }
230
231 QTreeView::dragEnterEvent( event );
232}
233
239{
240 const QMimeData *data = event->mimeData();
241
242 if ( data->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || data->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) )
243 {
244 // Inner drag and drop actions are always MoveAction
245 if ( event->source() == this )
246 {
247 event->setDropAction( Qt::MoveAction );
248 }
249 }
250 else
251 {
252 event->ignore();
253 }
254
255 QTreeView::dragMoveEvent( event );
256}
257
259{
260 if ( !( event->mimeData()->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || event->mimeData()->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) ) )
261 return;
262
263 if ( event->source() == this )
264 {
265 event->setDropAction( Qt::MoveAction );
266 }
267
268 QTreeView::dropEvent( event );
269}
270
271void QgsAttributesFormLayoutView::onItemDoubleClicked( const QModelIndex &index )
272{
273 QModelIndex sourceIndex = mModel->mapToSource( index );
275 const auto itemType = static_cast<QgsAttributesFormData::AttributesFormItemType>( sourceIndex.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
276 const QString itemName = sourceIndex.data( QgsAttributesFormModel::ItemNameRole ).toString();
277
278 QGroupBox *baseData = new QGroupBox( tr( "Base configuration" ) );
279
280 QFormLayout *baseLayout = new QFormLayout();
281 baseData->setLayout( baseLayout );
282 QCheckBox *showLabelCheckbox = new QCheckBox( u"Show label"_s );
283 showLabelCheckbox->setChecked( itemData.showLabel() );
284 baseLayout->addRow( showLabelCheckbox );
285 QWidget *baseWidget = new QWidget();
286 baseWidget->setLayout( baseLayout );
287
288 switch ( itemType )
289 {
295 break;
296
298 {
299 QDialog dlg;
300 dlg.setObjectName( "QML Form Configuration Widget" );
302 dlg.setWindowTitle( tr( "Configure QML Widget" ) );
303
304 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
305 QSplitter *qmlSplitter = new QSplitter();
306 QWidget *qmlConfigWiget = new QWidget();
307 QVBoxLayout *layout = new QVBoxLayout( qmlConfigWiget );
308 layout->setContentsMargins( 0, 0, 0, 0 );
309 mainLayout->addWidget( qmlSplitter );
310 qmlSplitter->addWidget( qmlConfigWiget );
311 layout->addWidget( baseWidget );
312
313 QLineEdit *title = new QLineEdit( itemName );
314
315 //qmlCode
317 qmlCode->setLineNumbersVisible( true );
318 qmlCode->setEditingTimeoutInterval( 250 );
319 qmlCode->setText( itemData.qmlElementEditorConfiguration().qmlCode );
320
321 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
322 QgsFeature previewFeature;
323 mLayer->getFeatures().nextFeature( previewFeature );
324
325 //templates
326 QComboBox *qmlObjectTemplate = new QComboBox();
327 qmlObjectTemplate->addItem( tr( "Free Text…" ) );
328 qmlObjectTemplate->addItem( tr( "Rectangle" ) );
329 qmlObjectTemplate->addItem( tr( "Pie Chart" ) );
330 qmlObjectTemplate->addItem( tr( "Bar Chart" ) );
331 connect( qmlObjectTemplate, qOverload<int>( &QComboBox::currentIndexChanged ), qmlCode, [qmlCode]( int index ) {
332 qmlCode->clear();
333 switch ( index )
334 {
335 case 0:
336 {
337 qmlCode->setText( QString() );
338 break;
339 }
340 case 1:
341 {
342 qmlCode->setText( QStringLiteral(
343 "import QtQuick 2.0\n"
344 "\n"
345 "Rectangle {\n"
346 " width: 100\n"
347 " height: 100\n"
348 " color: \"steelblue\"\n"
349 " Text{ text: \"A rectangle\" }\n"
350 "}\n"
351 ) );
352 break;
353 }
354 case 2:
355 {
356 qmlCode->setText( QStringLiteral(
357 "import QtQuick 2.0\n"
358 "import QtCharts 2.0\n"
359 "\n"
360 "ChartView {\n"
361 " width: 400\n"
362 " height: 400\n"
363 "\n"
364 " PieSeries {\n"
365 " id: pieSeries\n"
366 " PieSlice { label: \"First slice\"; value: 25 }\n"
367 " PieSlice { label: \"Second slice\"; value: 45 }\n"
368 " PieSlice { label: \"Third slice\"; value: 30 }\n"
369 " }\n"
370 "}\n"
371 ) );
372 break;
373 }
374 case 3:
375 {
376 qmlCode->setText( QStringLiteral(
377 "import QtQuick 2.0\n"
378 "import QtCharts 2.0\n"
379 "\n"
380 "ChartView {\n"
381 " title: \"Bar series\"\n"
382 " width: 600\n"
383 " height:400\n"
384 " legend.alignment: Qt.AlignBottom\n"
385 " antialiasing: true\n"
386 " ValueAxis{\n"
387 " id: valueAxisY\n"
388 " min: 0\n"
389 " max: 15\n"
390 " }\n"
391 "\n"
392 " BarSeries {\n"
393 " id: mySeries\n"
394 " axisY: valueAxisY\n"
395 " axisX: BarCategoryAxis { categories: [\"2007\", \"2008\", \"2009\", \"2010\", \"2011\", \"2012\" ] }\n"
396 " BarSet { label: \"Bob\"; values: [2, 2, 3, 4, 5, 6] }\n"
397 " BarSet { label: \"Susan\"; values: [5, 1, 2, 4, 1, 7] }\n"
398 " BarSet { label: \"James\"; values: [3, 5, 8, 13, 5, 8] }\n"
399 " }\n"
400 "}\n"
401 ) );
402 break;
403 }
404 default:
405 break;
406 }
407 } );
408
409 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
410 expressionWidget->setButtonVisible( false );
411 expressionWidget->registerExpressionContextGenerator( this );
412 expressionWidget->setLayer( mLayer );
413 QToolButton *addFieldButton = new QToolButton();
414 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
415
416 QToolButton *editExpressionButton = new QToolButton();
417 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
418 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
419
420 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, qmlCode] {
421 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
422 if ( !expression.isEmpty() )
423 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.arg( expression ) );
424 } );
425
426 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, qmlCode] {
427 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( qmlCode, u"expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)"_s );
428 expression.replace( "\\\""_L1, "\""_L1 );
429 QgsExpressionContext context = createExpressionContext();
430 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
431
432 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
433 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
434 {
435 QString expression = exprDlg.expressionText().trimmed().replace( '"', "\\\""_L1 );
436 if ( !expression.isEmpty() )
437 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.arg( expression ) );
438 }
439 } );
440
441 layout->addWidget( new QLabel( tr( "Title" ) ) );
442 layout->addWidget( title );
443 QGroupBox *qmlCodeBox = new QGroupBox( tr( "QML Code" ) );
444 qmlCodeBox->setLayout( new QVBoxLayout );
445 qmlCodeBox->layout()->addWidget( qmlObjectTemplate );
446 QWidget *expressionWidgetBox = new QWidget();
447 qmlCodeBox->layout()->addWidget( expressionWidgetBox );
448 expressionWidgetBox->setLayout( new QHBoxLayout );
449 expressionWidgetBox->layout()->setContentsMargins( 0, 0, 0, 0 );
450 expressionWidgetBox->layout()->addWidget( expressionWidget );
451 expressionWidgetBox->layout()->addWidget( addFieldButton );
452 expressionWidgetBox->layout()->addWidget( editExpressionButton );
453 layout->addWidget( qmlCodeBox );
454 layout->addWidget( qmlCode );
455
456 QTextEdit *errorFeedbackWidget = new QTextEdit();
457 errorFeedbackWidget->setMaximumHeight( 90 );
458 layout->addWidget( errorFeedbackWidget );
459
460 //update preview on text change
461 connect( qmlCode, &QgsCodeEditor::editingTimeout, this, [qmlWrapper, qmlCode, previewFeature, errorFeedbackWidget] {
462 qmlWrapper->setQmlCode( qmlCode->text() );
463 qmlWrapper->reinitWidget();
464 qmlWrapper->setFeature( previewFeature );
465 if ( QQuickWidget *qmlWidget = dynamic_cast<QQuickWidget *>( qmlWrapper->widget() ) )
466 {
467 const QList<QQmlError> errors = qmlWidget->errors();
468 if ( !errors.isEmpty() )
469 {
470 QStringList errorStrings;
471 for ( const QQmlError &error : errors )
472 {
473 errorStrings << u"%1:%2: %3"_s.arg( QString::number( error.line() ), QString::number( error.column() ), error.description() );
474 }
475 errorFeedbackWidget->setText( errorStrings.join( "\n" ) );
476 }
477 else
478 {
479 errorFeedbackWidget->setText( QObject::tr( "Valid code" ) );
480 }
481 }
482 } );
483
484 QScrollArea *qmlPreviewBox = new QgsScrollArea();
485 qmlPreviewBox->setMinimumWidth( 200 );
486 qmlPreviewBox->setWidget( qmlWrapper->widget() );
487 //emit to load preview for the first time
488 emit qmlCode->editingTimeout();
489 qmlSplitter->addWidget( qmlPreviewBox );
490 qmlSplitter->setChildrenCollapsible( false );
491 qmlSplitter->setHandleWidth( 6 );
492 qmlSplitter->setSizes( QList<int>() << 1 << 1 );
493
494 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
495
496 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
497 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
498 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
499
500 mainLayout->addWidget( buttonBox );
501
502 if ( dlg.exec() )
503 {
504 QgsAttributesFormData::QmlElementEditorConfiguration qmlEdCfg;
505 qmlEdCfg.qmlCode = qmlCode->text();
506 itemData.setQmlElementEditorConfiguration( qmlEdCfg );
507 itemData.setShowLabel( showLabelCheckbox->isChecked() );
508
509 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
510 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
511 }
512 }
513 break;
514
516 {
517 QDialog dlg;
518 dlg.setObjectName( "HTML Form Configuration Widget" );
520 dlg.setWindowTitle( tr( "Configure HTML Widget" ) );
521
522 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
523 QSplitter *htmlSplitter = new QSplitter();
524 QWidget *htmlConfigWiget = new QWidget();
525 QVBoxLayout *layout = new QVBoxLayout( htmlConfigWiget );
526 layout->setContentsMargins( 0, 0, 0, 0 );
527 mainLayout->addWidget( htmlSplitter );
528 htmlSplitter->addWidget( htmlConfigWiget );
529 htmlSplitter->setChildrenCollapsible( false );
530 htmlSplitter->setHandleWidth( 6 );
531 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
532 layout->addWidget( baseWidget );
533
534 QLineEdit *title = new QLineEdit( itemName );
535
536 //htmlCode
537 QgsCodeEditorHTML *htmlCode = new QgsCodeEditorHTML();
538 htmlCode->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
539 htmlCode->setText( itemData.htmlElementEditorConfiguration().htmlCode );
540
541 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
542 QgsFeature previewFeature;
543 mLayer->getFeatures().nextFeature( previewFeature );
544
545 //update preview on text change
546 connect( htmlCode, &QgsCodeEditorHTML::textChanged, this, [htmlWrapper, htmlCode, previewFeature] {
547 htmlWrapper->setHtmlCode( htmlCode->text() );
548 htmlWrapper->reinitWidget();
549 htmlWrapper->setFeature( previewFeature );
550 } );
551
552 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
553 expressionWidget->setButtonVisible( false );
554 expressionWidget->registerExpressionContextGenerator( this );
555 expressionWidget->setLayer( mLayer );
556 QToolButton *addFieldButton = new QToolButton();
557 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
558
559 QToolButton *editExpressionButton = new QToolButton();
560 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
561 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
562
563 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, htmlCode] {
564 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
565 if ( !expression.isEmpty() )
566 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
567 } );
568
569 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, htmlCode] {
570 QString expression
571 = QgsExpressionFinder::findAndSelectActiveExpression( htmlCode, u"<script>\\s*document\\.write\\(\\s*expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)\\s*\\)\\s*;?\\s*</script>"_s );
572 expression.replace( "\\\""_L1, "\""_L1 );
573 QgsExpressionContext context = createExpressionContext();
574 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
575
576 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
577 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
578 {
579 QString expression = exprDlg.expressionText().trimmed().replace( '"', "\\\""_L1 );
580 if ( !expression.isEmpty() )
581 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
582 }
583 } );
584
585 layout->addWidget( new QLabel( tr( "Title" ) ) );
586 layout->addWidget( title );
587 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "HTML Code" ) );
588 layout->addWidget( expressionWidgetBox );
589 expressionWidgetBox->setLayout( new QHBoxLayout );
590 expressionWidgetBox->layout()->addWidget( expressionWidget );
591 expressionWidgetBox->layout()->addWidget( addFieldButton );
592 expressionWidgetBox->layout()->addWidget( editExpressionButton );
593 layout->addWidget( htmlCode );
594 QScrollArea *htmlPreviewBox = new QgsScrollArea();
595 htmlPreviewBox->setLayout( new QGridLayout );
596 htmlPreviewBox->setMinimumWidth( 200 );
597 htmlPreviewBox->layout()->addWidget( htmlWrapper->widget() );
598 //emit to load preview for the first time
599 emit htmlCode->textChanged();
600 htmlSplitter->addWidget( htmlPreviewBox );
601 htmlSplitter->setChildrenCollapsible( false );
602 htmlSplitter->setHandleWidth( 6 );
603 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
604
605 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
606
607 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
608 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
609 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
610
611 mainLayout->addWidget( buttonBox );
612
613 if ( dlg.exec() )
614 {
615 QgsAttributesFormData::HtmlElementEditorConfiguration htmlEdCfg;
616 htmlEdCfg.htmlCode = htmlCode->text();
617 itemData.setHtmlElementEditorConfiguration( htmlEdCfg );
618 itemData.setShowLabel( showLabelCheckbox->isChecked() );
619
620 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
621 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
622 }
623 break;
624 }
625
627 {
628 QDialog dlg;
629 dlg.setObjectName( "Text Form Configuration Widget" );
631 dlg.setWindowTitle( tr( "Configure Text Widget" ) );
632
633 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
634 QSplitter *textSplitter = new QSplitter();
635 QWidget *textConfigWiget = new QWidget();
636 QVBoxLayout *layout = new QVBoxLayout( textConfigWiget );
637 layout->setContentsMargins( 0, 0, 0, 0 );
638 mainLayout->addWidget( textSplitter );
639 textSplitter->addWidget( textConfigWiget );
640 layout->addWidget( baseWidget );
641
642 QLineEdit *title = new QLineEdit( itemName );
643
644 QgsCodeEditorHTML *text = new QgsCodeEditorHTML();
645 text->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
646 text->setText( itemData.textElementEditorConfiguration().text );
647
648 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
649 QgsFeature previewFeature;
650 ( void ) mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( previewFeature );
651
652 //update preview on text change
653 connect( text, &QgsCodeEditorExpression::textChanged, this, [textWrapper, previewFeature, text] {
654 textWrapper->setText( text->text() );
655 textWrapper->reinitWidget();
656 textWrapper->setFeature( previewFeature );
657 } );
658
659 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
660 expressionWidget->setButtonVisible( false );
661 expressionWidget->registerExpressionContextGenerator( this );
662 expressionWidget->setLayer( mLayer );
663 QToolButton *addFieldButton = new QToolButton();
664 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
665
666 QToolButton *editExpressionButton = new QToolButton();
667 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
668 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
669
670 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, text] {
671 QString expression = expressionWidget->expression().trimmed();
672 if ( !expression.isEmpty() )
673 text->insertText( u"[%%1%]"_s.arg( expression ) );
674 } );
675 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, text] {
676 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( text );
677
678 QgsExpressionContext context = createExpressionContext();
679 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
680
681 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
682 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
683 {
684 QString expression = exprDlg.expressionText().trimmed();
685 if ( !expression.isEmpty() )
686 text->insertText( u"[%%1%]"_s.arg( expression ) );
687 }
688 } );
689
690 layout->addWidget( new QLabel( tr( "Title" ) ) );
691 layout->addWidget( title );
692 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "Text" ) );
693 layout->addWidget( expressionWidgetBox );
694 expressionWidgetBox->setLayout( new QHBoxLayout );
695 expressionWidgetBox->layout()->addWidget( expressionWidget );
696 expressionWidgetBox->layout()->addWidget( addFieldButton );
697 expressionWidgetBox->layout()->addWidget( editExpressionButton );
698 layout->addWidget( text );
699 QScrollArea *textPreviewBox = new QgsScrollArea();
700 textPreviewBox->setWidgetResizable( true );
701 textPreviewBox->setMinimumWidth( 200 );
702 textPreviewBox->setWidget( textWrapper->widget() );
703 //emit to load preview for the first time
704 emit text->textChanged();
705 textSplitter->addWidget( textPreviewBox );
706 textSplitter->setChildrenCollapsible( false );
707 textSplitter->setHandleWidth( 6 );
708 textSplitter->setSizes( QList<int>() << 1 << 1 );
709
710 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
711
712 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
713 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
714 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
715
716 mainLayout->addWidget( buttonBox );
717
718 if ( dlg.exec() )
719 {
720 QgsAttributesFormData::TextElementEditorConfiguration textEdCfg;
721 textEdCfg.text = text->text();
722 itemData.setTextElementEditorConfiguration( textEdCfg );
723 itemData.setShowLabel( showLabelCheckbox->isChecked() );
724
725 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
726 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
727 }
728 break;
729 }
730
732 {
733 QDialog dlg;
734 dlg.setObjectName( "Spacer Form Configuration Widget" );
736 dlg.setWindowTitle( tr( "Configure Spacer Widget" ) );
737
738 QVBoxLayout *mainLayout = new QVBoxLayout();
739 mainLayout->addWidget( new QLabel( tr( "Title" ) ) );
740 QLineEdit *title = new QLineEdit( itemName );
741 mainLayout->addWidget( title );
742
743 QHBoxLayout *cbLayout = new QHBoxLayout();
744 mainLayout->addLayout( cbLayout );
745 dlg.setLayout( mainLayout );
746 QCheckBox *cb = new QCheckBox { &dlg };
747 cb->setChecked( itemData.spacerElementEditorConfiguration().drawLine );
748 cbLayout->addWidget( new QLabel( tr( "Draw horizontal line" ), &dlg ) );
749 cbLayout->addWidget( cb );
750
751 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
752
753 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
754 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
755 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
756
757 mainLayout->addWidget( buttonBox );
758
759 if ( dlg.exec() )
760 {
761 QgsAttributesFormData::SpacerElementEditorConfiguration spacerEdCfg;
762 spacerEdCfg.drawLine = cb->isChecked();
763 itemData.setSpacerElementEditorConfiguration( spacerEdCfg );
764 itemData.setShowLabel( false );
765
766 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
767 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
768 }
769
770 break;
771 }
772 }
773}
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.
@ ScriptEditor
Standard mode, allows for display and edit of entire scripts.
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...
@ CodeFolding
Indicates that code folding should be enabled for the editor.
void insertText(const QString &text)
Insert text at cursor position, or replace any selected text if user has made a selection.
void setLineNumbersVisible(bool visible)
Sets whether line numbers should be visible in the editor.
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:60
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
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.