QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
316 QgsCodeEditor *qmlCode = new QgsCodeEditor( this );
317 qmlCode->setEditingTimeoutInterval( 250 );
318 qmlCode->setText( itemData.qmlElementEditorConfiguration().qmlCode );
319
320 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
321 QgsFeature previewFeature;
322 mLayer->getFeatures().nextFeature( previewFeature );
323
324 //update preview on text change
325 connect( qmlCode, &QgsCodeEditor::editingTimeout, this, [qmlWrapper, qmlCode, previewFeature] {
326 qmlWrapper->setQmlCode( qmlCode->text() );
327 qmlWrapper->reinitWidget();
328 qmlWrapper->setFeature( previewFeature );
329 } );
330
331 //templates
332 QComboBox *qmlObjectTemplate = new QComboBox();
333 qmlObjectTemplate->addItem( tr( "Free Text…" ) );
334 qmlObjectTemplate->addItem( tr( "Rectangle" ) );
335 qmlObjectTemplate->addItem( tr( "Pie Chart" ) );
336 qmlObjectTemplate->addItem( tr( "Bar Chart" ) );
337 connect( qmlObjectTemplate, qOverload<int>( &QComboBox::activated ), qmlCode, [qmlCode]( int index ) {
338 qmlCode->clear();
339 switch ( index )
340 {
341 case 0:
342 {
343 qmlCode->setText( QString() );
344 break;
345 }
346 case 1:
347 {
348 qmlCode->setText( QStringLiteral(
349 "import QtQuick 2.0\n"
350 "\n"
351 "Rectangle {\n"
352 " width: 100\n"
353 " height: 100\n"
354 " color: \"steelblue\"\n"
355 " Text{ text: \"A rectangle\" }\n"
356 "}\n"
357 ) );
358 break;
359 }
360 case 2:
361 {
362 qmlCode->setText( QStringLiteral(
363 "import QtQuick 2.0\n"
364 "import QtCharts 2.0\n"
365 "\n"
366 "ChartView {\n"
367 " width: 400\n"
368 " height: 400\n"
369 "\n"
370 " PieSeries {\n"
371 " id: pieSeries\n"
372 " PieSlice { label: \"First slice\"; value: 25 }\n"
373 " PieSlice { label: \"Second slice\"; value: 45 }\n"
374 " PieSlice { label: \"Third slice\"; value: 30 }\n"
375 " }\n"
376 "}\n"
377 ) );
378 break;
379 }
380 case 3:
381 {
382 qmlCode->setText( QStringLiteral(
383 "import QtQuick 2.0\n"
384 "import QtCharts 2.0\n"
385 "\n"
386 "ChartView {\n"
387 " title: \"Bar series\"\n"
388 " width: 600\n"
389 " height:400\n"
390 " legend.alignment: Qt.AlignBottom\n"
391 " antialiasing: true\n"
392 " ValueAxis{\n"
393 " id: valueAxisY\n"
394 " min: 0\n"
395 " max: 15\n"
396 " }\n"
397 "\n"
398 " BarSeries {\n"
399 " id: mySeries\n"
400 " axisY: valueAxisY\n"
401 " axisX: BarCategoryAxis { categories: [\"2007\", \"2008\", \"2009\", \"2010\", \"2011\", \"2012\" ] }\n"
402 " BarSet { label: \"Bob\"; values: [2, 2, 3, 4, 5, 6] }\n"
403 " BarSet { label: \"Susan\"; values: [5, 1, 2, 4, 1, 7] }\n"
404 " BarSet { label: \"James\"; values: [3, 5, 8, 13, 5, 8] }\n"
405 " }\n"
406 "}\n"
407 ) );
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( u"/symbologyAdd.svg"_s ) );
421
422 QToolButton *editExpressionButton = new QToolButton();
423 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
424 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
425
426 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, qmlCode] {
427 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
428 if ( !expression.isEmpty() )
429 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.arg( expression ) );
430 } );
431
432 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, qmlCode] {
433 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( qmlCode, u"expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)"_s );
434 expression.replace( "\\\""_L1, "\""_L1 );
435 QgsExpressionContext context = createExpressionContext();
436 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, 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( '"', "\\\""_L1 );
442 if ( !expression.isEmpty() )
443 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.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, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
477
478 mainLayout->addWidget( buttonBox );
479
480 if ( dlg.exec() )
481 {
482 QgsAttributesFormData::QmlElementEditorConfiguration qmlEdCfg;
483 qmlEdCfg.qmlCode = qmlCode->text();
484 itemData.setQmlElementEditorConfiguration( qmlEdCfg );
485 itemData.setShowLabel( showLabelCheckbox->isChecked() );
486
487 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
488 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
489 }
490 }
491 break;
492
494 {
495 QDialog dlg;
496 dlg.setObjectName( "HTML Form Configuration Widget" );
498 dlg.setWindowTitle( tr( "Configure HTML Widget" ) );
499
500 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
501 QSplitter *htmlSplitter = new QSplitter();
502 QWidget *htmlConfigWiget = new QWidget();
503 QVBoxLayout *layout = new QVBoxLayout( htmlConfigWiget );
504 layout->setContentsMargins( 0, 0, 0, 0 );
505 mainLayout->addWidget( htmlSplitter );
506 htmlSplitter->addWidget( htmlConfigWiget );
507 htmlSplitter->setChildrenCollapsible( false );
508 htmlSplitter->setHandleWidth( 6 );
509 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
510 layout->addWidget( baseWidget );
511
512 QLineEdit *title = new QLineEdit( itemName );
513
514 //htmlCode
515 QgsCodeEditorHTML *htmlCode = new QgsCodeEditorHTML();
516 htmlCode->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
517 htmlCode->setText( itemData.htmlElementEditorConfiguration().htmlCode );
518
519 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
520 QgsFeature previewFeature;
521 mLayer->getFeatures().nextFeature( previewFeature );
522
523 //update preview on text change
524 connect( htmlCode, &QgsCodeEditorHTML::textChanged, this, [htmlWrapper, htmlCode, previewFeature] {
525 htmlWrapper->setHtmlCode( htmlCode->text() );
526 htmlWrapper->reinitWidget();
527 htmlWrapper->setFeature( previewFeature );
528 } );
529
530 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
531 expressionWidget->setButtonVisible( false );
532 expressionWidget->registerExpressionContextGenerator( this );
533 expressionWidget->setLayer( mLayer );
534 QToolButton *addFieldButton = new QToolButton();
535 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
536
537 QToolButton *editExpressionButton = new QToolButton();
538 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
539 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
540
541 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, htmlCode] {
542 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
543 if ( !expression.isEmpty() )
544 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
545 } );
546
547 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, htmlCode] {
548 QString expression
549 = QgsExpressionFinder::findAndSelectActiveExpression( htmlCode, u"<script>\\s*document\\.write\\(\\s*expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)\\s*\\)\\s*;?\\s*</script>"_s );
550 expression.replace( "\\\""_L1, "\""_L1 );
551 QgsExpressionContext context = createExpressionContext();
552 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
553
554 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
555 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
556 {
557 QString expression = exprDlg.expressionText().trimmed().replace( '"', "\\\""_L1 );
558 if ( !expression.isEmpty() )
559 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
560 }
561 } );
562
563 layout->addWidget( new QLabel( tr( "Title" ) ) );
564 layout->addWidget( title );
565 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "HTML Code" ) );
566 layout->addWidget( expressionWidgetBox );
567 expressionWidgetBox->setLayout( new QHBoxLayout );
568 expressionWidgetBox->layout()->addWidget( expressionWidget );
569 expressionWidgetBox->layout()->addWidget( addFieldButton );
570 expressionWidgetBox->layout()->addWidget( editExpressionButton );
571 layout->addWidget( htmlCode );
572 QScrollArea *htmlPreviewBox = new QgsScrollArea();
573 htmlPreviewBox->setLayout( new QGridLayout );
574 htmlPreviewBox->setMinimumWidth( 200 );
575 htmlPreviewBox->layout()->addWidget( htmlWrapper->widget() );
576 //emit to load preview for the first time
577 emit htmlCode->textChanged();
578 htmlSplitter->addWidget( htmlPreviewBox );
579 htmlSplitter->setChildrenCollapsible( false );
580 htmlSplitter->setHandleWidth( 6 );
581 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
582
583 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
584
585 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
586 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
587 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
588
589 mainLayout->addWidget( buttonBox );
590
591 if ( dlg.exec() )
592 {
593 QgsAttributesFormData::HtmlElementEditorConfiguration htmlEdCfg;
594 htmlEdCfg.htmlCode = htmlCode->text();
595 itemData.setHtmlElementEditorConfiguration( htmlEdCfg );
596 itemData.setShowLabel( showLabelCheckbox->isChecked() );
597
598 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
599 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
600 }
601 break;
602 }
603
605 {
606 QDialog dlg;
607 dlg.setObjectName( "Text Form Configuration Widget" );
609 dlg.setWindowTitle( tr( "Configure Text Widget" ) );
610
611 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
612 QSplitter *textSplitter = new QSplitter();
613 QWidget *textConfigWiget = new QWidget();
614 QVBoxLayout *layout = new QVBoxLayout( textConfigWiget );
615 layout->setContentsMargins( 0, 0, 0, 0 );
616 mainLayout->addWidget( textSplitter );
617 textSplitter->addWidget( textConfigWiget );
618 layout->addWidget( baseWidget );
619
620 QLineEdit *title = new QLineEdit( itemName );
621
622 QgsCodeEditorHTML *text = new QgsCodeEditorHTML();
623 text->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
624 text->setText( itemData.textElementEditorConfiguration().text );
625
626 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
627 QgsFeature previewFeature;
628 ( void ) mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( previewFeature );
629
630 //update preview on text change
631 connect( text, &QgsCodeEditorExpression::textChanged, this, [textWrapper, previewFeature, text] {
632 textWrapper->setText( text->text() );
633 textWrapper->reinitWidget();
634 textWrapper->setFeature( previewFeature );
635 } );
636
637 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
638 expressionWidget->setButtonVisible( false );
639 expressionWidget->registerExpressionContextGenerator( this );
640 expressionWidget->setLayer( mLayer );
641 QToolButton *addFieldButton = new QToolButton();
642 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
643
644 QToolButton *editExpressionButton = new QToolButton();
645 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
646 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
647
648 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, text] {
649 QString expression = expressionWidget->expression().trimmed();
650 if ( !expression.isEmpty() )
651 text->insertText( u"[%%1%]"_s.arg( expression ) );
652 } );
653 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, text] {
654 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( text );
655
656 QgsExpressionContext context = createExpressionContext();
657 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
658
659 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
660 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
661 {
662 QString expression = exprDlg.expressionText().trimmed();
663 if ( !expression.isEmpty() )
664 text->insertText( u"[%%1%]"_s.arg( expression ) );
665 }
666 } );
667
668 layout->addWidget( new QLabel( tr( "Title" ) ) );
669 layout->addWidget( title );
670 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "Text" ) );
671 layout->addWidget( expressionWidgetBox );
672 expressionWidgetBox->setLayout( new QHBoxLayout );
673 expressionWidgetBox->layout()->addWidget( expressionWidget );
674 expressionWidgetBox->layout()->addWidget( addFieldButton );
675 expressionWidgetBox->layout()->addWidget( editExpressionButton );
676 layout->addWidget( text );
677 QScrollArea *textPreviewBox = new QgsScrollArea();
678 textPreviewBox->setWidgetResizable( true );
679 textPreviewBox->setMinimumWidth( 200 );
680 textPreviewBox->setWidget( textWrapper->widget() );
681 //emit to load preview for the first time
682 emit text->textChanged();
683 textSplitter->addWidget( textPreviewBox );
684 textSplitter->setChildrenCollapsible( false );
685 textSplitter->setHandleWidth( 6 );
686 textSplitter->setSizes( QList<int>() << 1 << 1 );
687
688 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
689
690 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
691 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
692 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
693
694 mainLayout->addWidget( buttonBox );
695
696 if ( dlg.exec() )
697 {
698 QgsAttributesFormData::TextElementEditorConfiguration textEdCfg;
699 textEdCfg.text = text->text();
700 itemData.setTextElementEditorConfiguration( textEdCfg );
701 itemData.setShowLabel( showLabelCheckbox->isChecked() );
702
703 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
704 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
705 }
706 break;
707 }
708
710 {
711 QDialog dlg;
712 dlg.setObjectName( "Spacer Form Configuration Widget" );
714 dlg.setWindowTitle( tr( "Configure Spacer Widget" ) );
715
716 QVBoxLayout *mainLayout = new QVBoxLayout();
717 mainLayout->addWidget( new QLabel( tr( "Title" ) ) );
718 QLineEdit *title = new QLineEdit( itemName );
719 mainLayout->addWidget( title );
720
721 QHBoxLayout *cbLayout = new QHBoxLayout();
722 mainLayout->addLayout( cbLayout );
723 dlg.setLayout( mainLayout );
724 QCheckBox *cb = new QCheckBox { &dlg };
725 cb->setChecked( itemData.spacerElementEditorConfiguration().drawLine );
726 cbLayout->addWidget( new QLabel( tr( "Draw horizontal line" ), &dlg ) );
727 cbLayout->addWidget( cb );
728
729 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
730
731 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
732 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
733 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
734
735 mainLayout->addWidget( buttonBox );
736
737 if ( dlg.exec() )
738 {
739 QgsAttributesFormData::SpacerElementEditorConfiguration spacerEdCfg;
740 spacerEdCfg.drawLine = cb->isChecked();
741 itemData.setSpacerElementEditorConfiguration( spacerEdCfg );
742 itemData.setShowLabel( false );
743
744 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
745 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
746 }
747
748 break;
749 }
750 }
751}
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.