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