QGIS API Documentation 4.3.0-Master (9ff14a2eeba)
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::externalItemsDropped, this, &QgsAttributesFormLayoutView::handleExternalDroppedItems );
188 connect( formLayoutModel, &QgsAttributesFormLayoutModel::internalItemsDropped, this, &QgsAttributesFormLayoutView::handleInternalDroppedItems );
189}
190
191
192void QgsAttributesFormLayoutView::selectDroppedItems( const QModelIndexList &indexes )
193{
194 if ( indexes.isEmpty() )
195 return;
196
197 QItemSelection selection;
198 for ( const QModelIndex &index : indexes )
199 {
200 const QModelIndex proxyIndex = mModel->mapFromSource( index );
201 selection.select( proxyIndex, proxyIndex );
202 }
203
204 // Set the current index first without touching the selection, so that the
205 // selection is changed once, keeping all dropped items selected
206 selectionModel()->setCurrentIndex( mModel->mapFromSource( indexes.constLast() ), QItemSelectionModel::NoUpdate );
207 selectionModel()->select( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
208}
209
210void QgsAttributesFormLayoutView::handleExternalDroppedItems( const QModelIndexList &indexes )
211{
212 selectDroppedItems( indexes );
213
214 for ( const QModelIndex &index : indexes )
215 {
216 const auto itemType = static_cast< QgsAttributesFormData::AttributesFormItemType >( index.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
217
218 switch ( itemType )
219 {
224 // These need to be configured right after being dropped
225 onItemDoubleClicked( mModel->mapFromSource( index ) );
226 break;
227
233 break;
234 }
235 }
236}
237
238void QgsAttributesFormLayoutView::handleInternalDroppedItems( const QModelIndexList &indexes )
239{
240 selectDroppedItems( indexes );
241
242 // The move resets the model, collapsing the whole tree, so restore the
243 // expanded state captured in dropEvent() before the move took place.
244 const int topLevelRows = mModel->sourceModel()->rowCount( QModelIndex() );
245 for ( int row = 0; row < topLevelRows; ++row )
246 {
247 restoreExpandedState( mModel->sourceModel()->index( row, 0, QModelIndex() ) );
248 }
249 mDraggedExpandedState.clear();
250}
251
252void QgsAttributesFormLayoutView::storeExpandedState( const QModelIndex &sourceIndex )
253{
254 if ( !sourceIndex.isValid() )
255 return;
256
257 if ( auto *item = static_cast< QgsAttributesFormItem * >( sourceIndex.internalPointer() ) )
258 mDraggedExpandedState.insert( item, isExpanded( mModel->mapFromSource( sourceIndex ) ) );
259
260 const int rows = mModel->sourceModel()->rowCount( sourceIndex );
261 for ( int row = 0; row < rows; ++row )
262 {
263 storeExpandedState( mModel->sourceModel()->index( row, 0, sourceIndex ) );
264 }
265}
266
267void QgsAttributesFormLayoutView::restoreExpandedState( const QModelIndex &sourceIndex )
268{
269 if ( !sourceIndex.isValid() )
270 return;
271
272 // Expand parents before children so descendant indexes are correct
273 if ( auto *item = static_cast< QgsAttributesFormItem * >( sourceIndex.internalPointer() ) )
274 setExpanded( mModel->mapFromSource( sourceIndex ), mDraggedExpandedState.value( item, true ) );
275
276 const int rows = mModel->sourceModel()->rowCount( sourceIndex );
277 for ( int row = 0; row < rows; ++row )
278 {
279 restoreExpandedState( mModel->sourceModel()->index( row, 0, sourceIndex ) );
280 }
281}
282
284{
285 const QMimeData *data = event->mimeData();
286
287 if ( data->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || data->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) )
288 {
289 // Inner drag and drop actions are always MoveAction
290 if ( event->source() == this )
291 {
292 event->setDropAction( Qt::MoveAction );
293 }
294 }
295 else
296 {
297 event->ignore();
298 }
299
300 QTreeView::dragEnterEvent( event );
301}
302
308{
309 const QMimeData *data = event->mimeData();
310
311 if ( data->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || data->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) )
312 {
313 // Inner drag and drop actions are always MoveAction
314 if ( event->source() == this )
315 {
316 event->setDropAction( Qt::MoveAction );
317 }
318 }
319 else
320 {
321 event->ignore();
322 }
323
324 QTreeView::dragMoveEvent( event );
325}
326
328{
329 if ( !( event->mimeData()->hasFormat( u"application/x-qgsattributesformavailablewidgetsrelement"_s ) || event->mimeData()->hasFormat( u"application/x-qgsattributesformlayoutelement"_s ) ) )
330 return;
331
332 const bool internalMove = event->source() == this && event->mimeData()->hasFormat( u"application/x-qgsattributesformlayoutelement"_s );
333
334 if ( internalMove )
335 {
336 // Capture the expanded state of the whole tree now, before the (deferred)
337 // move resets the model, which collapses everything. It is restored
338 // afterwards in handleInternalDroppedItems().
339 mDraggedExpandedState.clear();
340 const int topLevelRows = mModel->sourceModel()->rowCount( QModelIndex() );
341 for ( int row = 0; row < topLevelRows; ++row )
342 {
343 storeExpandedState( mModel->sourceModel()->index( row, 0, QModelIndex() ) );
344 }
345 }
346
347 if ( event->source() == this )
348 {
349 event->setDropAction( Qt::MoveAction );
350 }
351
352 QTreeView::dropEvent( event );
353
354 if ( internalMove && event->isAccepted() )
355 {
356 // Reporting a CopyAction makes QAbstractItemView::startDrag() skip its clearOrRemove() step.
357 event->setDropAction( Qt::CopyAction );
358 event->accept();
359 }
360}
361
362void QgsAttributesFormLayoutView::onItemDoubleClicked( const QModelIndex &index )
363{
364 QModelIndex sourceIndex = mModel->mapToSource( index );
366 const auto itemType = static_cast<QgsAttributesFormData::AttributesFormItemType>( sourceIndex.data( QgsAttributesFormModel::ItemTypeRole ).toInt() );
367 const QString itemName = sourceIndex.data( QgsAttributesFormModel::ItemNameRole ).toString();
368
369 QGroupBox *baseData = new QGroupBox( tr( "Base configuration" ) );
370
371 QFormLayout *baseLayout = new QFormLayout();
372 baseData->setLayout( baseLayout );
373 QCheckBox *showLabelCheckbox = new QCheckBox( u"Show label"_s );
374 showLabelCheckbox->setChecked( itemData.showLabel() );
375 baseLayout->addRow( showLabelCheckbox );
376 QWidget *baseWidget = new QWidget();
377 baseWidget->setLayout( baseLayout );
378
379 switch ( itemType )
380 {
386 break;
387
389 {
390 QDialog dlg;
391 dlg.setObjectName( "QML Form Configuration Widget" );
393 dlg.setWindowTitle( tr( "Configure QML Widget" ) );
394
395 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
396 QSplitter *qmlSplitter = new QSplitter();
397 QWidget *qmlConfigWiget = new QWidget();
398 QVBoxLayout *layout = new QVBoxLayout( qmlConfigWiget );
399 layout->setContentsMargins( 0, 0, 0, 0 );
400 mainLayout->addWidget( qmlSplitter );
401 qmlSplitter->addWidget( qmlConfigWiget );
402 layout->addWidget( baseWidget );
403
404 QLineEdit *title = new QLineEdit( itemName );
405
406 //qmlCode
408 qmlCode->setLineNumbersVisible( true );
409 qmlCode->setEditingTimeoutInterval( 250 );
410 qmlCode->setText( itemData.qmlElementEditorConfiguration().qmlCode );
411
412 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
413 QgsFeature previewFeature;
414 mLayer->getFeatures().nextFeature( previewFeature );
415
416 //templates
417 QComboBox *qmlObjectTemplate = new QComboBox();
418 qmlObjectTemplate->addItem( tr( "Free Text…" ) );
419 qmlObjectTemplate->addItem( tr( "Rectangle" ) );
420 qmlObjectTemplate->addItem( tr( "Pie Chart" ) );
421 qmlObjectTemplate->addItem( tr( "Bar Chart" ) );
422 connect( qmlObjectTemplate, qOverload<int>( &QComboBox::currentIndexChanged ), qmlCode, [qmlCode]( int index ) {
423 qmlCode->clear();
424 switch ( index )
425 {
426 case 0:
427 {
428 qmlCode->setText( QString() );
429 break;
430 }
431 case 1:
432 {
433 qmlCode->setText( QStringLiteral(
434 "import QtQuick 2.0\n"
435 "\n"
436 "Rectangle {\n"
437 " width: 100\n"
438 " height: 100\n"
439 " color: \"steelblue\"\n"
440 " Text{ text: \"A rectangle\" }\n"
441 "}\n"
442 ) );
443 break;
444 }
445 case 2:
446 {
447 qmlCode->setText( QStringLiteral(
448 "import QtQuick 2.0\n"
449 "import QtCharts 2.0\n"
450 "\n"
451 "ChartView {\n"
452 " width: 400\n"
453 " height: 400\n"
454 "\n"
455 " PieSeries {\n"
456 " id: pieSeries\n"
457 " PieSlice { label: \"First slice\"; value: 25 }\n"
458 " PieSlice { label: \"Second slice\"; value: 45 }\n"
459 " PieSlice { label: \"Third slice\"; value: 30 }\n"
460 " }\n"
461 "}\n"
462 ) );
463 break;
464 }
465 case 3:
466 {
467 qmlCode->setText( QStringLiteral(
468 "import QtQuick 2.0\n"
469 "import QtCharts 2.0\n"
470 "\n"
471 "ChartView {\n"
472 " title: \"Bar series\"\n"
473 " width: 600\n"
474 " height:400\n"
475 " legend.alignment: Qt.AlignBottom\n"
476 " antialiasing: true\n"
477 " ValueAxis{\n"
478 " id: valueAxisY\n"
479 " min: 0\n"
480 " max: 15\n"
481 " }\n"
482 "\n"
483 " BarSeries {\n"
484 " id: mySeries\n"
485 " axisY: valueAxisY\n"
486 " axisX: BarCategoryAxis { categories: [\"2007\", \"2008\", \"2009\", \"2010\", \"2011\", \"2012\" ] }\n"
487 " BarSet { label: \"Bob\"; values: [2, 2, 3, 4, 5, 6] }\n"
488 " BarSet { label: \"Susan\"; values: [5, 1, 2, 4, 1, 7] }\n"
489 " BarSet { label: \"James\"; values: [3, 5, 8, 13, 5, 8] }\n"
490 " }\n"
491 "}\n"
492 ) );
493 break;
494 }
495 default:
496 break;
497 }
498 } );
499
500 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
501 expressionWidget->setButtonVisible( false );
502 expressionWidget->registerExpressionContextGenerator( this );
503 expressionWidget->setLayer( mLayer );
504 QToolButton *addFieldButton = new QToolButton();
505 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
506
507 QToolButton *editExpressionButton = new QToolButton();
508 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
509 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
510
511 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, qmlCode] {
512 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
513 if ( !expression.isEmpty() )
514 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.arg( expression ) );
515 } );
516
517 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, qmlCode] {
518 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( qmlCode, u"expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)"_s );
519 expression.replace( "\\\""_L1, "\""_L1 );
520 QgsExpressionContext context = createExpressionContext();
521 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
522
523 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
524 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
525 {
526 QString expression = exprDlg.expressionText().trimmed().replace( '"', "\\\""_L1 );
527 if ( !expression.isEmpty() )
528 qmlCode->insertText( u"expression.evaluate(\"%1\")"_s.arg( expression ) );
529 }
530 } );
531
532 layout->addWidget( new QLabel( tr( "Title" ) ) );
533 layout->addWidget( title );
534 QGroupBox *qmlCodeBox = new QGroupBox( tr( "QML Code" ) );
535 qmlCodeBox->setLayout( new QVBoxLayout );
536 qmlCodeBox->layout()->addWidget( qmlObjectTemplate );
537 QWidget *expressionWidgetBox = new QWidget();
538 qmlCodeBox->layout()->addWidget( expressionWidgetBox );
539 expressionWidgetBox->setLayout( new QHBoxLayout );
540 expressionWidgetBox->layout()->setContentsMargins( 0, 0, 0, 0 );
541 expressionWidgetBox->layout()->addWidget( expressionWidget );
542 expressionWidgetBox->layout()->addWidget( addFieldButton );
543 expressionWidgetBox->layout()->addWidget( editExpressionButton );
544 layout->addWidget( qmlCodeBox );
545 layout->addWidget( qmlCode );
546
547 QTextEdit *errorFeedbackWidget = new QTextEdit();
548 errorFeedbackWidget->setMaximumHeight( 90 );
549 layout->addWidget( errorFeedbackWidget );
550
551 //update preview on text change
552 connect( qmlCode, &QgsCodeEditor::editingTimeout, this, [qmlWrapper, qmlCode, previewFeature, errorFeedbackWidget] {
553 QQuickWidget *qmlWidget = dynamic_cast<QQuickWidget *>( qmlWrapper->widget() );
554 if ( qmlCode->text().trimmed().isEmpty() )
555 {
556 errorFeedbackWidget->setText( QObject::tr( "No QML code" ) );
557 if ( qmlWidget )
558 {
559 qmlWidget->setSource( QUrl() );
560 }
561 return;
562 }
563 qmlWrapper->setQmlCode( qmlCode->text() );
564 qmlWrapper->reinitWidget();
565 qmlWrapper->setFeature( previewFeature );
566 if ( qmlWidget )
567 {
568 const QList<QQmlError> errors = qmlWidget->errors();
569 if ( !errors.isEmpty() )
570 {
571 QStringList errorStrings;
572 for ( const QQmlError &error : errors )
573 {
574 errorStrings << u"%1:%2: %3"_s.arg( QString::number( error.line() ), QString::number( error.column() ), error.description() );
575 }
576 errorFeedbackWidget->setText( errorStrings.join( "\n" ) );
577 }
578 else
579 {
580 errorFeedbackWidget->setText( QObject::tr( "Valid code" ) );
581 }
582 }
583 } );
584
585 QScrollArea *qmlPreviewBox = new QgsScrollArea();
586 qmlPreviewBox->setMinimumWidth( 200 );
587 qmlPreviewBox->setWidget( qmlWrapper->widget() );
588 //emit to load preview for the first time
589 emit qmlCode->editingTimeout();
590 qmlSplitter->addWidget( qmlPreviewBox );
591 qmlSplitter->setChildrenCollapsible( false );
592 qmlSplitter->setHandleWidth( 6 );
593 qmlSplitter->setSizes( QList<int>() << 1 << 1 );
594
595 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
596
597 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
598 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
599 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
600
601 mainLayout->addWidget( buttonBox );
602
603 if ( dlg.exec() )
604 {
605 QgsAttributesFormData::QmlElementEditorConfiguration qmlEdCfg;
606 qmlEdCfg.qmlCode = qmlCode->text();
607 itemData.setQmlElementEditorConfiguration( qmlEdCfg );
608 itemData.setShowLabel( showLabelCheckbox->isChecked() );
609
610 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
611 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
612 }
613 }
614 break;
615
617 {
618 QDialog dlg;
619 dlg.setObjectName( "HTML Form Configuration Widget" );
621 dlg.setWindowTitle( tr( "Configure HTML Widget" ) );
622
623 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
624 QSplitter *htmlSplitter = new QSplitter();
625 QWidget *htmlConfigWiget = new QWidget();
626 QVBoxLayout *layout = new QVBoxLayout( htmlConfigWiget );
627 layout->setContentsMargins( 0, 0, 0, 0 );
628 mainLayout->addWidget( htmlSplitter );
629 htmlSplitter->addWidget( htmlConfigWiget );
630 htmlSplitter->setChildrenCollapsible( false );
631 htmlSplitter->setHandleWidth( 6 );
632 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
633 layout->addWidget( baseWidget );
634
635 QLineEdit *title = new QLineEdit( itemName );
636
637 //htmlCode
638 QgsCodeEditorHTML *htmlCode = new QgsCodeEditorHTML();
639 htmlCode->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
640 htmlCode->setText( itemData.htmlElementEditorConfiguration().htmlCode );
641
642 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
643 QgsFeature previewFeature;
644 mLayer->getFeatures().nextFeature( previewFeature );
645
646 //update preview on text change
647 connect( htmlCode, &QgsCodeEditorHTML::textChanged, this, [htmlWrapper, htmlCode, previewFeature] {
648 htmlWrapper->setHtmlCode( htmlCode->text() );
649 htmlWrapper->reinitWidget();
650 htmlWrapper->setFeature( previewFeature );
651 } );
652
653 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
654 expressionWidget->setButtonVisible( false );
655 expressionWidget->registerExpressionContextGenerator( this );
656 expressionWidget->setLayer( mLayer );
657 QToolButton *addFieldButton = new QToolButton();
658 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
659
660 QToolButton *editExpressionButton = new QToolButton();
661 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
662 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
663
664 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, htmlCode] {
665 QString expression = expressionWidget->expression().trimmed().replace( '"', "\\\""_L1 );
666 if ( !expression.isEmpty() )
667 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
668 } );
669
670 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, htmlCode] {
671 QString expression
672 = QgsExpressionFinder::findAndSelectActiveExpression( htmlCode, u"<script>\\s*document\\.write\\(\\s*expression\\.evaluate\\(\\s*\"(.*?)\\s*\"\\s*\\)\\s*\\)\\s*;?\\s*</script>"_s );
673 expression.replace( "\\\""_L1, "\""_L1 );
674 QgsExpressionContext context = createExpressionContext();
675 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
676
677 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
678 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
679 {
680 QString expression = exprDlg.expressionText().trimmed().replace( '"', "\\\""_L1 );
681 if ( !expression.isEmpty() )
682 htmlCode->insertText( u"<script>document.write(expression.evaluate(\"%1\"));</script>"_s.arg( expression ) );
683 }
684 } );
685
686 layout->addWidget( new QLabel( tr( "Title" ) ) );
687 layout->addWidget( title );
688 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "HTML Code" ) );
689 layout->addWidget( expressionWidgetBox );
690 expressionWidgetBox->setLayout( new QHBoxLayout );
691 expressionWidgetBox->layout()->addWidget( expressionWidget );
692 expressionWidgetBox->layout()->addWidget( addFieldButton );
693 expressionWidgetBox->layout()->addWidget( editExpressionButton );
694 layout->addWidget( htmlCode );
695 QScrollArea *htmlPreviewBox = new QgsScrollArea();
696 htmlPreviewBox->setLayout( new QGridLayout );
697 htmlPreviewBox->setMinimumWidth( 200 );
698 htmlPreviewBox->layout()->addWidget( htmlWrapper->widget() );
699 //emit to load preview for the first time
700 emit htmlCode->textChanged();
701 htmlSplitter->addWidget( htmlPreviewBox );
702 htmlSplitter->setChildrenCollapsible( false );
703 htmlSplitter->setHandleWidth( 6 );
704 htmlSplitter->setSizes( QList<int>() << 1 << 1 );
705
706 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
707
708 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
709 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
710 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
711
712 mainLayout->addWidget( buttonBox );
713
714 if ( dlg.exec() )
715 {
716 QgsAttributesFormData::HtmlElementEditorConfiguration htmlEdCfg;
717 htmlEdCfg.htmlCode = htmlCode->text();
718 itemData.setHtmlElementEditorConfiguration( htmlEdCfg );
719 itemData.setShowLabel( showLabelCheckbox->isChecked() );
720
721 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
722 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
723 }
724 break;
725 }
726
728 {
729 QDialog dlg;
730 dlg.setObjectName( "Text Form Configuration Widget" );
732 dlg.setWindowTitle( tr( "Configure Text Widget" ) );
733
734 QVBoxLayout *mainLayout = new QVBoxLayout( &dlg );
735 QSplitter *textSplitter = new QSplitter();
736 QWidget *textConfigWiget = new QWidget();
737 QVBoxLayout *layout = new QVBoxLayout( textConfigWiget );
738 layout->setContentsMargins( 0, 0, 0, 0 );
739 mainLayout->addWidget( textSplitter );
740 textSplitter->addWidget( textConfigWiget );
741 layout->addWidget( baseWidget );
742
743 QLineEdit *title = new QLineEdit( itemName );
744
745 QgsCodeEditorHTML *text = new QgsCodeEditorHTML();
746 text->setSizePolicy( QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding );
747 text->setText( itemData.textElementEditorConfiguration().text );
748
749 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
750 QgsFeature previewFeature;
751 ( void ) mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( previewFeature );
752
753 //update preview on text change
754 connect( text, &QgsCodeEditorExpression::textChanged, this, [textWrapper, previewFeature, text] {
755 textWrapper->setText( text->text() );
756 textWrapper->reinitWidget();
757 textWrapper->setFeature( previewFeature );
758 } );
759
760 QgsFieldExpressionWidget *expressionWidget = new QgsFieldExpressionWidget;
761 expressionWidget->setButtonVisible( false );
762 expressionWidget->registerExpressionContextGenerator( this );
763 expressionWidget->setLayer( mLayer );
764 QToolButton *addFieldButton = new QToolButton();
765 addFieldButton->setIcon( QgsApplication::getThemeIcon( u"/symbologyAdd.svg"_s ) );
766
767 QToolButton *editExpressionButton = new QToolButton();
768 editExpressionButton->setIcon( QgsApplication::getThemeIcon( u"/mIconExpression.svg"_s ) );
769 editExpressionButton->setToolTip( tr( "Insert/Edit Expression" ) );
770
771 connect( addFieldButton, &QAbstractButton::clicked, this, [expressionWidget, text] {
772 QString expression = expressionWidget->expression().trimmed();
773 if ( !expression.isEmpty() )
774 text->insertText( u"[%%1%]"_s.arg( expression ) );
775 } );
776 connect( editExpressionButton, &QAbstractButton::clicked, this, [this, text] {
777 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( text );
778
779 QgsExpressionContext context = createExpressionContext();
780 QgsExpressionBuilderDialog exprDlg( mLayer, expression, this, u"generic"_s, context );
781
782 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
783 if ( exprDlg.exec() == QDialog::Accepted && !exprDlg.expressionText().trimmed().isEmpty() )
784 {
785 QString expression = exprDlg.expressionText().trimmed();
786 if ( !expression.isEmpty() )
787 text->insertText( u"[%%1%]"_s.arg( expression ) );
788 }
789 } );
790
791 layout->addWidget( new QLabel( tr( "Title" ) ) );
792 layout->addWidget( title );
793 QGroupBox *expressionWidgetBox = new QGroupBox( tr( "Text" ) );
794 layout->addWidget( expressionWidgetBox );
795 expressionWidgetBox->setLayout( new QHBoxLayout );
796 expressionWidgetBox->layout()->addWidget( expressionWidget );
797 expressionWidgetBox->layout()->addWidget( addFieldButton );
798 expressionWidgetBox->layout()->addWidget( editExpressionButton );
799 layout->addWidget( text );
800 QScrollArea *textPreviewBox = new QgsScrollArea();
801 textPreviewBox->setWidgetResizable( true );
802 textPreviewBox->setMinimumWidth( 200 );
803 textPreviewBox->setWidget( textWrapper->widget() );
804 //emit to load preview for the first time
805 emit text->textChanged();
806 textSplitter->addWidget( textPreviewBox );
807 textSplitter->setChildrenCollapsible( false );
808 textSplitter->setHandleWidth( 6 );
809 textSplitter->setSizes( QList<int>() << 1 << 1 );
810
811 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
812
813 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
814 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
815 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
816
817 mainLayout->addWidget( buttonBox );
818
819 if ( dlg.exec() )
820 {
821 QgsAttributesFormData::TextElementEditorConfiguration textEdCfg;
822 textEdCfg.text = text->text();
823 itemData.setTextElementEditorConfiguration( textEdCfg );
824 itemData.setShowLabel( showLabelCheckbox->isChecked() );
825
826 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
827 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
828 }
829 break;
830 }
831
833 {
834 QDialog dlg;
835 dlg.setObjectName( "Spacer Form Configuration Widget" );
837 dlg.setWindowTitle( tr( "Configure Spacer Widget" ) );
838
839 QVBoxLayout *mainLayout = new QVBoxLayout();
840 mainLayout->addWidget( new QLabel( tr( "Title" ) ) );
841 QLineEdit *title = new QLineEdit( itemName );
842 mainLayout->addWidget( title );
843
844 QHBoxLayout *cbLayout = new QHBoxLayout();
845 mainLayout->addLayout( cbLayout );
846 dlg.setLayout( mainLayout );
847 QCheckBox *cb = new QCheckBox { &dlg };
848 cb->setChecked( itemData.spacerElementEditorConfiguration().drawLine );
849 cbLayout->addWidget( new QLabel( tr( "Draw horizontal line" ), &dlg ) );
850 cbLayout->addWidget( cb );
851
852 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help );
853
854 connect( buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept );
855 connect( buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject );
856 connect( buttonBox, &QDialogButtonBox::helpRequested, &dlg, [] { QgsHelp::openHelp( u"working_with_vector/vector_properties.html#other-widgets"_s ); } );
857
858 mainLayout->addWidget( buttonBox );
859
860 if ( dlg.exec() )
861 {
862 QgsAttributesFormData::SpacerElementEditorConfiguration spacerEdCfg;
863 spacerEdCfg.drawLine = cb->isChecked();
864 itemData.setSpacerElementEditorConfiguration( spacerEdCfg );
865 itemData.setShowLabel( false );
866
867 mModel->sourceModel()->setData( sourceIndex, itemData, QgsAttributesFormModel::ItemDataRole );
868 mModel->sourceModel()->setData( sourceIndex, title->text(), QgsAttributesFormModel::ItemNameRole );
869 }
870
871 break;
872 }
873 }
874}
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 internalItemsDropped(const QModelIndexList &indexes)
Informs that items were moved (via drop) in the model from the same model.
void externalItemsDropped(const QModelIndexList &indexes)
Informs that items were inserted (via drop) in the model from another 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.