QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgstableeditorwidget.cpp
Go to the documentation of this file.
1// This file is part of CppSheets.
2//
3// Copyright 2018 Patrick Flynn <[email protected]>
4//
5// CppSheets is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// CppSheets is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with CppSheets. If not, see <https://www.gnu.org/licenses/>.
17
19
20#include "qgsnumericformat.h"
21
22#include <QHeaderView>
23#include <QKeyEvent>
24#include <QMenu>
25#include <QPlainTextEdit>
26#include <QStringList>
27
28#include "moc_qgstableeditorwidget.cpp"
29
31 : QTableWidget( parent )
32{
33 mHeaderMenu = new QMenu( this );
34 mCellMenu = new QMenu( this );
35 setColumnCount( 0 );
36 setRowCount( 0 );
37 connect( this, &QgsTableEditorWidget::cellChanged, this, [this] {
38 if ( !mBlockSignals )
39 emit tableChanged();
40 } );
41
42 setContextMenuPolicy( Qt::CustomContextMenu );
43 connect( this, &QWidget::customContextMenuRequested, this, [this]( const QPoint &point ) {
44 mCellMenu->clear();
45 if ( canMergeSelection() )
46 {
47 QAction *mergeCells = mCellMenu->addAction( tr( "Merge Selected Cells" ) );
48 connect( mergeCells, &QAction::triggered, this, &QgsTableEditorWidget::mergeSelectedCells );
49 }
50 if ( canSplitSelection() )
51 {
52 QAction *splitCells = mCellMenu->addAction( tr( "Split Selected Cells" ) );
53 connect( splitCells, &QAction::triggered, this, &QgsTableEditorWidget::splitSelectedCells );
54 }
55 if ( !mCellMenu->isEmpty() )
56 mCellMenu->popup( mapToGlobal( point ) );
57 } );
58
59
60 horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
61 connect( horizontalHeader(), &QWidget::customContextMenuRequested, this, [this]( const QPoint &point ) {
62 const int column = horizontalHeader()->logicalIndexAt( point.x() );
63
64 QSet<int> selectedColumns;
65 for ( const QModelIndex &index : selectedIndexes() )
66 {
67 selectedColumns.insert( index.column() );
68 }
69 int minCol = 0;
70 int maxCol = 0;
71 bool isConsecutive = collectConsecutiveColumnRange( selectedIndexes(), minCol, maxCol );
72
73 // this is modeled off Libreoffice calc!
74 if ( selectedIndexes().count() == 1 )
75 {
76 // select whole column
77 selectColumn( column );
78 isConsecutive = true;
79 }
80 else if ( !selectedColumns.contains( column ) )
81 {
82 // select whole column
83 selectColumn( column );
84 isConsecutive = true;
85 }
86
87 mHeaderMenu->clear();
88 if ( isConsecutive )
89 {
90 QAction *insertBefore = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Insert %n Column(s) Before", nullptr, selectedColumns.size() ) : tr( "Insert Column Before" ) );
91 connect( insertBefore, &QAction::triggered, this, &QgsTableEditorWidget::insertColumnsBefore );
92 QAction *insertAfter = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Insert %n Column(s) After", nullptr, selectedColumns.size() ) : tr( "Insert Column After" ) );
93 connect( insertAfter, &QAction::triggered, this, &QgsTableEditorWidget::insertColumnsAfter );
94 }
95 QAction *deleteSelected = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Delete %n Column(s)", nullptr, selectedColumns.size() ) : tr( "Delete Column" ) );
96 connect( deleteSelected, &QAction::triggered, this, &QgsTableEditorWidget::deleteColumns );
97
98 mHeaderMenu->popup( horizontalHeader()->mapToGlobal( point ) );
99 } );
100
101 verticalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
102 connect( verticalHeader(), &QWidget::customContextMenuRequested, this, [this]( const QPoint &point ) {
103 const int row = verticalHeader()->logicalIndexAt( point.y() );
104
105 QSet<int> selectedRows;
106 for ( const QModelIndex &index : selectedIndexes() )
107 {
108 selectedRows.insert( index.row() );
109 }
110 int minRow = 0;
111 int maxRow = 0;
112 bool isConsecutive = collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow );
113
114 // this is modeled off Libreoffice calc!
115 if ( selectedIndexes().count() == 1 )
116 {
117 // select whole row
118 selectRow( row );
119 isConsecutive = true;
120 }
121 else if ( !selectedRows.contains( row ) )
122 {
123 // select whole row
124 selectRow( row );
125 isConsecutive = true;
126 }
127
128 mHeaderMenu->clear();
129 if ( isConsecutive )
130 {
131 QAction *insertBefore = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Insert %n Row(s) Above", nullptr, selectedRows.size() ) : tr( "Insert Row Above" ) );
132 connect( insertBefore, &QAction::triggered, this, &QgsTableEditorWidget::insertRowsAbove );
133 QAction *insertAfter = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Insert %n Row(s) Below", nullptr, selectedRows.size() ) : tr( "Insert Row Below" ) );
134 connect( insertAfter, &QAction::triggered, this, &QgsTableEditorWidget::insertRowsBelow );
135 }
136 QAction *deleteSelected = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Delete %n Row(s)", nullptr, selectedRows.size() ) : tr( "Delete Row" ) );
137 connect( deleteSelected, &QAction::triggered, this, &QgsTableEditorWidget::deleteRows );
138
139 mHeaderMenu->popup( verticalHeader()->mapToGlobal( point ) );
140 } );
141
142
143 QgsTableEditorDelegate *delegate = new QgsTableEditorDelegate( this );
144 connect( delegate, &QgsTableEditorDelegate::updateNumericFormatForIndex, this, &QgsTableEditorWidget::updateNumericFormatForIndex );
145 setItemDelegate( delegate );
146
147
148 connect( this, &QTableWidget::cellDoubleClicked, this, [this] {
149 if ( QgsTableEditorDelegate *d = qobject_cast<QgsTableEditorDelegate *>( itemDelegate() ) )
150 {
151 d->setWeakEditorMode( false );
152 }
153 } );
154
155 connect( selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsTableEditorWidget::activeCellChanged );
156}
157
159{
160 qDeleteAll( mNumericFormats );
161}
162
163void QgsTableEditorWidget::updateNumericFormatForIndex( const QModelIndex &index )
164{
165 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
166 {
167 if ( QgsNumericFormat *format = mNumericFormats.value( i ) )
168 {
169 i->setData( Qt::DisplayRole, format->formatDouble( index.data( CellContent ).toDouble(), QgsNumericFormatContext() ) );
170 }
171 }
172}
173
174void QgsTableEditorWidget::updateHeaders()
175{
176 QStringList headers;
177 QStringList letters;
178
179 QString first;
180 QString current;
181
182 for ( char c = 'A'; c <= 'Z'; c++ )
183 {
184 letters.push_back( QString( c ) );
185 }
186
187 int len = letters.length();
188 int index = 0;
189 int fIndex = 0;
190
191 for ( int i = 0; i < 1000; i++ )
192 {
193 if ( index == len )
194 {
195 index = 0;
196
197 first = letters.at( fIndex );
198 fIndex++;
199
200 if ( fIndex == len )
201 {
202 fIndex = 0;
203 }
204 }
205
206 current = first;
207 current += letters.at( index );
208 headers.push_back( current );
209 current.clear();
210
211 index++;
212 }
213
214 setHorizontalHeaderLabels( headers );
215
216 headers.clear();
217 if ( mIncludeHeader )
218 headers << tr( "Header" );
219 for ( int i = 1; i <= 1000; i++ )
220 {
221 headers << QString::number( i );
222 }
223
224 setVerticalHeaderLabels( headers );
225}
226
227bool QgsTableEditorWidget::collectConsecutiveRowRange( const QModelIndexList &list, int &minRow, int &maxRow ) const
228{
229 QSet<int> includedRows;
230 minRow = std::numeric_limits<int>::max();
231 maxRow = -1;
232 for ( const QModelIndex &index : list )
233 {
234 includedRows.insert( index.row() );
235 minRow = std::min( minRow, index.row() );
236 maxRow = std::max( maxRow, index.row() );
237 }
238
239 // test that selection is consecutive rows
240 for ( int r = minRow + 1; r < maxRow; r++ )
241 {
242 if ( !includedRows.contains( r ) )
243 return false;
244 }
245 return true;
246}
247
248bool QgsTableEditorWidget::collectConsecutiveColumnRange( const QModelIndexList &list, int &minColumn, int &maxColumn ) const
249{
250 QSet<int> includedColumns;
251 minColumn = std::numeric_limits<int>::max();
252 maxColumn = -1;
253 for ( const QModelIndex &index : list )
254 {
255 includedColumns.insert( index.column() );
256 minColumn = std::min( minColumn, index.column() );
257 maxColumn = std::max( maxColumn, index.column() );
258 }
259
260 // test that selection is consecutive columns
261 for ( int r = minColumn + 1; r < maxColumn; r++ )
262 {
263 if ( !includedColumns.contains( r ) )
264 return false;
265 }
266 return true;
267}
268
269QList<int> QgsTableEditorWidget::collectUniqueRows( const QModelIndexList &list ) const
270{
271 QList<int> res;
272 for ( const QModelIndex &index : list )
273 {
274 if ( !res.contains( index.row() ) )
275 res << index.row();
276 }
277 std::sort( res.begin(), res.end() );
278 return res;
279}
280
281QList<int> QgsTableEditorWidget::collectUniqueColumns( const QModelIndexList &list ) const
282{
283 QList<int> res;
284 for ( const QModelIndex &index : list )
285 {
286 if ( !res.contains( index.column() ) )
287 res << index.column();
288 }
289 std::sort( res.begin(), res.end() );
290 return res;
291}
292
293bool QgsTableEditorWidget::isRectangularSelection( const QModelIndexList &list ) const
294{
295 if ( list.empty() )
296 return false;
297
298 int minRow = -1;
299 int maxRow = -1;
300 int minCol = -1;
301 int maxCol = -1;
302 QSet<QPair<int, int>> selectedSet;
303 for ( const QModelIndex &index : list )
304 {
305 if ( minRow == -1 || index.row() < minRow )
306 minRow = index.row();
307 if ( maxRow == -1 || index.row() > maxRow )
308 maxRow = index.row();
309 if ( minCol == -1 || index.column() < minCol )
310 minCol = index.column();
311 if ( maxCol == -1 || index.column() > maxCol )
312 maxCol = index.column();
313 selectedSet.insert( qMakePair( index.row(), index.column() ) );
314 }
315
316 // check if the number of cells matches the expected rectangle size
317 if ( list.size() != ( maxRow - minRow + 1 ) * ( maxCol - minCol + 1 ) )
318 return false;
319
320 // check if all cells within the rectangle are selected
321 QSet<QPair<int, int>> expectedSet;
322 for ( int row = minRow; row <= maxRow; ++row )
323 {
324 for ( int col = minCol; col <= maxCol; ++col )
325 {
326 expectedSet.insert( qMakePair( row, col ) );
327 }
328 }
329 return selectedSet == expectedSet;
330}
331
332bool QgsTableEditorWidget::hasMergedCells( const QModelIndexList &list ) const
333{
334 for ( const QModelIndex &index : list )
335 {
336 if ( rowSpan( index.row(), index.column() ) > 1
337 || columnSpan( index.row(), index.column() ) > 1 )
338 return true;
339 }
340 return false;
341}
342
344{
345 switch ( event->key() )
346 {
347 case Qt::Key_Enter:
348 case Qt::Key_Return:
349 {
350 //Enter or return keys moves to next row
351 QTableWidget::keyPressEvent( event );
352 setCurrentCell( currentRow() + 1, currentColumn() );
353 break;
354 }
355
356 case Qt::Key_Delete:
357 {
359 break;
360 }
361
362 default:
363 QTableWidget::keyPressEvent( event );
364 }
365 if ( QgsTableEditorDelegate *d = qobject_cast<QgsTableEditorDelegate *>( itemDelegate() ) )
366 {
367 d->setWeakEditorMode( true );
368 }
369}
370
372{
373 mBlockSignals++;
374 qDeleteAll( mNumericFormats );
375 mNumericFormats.clear();
376
377 QgsNumericFormatContext numericContext;
378 int rowNumber = mIncludeHeader ? 1 : 0;
379 bool first = true;
380 setRowCount( contents.size() + rowNumber );
381 for ( const QgsTableRow &row : contents )
382 {
383 if ( first )
384 {
385 setColumnCount( row.size() );
386 first = false;
387 }
388
389 int colNumber = 0;
390 for ( const QgsTableCell &col : row )
391 {
392 QTableWidgetItem *item = new QTableWidgetItem( col.content().value<QgsProperty>().isActive() ? col.content().value<QgsProperty>().asExpression() : col.content().toString() );
393 item->setData( CellContent, col.content() ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
394 item->setData( Qt::BackgroundRole, col.backgroundColor().isValid() ? col.backgroundColor() : QColor( 255, 255, 255 ) );
395 item->setData( PresetBackgroundColorRole, col.backgroundColor().isValid() ? col.backgroundColor() : QVariant() );
396 item->setData( Qt::ForegroundRole, col.textFormat().isValid() ? col.textFormat().color() : QVariant() );
397 item->setData( TextFormat, QVariant::fromValue( col.textFormat() ) );
398 item->setData( HorizontalAlignment, static_cast<int>( col.horizontalAlignment() ) );
399 item->setData( VerticalAlignment, static_cast<int>( col.verticalAlignment() ) );
400 item->setData( CellProperty, QVariant::fromValue( col.content().value<QgsProperty>() ) );
401
402 if ( col.content().value<QgsProperty>().isActive() )
403 item->setFlags( item->flags() & ( ~Qt::ItemIsEditable ) );
404
405 if ( auto *lNumericFormat = col.numericFormat() )
406 {
407 mNumericFormats.insert( item, lNumericFormat->clone() );
408 item->setData( Qt::DisplayRole, mNumericFormats.value( item )->formatDouble( col.content().toDouble(), numericContext ) );
409 }
410 setItem( rowNumber, colNumber, item );
411
412 if ( col.rowSpan() > 1 || col.columnSpan() > 1 )
413 setSpan( rowNumber, colNumber, col.rowSpan(), col.columnSpan() );
414
415 colNumber++;
416 }
417 rowNumber++;
418 }
419
420 mBlockSignals--;
421 updateHeaders();
422
423 if ( mFirstSet )
424 {
425 resizeColumnsToContents();
426 resizeRowsToContents();
427 mFirstSet = false;
428 }
429 emit tableChanged();
430}
431
433{
434 QgsTableContents items;
435 items.reserve( rowCount() );
436
437 QSet<QPair<int, int>> spannedCells;
438 for ( int r = mIncludeHeader ? 1 : 0; r < rowCount(); r++ )
439 {
440 QgsTableRow row;
441 row.reserve( columnCount() );
442 for ( int c = 0; c < columnCount(); c++ )
443 {
444 QgsTableCell cell;
445 if ( QTableWidgetItem *i = item( r, c ) )
446 {
447 cell.setContent( i->data( CellProperty ).value<QgsProperty>().isActive() ? i->data( CellProperty ) : i->data( CellContent ) );
448 cell.setBackgroundColor( i->data( PresetBackgroundColorRole ).value<QColor>() );
449 cell.setTextFormat( i->data( TextFormat ).value<QgsTextFormat>() );
450 cell.setHorizontalAlignment( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) );
451 cell.setVerticalAlignment( static_cast<Qt::Alignment>( i->data( VerticalAlignment ).toInt() ) );
452
453 // we only want to set row/col span > 1 for the left/top most cells in a span:
454 if ( !spannedCells.contains( qMakePair( r, c ) ) )
455 {
456 const int rowsSpan = rowSpan( r, c );
457 const int colsSpan = columnSpan( r, c );
458 for ( int spannedRow = r; spannedRow < r + rowsSpan; ++spannedRow )
459 {
460 for ( int spannedCol = c; spannedCol < c + colsSpan; ++spannedCol )
461 {
462 spannedCells.insert( qMakePair( spannedRow, spannedCol ) );
463 }
464 }
465 cell.setSpan( rowSpan( r, c ), columnSpan( r, c ) );
466 }
467 else
468 {
469 cell.setSpan( 1, 1 );
470 }
471
472 if ( mNumericFormats.value( i ) )
473 {
474 cell.setNumericFormat( mNumericFormats.value( i )->clone() );
475 }
476 }
477 row.push_back( cell );
478 }
479 items.push_back( row );
480 }
481
482 return items;
483}
484
486{
487 bool changed = false;
488 mBlockSignals++;
489 std::unique_ptr<QgsNumericFormat> newFormat( format );
490 const QModelIndexList selection = selectedIndexes();
491 QgsNumericFormatContext numericContext;
492 for ( const QModelIndex &index : selection )
493 {
494 if ( index.row() == 0 && mIncludeHeader )
495 continue;
496
497 QTableWidgetItem *i = item( index.row(), index.column() );
498 if ( !i )
499 {
500 i = new QTableWidgetItem();
501 setItem( index.row(), index.column(), i );
502 }
503 if ( !mNumericFormats.value( i ) && newFormat )
504 {
505 changed = true;
506 mNumericFormats.insert( i, newFormat->clone() );
507 }
508 else if ( mNumericFormats.value( i ) && !newFormat )
509 {
510 changed = true;
511 delete mNumericFormats.value( i );
512 mNumericFormats.remove( i );
513 }
514 else if ( newFormat && *newFormat != *mNumericFormats.value( i ) )
515 {
516 changed = true;
517 delete mNumericFormats.value( i );
518 mNumericFormats.insert( i, newFormat->clone() );
519 }
520 i->setData( Qt::DisplayRole, newFormat ? mNumericFormats.value( i )->formatDouble( i->data( CellContent ).toDouble(), numericContext ) : i->data( CellContent ) );
521 }
522 mBlockSignals--;
523 if ( changed && !mBlockSignals )
524 emit tableChanged();
525}
526
528{
529 QgsNumericFormat *f = nullptr;
530 bool first = true;
531 const QModelIndexList selection = selectedIndexes();
532 for ( const QModelIndex &index : selection )
533 {
534 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
535 {
536 if ( first )
537 {
538 f = mNumericFormats.value( i );
539 first = false;
540 }
541 else if ( ( !f && !mNumericFormats.value( i ) )
542 || ( f && mNumericFormats.value( i ) && *f == *mNumericFormats.value( i ) ) )
543 continue;
544 else
545 {
546 return nullptr;
547 }
548 }
549 else
550 {
551 return nullptr;
552 }
553 }
554 return f;
555}
556
558{
559 QgsNumericFormat *f = nullptr;
560 bool first = true;
561 const QModelIndexList selection = selectedIndexes();
562 for ( const QModelIndex &index : selection )
563 {
564 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
565 {
566 if ( first )
567 {
568 f = mNumericFormats.value( i );
569 first = false;
570 }
571 else if ( ( !f && !mNumericFormats.value( i ) )
572 || ( f && mNumericFormats.value( i ) && *f == *mNumericFormats.value( i ) ) )
573 continue;
574 else
575 {
576 return true;
577 }
578 }
579 else if ( f )
580 {
581 return true;
582 }
583 }
584 return false;
585}
586
588{
590 return f.isValid() ? f.color() : QColor();
591}
592
594{
595 QColor c;
596 bool first = true;
597 const QModelIndexList selection = selectedIndexes();
598 for ( const QModelIndex &index : selection )
599 {
600 QColor indexColor = model()->data( index, PresetBackgroundColorRole ).isValid() ? model()->data( index, PresetBackgroundColorRole ).value<QColor>() : QColor();
601 if ( first )
602 {
603 c = indexColor;
604 first = false;
605 }
606 else if ( indexColor == c )
607 continue;
608 else
609 {
610 return QColor();
611 }
612 }
613 return c;
614}
615
617{
618 Qt::Alignment alignment = Qt::AlignLeft;
619 bool first = true;
620 const QModelIndexList selection = selectedIndexes();
621 for ( const QModelIndex &index : selection )
622 {
623 Qt::Alignment cellAlign = static_cast<Qt::Alignment>( model()->data( index, HorizontalAlignment ).toInt() );
624 if ( first )
625 {
626 alignment = cellAlign;
627 first = false;
628 }
629 else if ( cellAlign == alignment )
630 continue;
631 else
632 {
633 return Qt::AlignLeft | Qt::AlignTop;
634 }
635 }
636 return alignment;
637}
638
640{
641 Qt::Alignment alignment = Qt::AlignVCenter;
642 bool first = true;
643 const QModelIndexList selection = selectedIndexes();
644 for ( const QModelIndex &index : selection )
645 {
646 Qt::Alignment cellAlign = static_cast<Qt::Alignment>( model()->data( index, VerticalAlignment ).toInt() );
647 if ( first )
648 {
649 alignment = cellAlign;
650 first = false;
651 }
652 else if ( cellAlign == alignment )
653 continue;
654 else
655 {
656 return Qt::AlignLeft | Qt::AlignTop;
657 }
658 }
659 return alignment;
660}
661
663{
664 QgsProperty property;
665 bool first = true;
666 const QModelIndexList selection = selectedIndexes();
667 for ( const QModelIndex &index : selection )
668 {
669 const QgsProperty cellProperty = model()->data( index, CellProperty ).value<QgsProperty>();
670 if ( first )
671 {
672 property = cellProperty;
673 first = false;
674 }
675 else if ( cellProperty == property )
676 continue;
677 else
678 {
679 return QgsProperty();
680 }
681 }
682 return property;
683}
684
686{
687 QgsTextFormat format;
688 bool first = true;
689 const QModelIndexList selection = selectedIndexes();
690 for ( const QModelIndex &index : selection )
691 {
692 if ( !model()->data( index, TextFormat ).isValid() )
693 return QgsTextFormat();
694
695 QgsTextFormat cellFormat = model()->data( index, TextFormat ).value<QgsTextFormat>();
696 if ( first )
697 {
698 format = std::move( cellFormat );
699 first = false;
700 }
701 else if ( cellFormat == format )
702 continue;
703 else
704 return QgsTextFormat();
705 }
706 return format;
707}
708
710{
711 double height = 0;
712 bool first = true;
713 const QModelIndexList selection = selectedIndexes();
714 for ( const QModelIndex &index : selection )
715 {
716 double thisHeight = tableRowHeight( index.row() );
717 if ( first )
718 height = thisHeight;
719 else if ( thisHeight != height )
720 {
721 return -1;
722 }
723 first = false;
724 }
725 return height;
726}
727
729{
730 double width = 0;
731 bool first = true;
732 const QModelIndexList selection = selectedIndexes();
733 for ( const QModelIndex &index : selection )
734 {
735 double thisWidth = tableColumnWidth( index.column() );
736 if ( first )
737 width = thisWidth;
738 else if ( thisWidth != width )
739 {
740 return -1;
741 }
742 first = false;
743 }
744 return width;
745}
746
748{
749 double height = 0;
750 for ( int col = 0; col < columnCount(); ++col )
751 {
752 double thisHeight = model()->data( model()->index( row + ( mIncludeHeader ? 1 : 0 ), col ), RowHeight ).toDouble();
753 height = std::max( thisHeight, height );
754 }
755 return height;
756}
757
759{
760 double width = 0;
761 for ( int row = 0; row < rowCount(); ++row )
762 {
763 double thisWidth = model()->data( model()->index( row, column ), ColumnWidth ).toDouble();
764 width = std::max( thisWidth, width );
765 }
766 return width;
767}
768
769void QgsTableEditorWidget::setTableRowHeight( int row, double height )
770{
771 if ( row == 0 && mIncludeHeader )
772 return;
773
774 bool changed = false;
775 mBlockSignals++;
776
777 for ( int col = 0; col < columnCount(); ++col )
778 {
779 if ( QTableWidgetItem *i = item( row + ( mIncludeHeader ? 1 : 0 ), col ) )
780 {
781 if ( i->data( RowHeight ).toDouble() != height )
782 {
783 i->setData( RowHeight, height );
784 changed = true;
785 }
786 }
787 else
788 {
789 QTableWidgetItem *newItem = new QTableWidgetItem();
790 newItem->setData( RowHeight, height );
791 setItem( row + ( mIncludeHeader ? 1 : 0 ), col, newItem );
792 changed = true;
793 }
794 }
795
796 mBlockSignals--;
797 if ( changed && !mBlockSignals )
798 emit tableChanged();
799}
800
801void QgsTableEditorWidget::setTableColumnWidth( int col, double width )
802{
803 bool changed = false;
804 mBlockSignals++;
805 for ( int row = 0; row < rowCount(); ++row )
806 {
807 if ( QTableWidgetItem *i = item( row, col ) )
808 {
809 if ( i->data( ColumnWidth ).toDouble() != width )
810 {
811 i->setData( ColumnWidth, width );
812 changed = true;
813 }
814 }
815 else
816 {
817 QTableWidgetItem *newItem = new QTableWidgetItem();
818 newItem->setData( ColumnWidth, width );
819 setItem( row, col, newItem );
820 changed = true;
821 }
822 }
823 mBlockSignals--;
824 if ( changed && !mBlockSignals )
825 emit tableChanged();
826}
827
829{
830 return collectUniqueRows( selectedIndexes() );
831}
832
834{
835 return collectUniqueColumns( selectedIndexes() );
836}
837
839{
840 if ( !mIncludeHeader )
841 return QVariantList();
842
843 QVariantList res;
844 res.reserve( columnCount() );
845 for ( int col = 0; col < columnCount(); ++col )
846 {
847 if ( QTableWidgetItem *i = item( 0, col ) )
848 {
849 res << i->data( CellContent );
850 }
851 else
852 {
853 res << QVariant();
854 }
855 }
856 return res;
857}
858
860{
861 if ( !mIncludeHeader )
862 return false;
863
864 return collectUniqueRows( selectedIndexes() ).contains( 0 );
865}
866
868{
869 return selectedIndexes().size() > 1
871 && isRectangularSelection( selectedIndexes() );
872}
873
875{
876 return !selectedIndexes().empty()
878 && hasMergedCells( selectedIndexes() );
879}
880
882{
883 if ( rowCount() == 0 )
884 {
885 insertRow( 0 );
886 return;
887 }
888
889 int minRow = 0;
890 int maxRow = 0;
891 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
892 return;
893
894 const int rowsToInsert = maxRow - minRow + 1;
895 for ( int i = 0; i < rowsToInsert; ++i )
896 insertRow( maxRow + 1 );
897
898 updateHeaders();
899 if ( !mBlockSignals )
900 emit tableChanged();
901}
902
904{
905 if ( rowCount() == 0 )
906 {
907 insertRow( 0 );
908 return;
909 }
910
911 int minRow = 0;
912 int maxRow = 0;
913 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
914 return;
915
916 const int rowsToInsert = maxRow - minRow + 1;
917 for ( int i = 0; i < rowsToInsert; ++i )
918 insertRow( minRow );
919
920 updateHeaders();
921 if ( !mBlockSignals )
922 emit tableChanged();
923}
924
926{
927 if ( columnCount() == 0 )
928 {
929 insertColumn( 0 );
930 return;
931 }
932
933 int minColumn = 0;
934 int maxColumn = 0;
935 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
936 return;
937
938 const int columnsToInsert = maxColumn - minColumn + 1;
939 for ( int i = 0; i < columnsToInsert; ++i )
940 insertColumn( minColumn );
941
942 updateHeaders();
943 if ( !mBlockSignals )
944 emit tableChanged();
945}
946
948{
949 if ( columnCount() == 0 )
950 {
951 insertColumn( 0 );
952 return;
953 }
954
955 int minColumn = 0;
956 int maxColumn = 0;
957 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
958 return;
959
960 const int columnsToInsert = maxColumn - minColumn + 1;
961 for ( int i = 0; i < columnsToInsert; ++i )
962 insertColumn( maxColumn + 1 );
963
964 updateHeaders();
965 if ( !mBlockSignals )
966 emit tableChanged();
967}
968
970{
971 const QList<int> rows = rowsAssociatedWithSelection();
972 if ( rows.empty() )
973 return;
974
975 bool changed = false;
976 for ( int i = rows.size() - 1; i >= 0 && rowCount() > 1; i-- )
977 {
978 removeRow( rows.at( i ) );
979 changed = true;
980 }
981 updateHeaders();
982 if ( changed && !mBlockSignals )
983 emit tableChanged();
984}
985
987{
988 const QList<int> columns = columnsAssociatedWithSelection();
989 if ( columns.empty() )
990 return;
991
992 bool changed = false;
993 for ( int i = columns.size() - 1; i >= 0 && columnCount() > 1; i-- )
994 {
995 removeColumn( columns.at( i ) );
996 changed = true;
997 }
998 updateHeaders();
999 if ( !mBlockSignals && changed )
1000 emit tableChanged();
1001}
1002
1004{
1005 const QModelIndexList s = selectedIndexes();
1006 for ( const QModelIndex &index : s )
1007 {
1008 selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1009 }
1010}
1011
1013{
1014 const QModelIndexList s = selectedIndexes();
1015 for ( const QModelIndex &index : s )
1016 {
1017 selectionModel()->select( index, QItemSelectionModel::Columns | QItemSelectionModel::Select );
1018 }
1019}
1020
1022{
1023 const QModelIndexList selection = selectedIndexes();
1024 bool changed = false;
1025 mBlockSignals++;
1026 for ( const QModelIndex &index : selection )
1027 {
1028 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1029 {
1030 i->setText( QString() );
1031 i->setData( CellContent, QVariant() );
1032 changed = true;
1033 }
1034 }
1035 mBlockSignals--;
1036 if ( changed && !mBlockSignals )
1037 emit tableChanged();
1038}
1039
1041{
1042 const QModelIndexList selection = selectedIndexes();
1043 bool changed = false;
1044 mBlockSignals++;
1045 for ( const QModelIndex &index : selection )
1046 {
1047 if ( index.row() == 0 && mIncludeHeader )
1048 continue;
1049
1050 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1051 {
1052 if ( i->data( Qt::ForegroundRole ).value<QColor>() != color )
1053 {
1054 i->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1055 QgsTextFormat f = i->data( TextFormat ).value<QgsTextFormat>();
1056 f.setColor( color );
1057 i->setData( TextFormat, QVariant::fromValue( f ) );
1058 changed = true;
1059 }
1060 }
1061 else
1062 {
1063 QTableWidgetItem *newItem = new QTableWidgetItem();
1064 newItem->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1065 QgsTextFormat f;
1066 f.setColor( color );
1067 newItem->setData( TextFormat, QVariant::fromValue( f ) );
1068 setItem( index.row(), index.column(), newItem );
1069 changed = true;
1070 }
1071 }
1072 mBlockSignals--;
1073 if ( changed && !mBlockSignals )
1074 emit tableChanged();
1075}
1076
1078{
1079 const QModelIndexList selection = selectedIndexes();
1080 bool changed = false;
1081 mBlockSignals++;
1082 for ( const QModelIndex &index : selection )
1083 {
1084 if ( index.row() == 0 && mIncludeHeader )
1085 continue;
1086
1087 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1088 {
1089 if ( i->data( PresetBackgroundColorRole ).value<QColor>() != color )
1090 {
1091 i->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1092 i->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1093 changed = true;
1094 }
1095 }
1096 else
1097 {
1098 QTableWidgetItem *newItem = new QTableWidgetItem();
1099 newItem->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1100 newItem->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1101 setItem( index.row(), index.column(), newItem );
1102 changed = true;
1103 }
1104 }
1105 mBlockSignals--;
1106 if ( changed && !mBlockSignals )
1107 emit tableChanged();
1108}
1109
1111{
1112 const QModelIndexList selection = selectedIndexes();
1113 bool changed = false;
1114 mBlockSignals++;
1115 for ( const QModelIndex &index : selection )
1116 {
1117 if ( index.row() == 0 && mIncludeHeader )
1118 continue;
1119
1120 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1121 {
1122 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1123 {
1124 i->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1125 changed = true;
1126 }
1127 }
1128 else
1129 {
1130 QTableWidgetItem *newItem = new QTableWidgetItem();
1131 newItem->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1132 setItem( index.row(), index.column(), newItem );
1133 changed = true;
1134 }
1135 }
1136 mBlockSignals--;
1137 if ( changed && !mBlockSignals )
1138 emit tableChanged();
1139}
1140
1142{
1143 const QModelIndexList selection = selectedIndexes();
1144 bool changed = false;
1145 mBlockSignals++;
1146 for ( const QModelIndex &index : selection )
1147 {
1148 if ( index.row() == 0 && mIncludeHeader )
1149 continue;
1150
1151 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1152 {
1153 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1154 {
1155 i->setData( VerticalAlignment, static_cast<int>( alignment ) );
1156 changed = true;
1157 }
1158 }
1159 else
1160 {
1161 QTableWidgetItem *newItem = new QTableWidgetItem();
1162 newItem->setData( VerticalAlignment, static_cast<int>( alignment ) );
1163 setItem( index.row(), index.column(), newItem );
1164 changed = true;
1165 }
1166 }
1167 mBlockSignals--;
1168 if ( changed && !mBlockSignals )
1169 emit tableChanged();
1170}
1171
1173{
1174 const QModelIndexList selection = selectedIndexes();
1175 bool changed = false;
1176 mBlockSignals++;
1177 for ( const QModelIndex &index : selection )
1178 {
1179 if ( index.row() == 0 && mIncludeHeader )
1180 continue;
1181
1182 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1183 {
1184 if ( i->data( CellProperty ).value<QgsProperty>() != property )
1185 {
1186 if ( property.isActive() )
1187 {
1188 i->setData( CellProperty, QVariant::fromValue( property ) );
1189 i->setText( property.asExpression() );
1190 i->setFlags( i->flags() & ( ~Qt::ItemIsEditable ) );
1191 }
1192 else
1193 {
1194 i->setData( CellProperty, QVariant() );
1195 i->setText( QString() );
1196 i->setFlags( i->flags() | Qt::ItemIsEditable );
1197 }
1198 changed = true;
1199 }
1200 }
1201 else
1202 {
1203 QTableWidgetItem *newItem = new QTableWidgetItem( property.asExpression() );
1204 if ( property.isActive() )
1205 {
1206 newItem->setData( CellProperty, QVariant::fromValue( property ) );
1207 newItem->setFlags( newItem->flags() & ( ~Qt::ItemIsEditable ) );
1208 }
1209 else
1210 {
1211 newItem->setData( CellProperty, QVariant() );
1212 newItem->setFlags( newItem->flags() | Qt::ItemIsEditable );
1213 }
1214 setItem( index.row(), index.column(), newItem );
1215 changed = true;
1216 }
1217 }
1218 mBlockSignals--;
1219 if ( changed && !mBlockSignals )
1220 emit tableChanged();
1221}
1222
1224{
1225 const QModelIndexList selection = selectedIndexes();
1226 bool changed = false;
1227 mBlockSignals++;
1228 for ( const QModelIndex &index : selection )
1229 {
1230 if ( index.row() == 0 && mIncludeHeader )
1231 continue;
1232
1233 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1234 {
1235 i->setData( TextFormat, QVariant::fromValue( format ) );
1236 i->setData( Qt::ForegroundRole, format.color() );
1237 changed = true;
1238 }
1239 else
1240 {
1241 QTableWidgetItem *newItem = new QTableWidgetItem();
1242 newItem->setData( TextFormat, QVariant::fromValue( format ) );
1243 newItem->setData( Qt::ForegroundRole, format.color() );
1244 setItem( index.row(), index.column(), newItem );
1245 changed = true;
1246 }
1247 }
1248 mBlockSignals--;
1249 if ( changed && !mBlockSignals )
1250 emit tableChanged();
1251}
1252
1254{
1255 bool changed = false;
1256 mBlockSignals++;
1257 const QList<int> rows = rowsAssociatedWithSelection();
1258 for ( int row : rows )
1259 {
1260 if ( row == 0 && mIncludeHeader )
1261 continue;
1262
1263 for ( int col = 0; col < columnCount(); ++col )
1264 {
1265 if ( QTableWidgetItem *i = item( row, col ) )
1266 {
1267 if ( i->data( RowHeight ).toDouble() != height )
1268 {
1269 i->setData( RowHeight, height );
1270 changed = true;
1271 }
1272 }
1273 else
1274 {
1275 QTableWidgetItem *newItem = new QTableWidgetItem();
1276 newItem->setData( RowHeight, height );
1277 setItem( row, col, newItem );
1278 changed = true;
1279 }
1280 }
1281 }
1282 mBlockSignals--;
1283 if ( changed && !mBlockSignals )
1284 emit tableChanged();
1285}
1286
1288{
1289 bool changed = false;
1290 mBlockSignals++;
1291 const QList<int> cols = columnsAssociatedWithSelection();
1292 for ( int col : cols )
1293 {
1294 for ( int row = 0; row < rowCount(); ++row )
1295 {
1296 if ( QTableWidgetItem *i = item( row, col ) )
1297 {
1298 if ( i->data( ColumnWidth ).toDouble() != width )
1299 {
1300 i->setData( ColumnWidth, width );
1301 changed = true;
1302 }
1303 }
1304 else
1305 {
1306 QTableWidgetItem *newItem = new QTableWidgetItem();
1307 newItem->setData( ColumnWidth, width );
1308 setItem( row, col, newItem );
1309 changed = true;
1310 }
1311 }
1312 }
1313 mBlockSignals--;
1314 if ( changed && !mBlockSignals )
1315 emit tableChanged();
1316}
1317
1319{
1320 if ( included == mIncludeHeader )
1321 return;
1322
1323 mIncludeHeader = included;
1324
1325 if ( mIncludeHeader )
1326 insertRow( 0 );
1327 else
1328 removeRow( 0 );
1329 updateHeaders();
1330}
1331
1332void QgsTableEditorWidget::setTableHeaders( const QVariantList &headers )
1333{
1334 if ( !mIncludeHeader )
1335 return;
1336
1337 mBlockSignals++;
1338
1339 for ( int col = 0; col < columnCount(); ++col )
1340 {
1341 if ( QTableWidgetItem *i = item( 0, col ) )
1342 {
1343 i->setText( headers.value( col ).toString() );
1344 i->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1345 }
1346 else
1347 {
1348 QTableWidgetItem *item = new QTableWidgetItem( headers.value( col ).toString() );
1349 item->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1350 setItem( 0, col, item );
1351 }
1352 }
1353 mBlockSignals--;
1354}
1355
1357{
1358 const QModelIndexList selection = selectedIndexes();
1359 if ( selection.size() < 2 )
1360 return;
1361
1362 int minRow = -1;
1363 int maxRow = -1;
1364 int minCol = -1;
1365 int maxCol = -1;
1366 for ( const QModelIndex &index : selection )
1367 {
1368 if ( minRow == -1 || index.row() < minRow )
1369 minRow = index.row();
1370 if ( maxRow == -1 || index.row() > maxRow )
1371 maxRow = index.row();
1372 if ( minCol == -1 || index.column() < minCol )
1373 minCol = index.column();
1374 if ( maxCol == -1 || index.column() > maxCol )
1375 maxCol = index.column();
1376 }
1377 QStringList mergedCellText;
1378 for ( int row = minRow; row <= maxRow; ++row )
1379 {
1380 for ( int col = minCol; col <= maxCol; ++col )
1381 {
1382 if ( QTableWidgetItem *i = item( row, col ) )
1383 {
1384 const QString content = i->data( CellContent ).toString();
1385 if ( !content.isEmpty() )
1386 {
1387 mergedCellText.append( content );
1388 }
1389 }
1390 }
1391 }
1392
1393 setSpan( minRow, minCol, maxRow - minRow + 1, maxCol - minCol + 1 );
1394
1395 // set merged cell text to concatenate original cell text
1396 if ( !mergedCellText.isEmpty() )
1397 {
1398 if ( QTableWidgetItem *i = item( minRow, minCol ) )
1399 {
1400 i->setText( mergedCellText.join( ' ' ) );
1401 i->setData( CellContent, i->text() );
1402 }
1403 }
1404
1405 if ( !mBlockSignals )
1406 emit tableChanged();
1407}
1408
1410{
1411 const QModelIndexList selection = selectedIndexes();
1412 for ( const QModelIndex &index : selection )
1413 {
1414 if ( rowSpan( index.row(), index.column() ) > 1 || columnSpan( index.row(), index.column() ) > 1 )
1415 setSpan( index.row(), index.column(), 1, 1 );
1416 }
1417
1418 if ( !mBlockSignals )
1419 emit tableChanged();
1420}
1421
1423
1424QgsTableEditorTextEdit::QgsTableEditorTextEdit( QWidget *parent )
1425 : QPlainTextEdit( parent )
1426{
1427 // narrower default margins
1428 document()->setDocumentMargin( document()->documentMargin() / 2 );
1429
1430 connect( this, &QPlainTextEdit::textChanged, this, &QgsTableEditorTextEdit::resizeToContents );
1431 updateMinimumSize();
1432}
1433
1434void QgsTableEditorTextEdit::keyPressEvent( QKeyEvent *event )
1435{
1436 switch ( event->key() )
1437 {
1438 case Qt::Key_Enter:
1439 case Qt::Key_Return:
1440 {
1441 if ( event->modifiers() & Qt::ControlModifier )
1442 {
1443 // ctrl+enter inserts a line break
1444 insertPlainText( QString( '\n' ) );
1445 resizeToContents();
1446 }
1447 else
1448 {
1449 // closes editor
1450 event->ignore();
1451 }
1452 break;
1453 }
1454
1455 case Qt::Key_Right:
1456 case Qt::Key_Left:
1457 case Qt::Key_Up:
1458 case Qt::Key_Down:
1459 {
1460 if ( mWeakEditorMode )
1461 {
1462 // close editor and defer to table
1463 event->ignore();
1464 }
1465 else
1466 {
1467 QPlainTextEdit::keyPressEvent( event );
1468 }
1469 break;
1470 }
1471
1472 case Qt::Key_Tab:
1473 {
1474 if ( event->modifiers() & Qt::ControlModifier )
1475 {
1476 // if tab is pressed then defer to table, unless ctrl modifier is also held
1477 // (emulate spreadsheet behavior)
1478 insertPlainText( QString( '\t' ) );
1479 resizeToContents();
1480 }
1481 else
1482 {
1483 event->ignore();
1484 }
1485 break;
1486 }
1487
1488 default:
1489 QPlainTextEdit::keyPressEvent( event );
1490 }
1491}
1492
1493void QgsTableEditorTextEdit::updateMinimumSize()
1494{
1495 const double tm = document()->documentMargin();
1496 const QMargins cm = contentsMargins();
1497 const int width = tm * 2 + cm.left() + cm.right() + 30;
1498 const int height = tm * 2 + cm.top() + cm.bottom() + 4;
1499 QStyleOptionFrame opt;
1500 initStyleOption( &opt );
1501 const QSize sizeFromContent = style()->sizeFromContents( QStyle::CT_LineEdit, &opt, QSize( width, height ), this );
1502 setMinimumWidth( sizeFromContent.width() );
1503 setMinimumHeight( sizeFromContent.height() );
1504}
1505
1506void QgsTableEditorTextEdit::setWeakEditorMode( bool weakEditorMode )
1507{
1508 mWeakEditorMode = weakEditorMode;
1509}
1510
1511void QgsTableEditorTextEdit::resizeToContents()
1512{
1513 int oldWidth = width();
1514 int oldHeight = height();
1515 if ( mOriginalWidth == -1 )
1516 mOriginalWidth = oldWidth;
1517 if ( mOriginalHeight == -1 )
1518 mOriginalHeight = oldHeight;
1519
1520 if ( QWidget *parent = parentWidget() )
1521 {
1522 QPoint position = pos();
1523 QFontMetrics fm( font() );
1524
1525 const QStringList lines = toPlainText().split( '\n' );
1526 int maxTextLineWidth = 0;
1527 int totalTextHeight = 0;
1528 for ( const QString &line : lines )
1529 {
1530 const QRect bounds = fontMetrics().boundingRect( line );
1531 maxTextLineWidth = std::max( maxTextLineWidth, bounds.width() );
1532 totalTextHeight += fm.height();
1533 }
1534
1535 int hintWidth = minimumWidth() + maxTextLineWidth;
1536 int hintHeight = minimumHeight() + totalTextHeight;
1537 int parentWidth = parent->width();
1538 int maxWidth = isRightToLeft() ? position.x() + oldWidth : parentWidth - position.x();
1539 int maxHeight = parent->height() - position.y();
1540 int newWidth = std::clamp( hintWidth, mOriginalWidth, maxWidth );
1541 int newHeight = std::clamp( hintHeight, mOriginalHeight, maxHeight );
1542
1543 if ( mWidgetOwnsGeometry )
1544 {
1545 setMaximumWidth( newWidth );
1546 setMaximumHeight( newHeight );
1547 }
1548 if ( isRightToLeft() )
1549 move( position.x() - newWidth + oldWidth, position.y() );
1550 resize( newWidth, newHeight );
1551 }
1552}
1553
1554void QgsTableEditorTextEdit::changeEvent( QEvent *e )
1555{
1556 switch ( e->type() )
1557 {
1558 case QEvent::FontChange:
1559 case QEvent::StyleChange:
1560 case QEvent::ContentsRectChange:
1561 updateMinimumSize();
1562 break;
1563 default:
1564 break;
1565 }
1566 QPlainTextEdit::changeEvent( e );
1567}
1568
1569QgsTableEditorDelegate::QgsTableEditorDelegate( QObject *parent )
1570 : QStyledItemDelegate( parent )
1571{
1572}
1573
1574void QgsTableEditorDelegate::setWeakEditorMode( bool weakEditorMode )
1575{
1576 mWeakEditorMode = weakEditorMode;
1577}
1578
1579QWidget *QgsTableEditorDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
1580{
1581 QgsTableEditorTextEdit *w = new QgsTableEditorTextEdit( parent );
1582 w->setWeakEditorMode( mWeakEditorMode );
1583
1584 if ( !w->style()->styleHint( QStyle::SH_ItemView_DrawDelegateFrame, nullptr, w ) )
1585 w->setFrameShape( QFrame::NoFrame );
1586 if ( !w->style()->styleHint( QStyle::SH_ItemView_ShowDecorationSelected, nullptr, w ) )
1587 w->setWidgetOwnsGeometry( true );
1588
1589 return w;
1590}
1591
1592void QgsTableEditorDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1593{
1594 QVariant value = index.model()->data( index, QgsTableEditorWidget::CellContent );
1595 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1596 {
1597 if ( index != mLastIndex || lineEdit->toPlainText() != value.toString() )
1598 {
1599 lineEdit->setPlainText( value.toString() );
1600 lineEdit->selectAll();
1601 }
1602 }
1603 mLastIndex = index;
1604}
1605
1606void QgsTableEditorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
1607{
1608 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1609 {
1610 const QString text = lineEdit->toPlainText();
1611 if ( text != model->data( index, QgsTableEditorWidget::CellContent ).toString() && !model->data( index, QgsTableEditorWidget::CellProperty ).value<QgsProperty>().isActive() )
1612 {
1613 model->setData( index, text, QgsTableEditorWidget::CellContent );
1614 model->setData( index, text, Qt::DisplayRole );
1615 emit updateNumericFormatForIndex( index );
1616 }
1617 }
1618}
1619
1620
A context for numeric formats.
Abstract base class for numeric formatters, which allow for formatting a numeric value for display.
A store for object properties.
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
bool isActive() const
Returns whether the property is currently active.
Encapsulates the contents and formatting of a single table cell.
void setHorizontalAlignment(Qt::Alignment alignment)
Sets the horizontal alignment for text in the cell.
void setSpan(int rowSpan, int columnSpan)
Sets the row and column span for the cell.
void setVerticalAlignment(Qt::Alignment alignment)
Sets the vertical alignment for text in the cell.
void setBackgroundColor(const QColor &color)
Sets the cell's background color.
void setTextFormat(const QgsTextFormat &format)
Sets the cell's text format.
void setContent(const QVariant &content)
Sets the cell's content.
void setNumericFormat(QgsNumericFormat *format)
Sets the numeric format used for numbers in the cell, or nullptr if no specific format is set.
void setSelectionVerticalAlignment(Qt::Alignment alignment)
Sets the vertical alignment for the currently selected cells.
Q_DECL_DEPRECATED void setSelectionForegroundColor(const QColor &color)
Sets the foreground color for the currently selected cells.
void splitSelectedCells()
Splits (un-merges) selected table cells.
QgsProperty selectionCellProperty()
Returns the QgsProperty used for the contents of the currently selected cells.
QVariantList tableHeaders() const
Returns the table header values.
void setSelectionTextFormat(const QgsTextFormat &format)
Sets the text format for the selected cells.
void setTableHeaders(const QVariantList &headers)
Sets the table headers.
void setSelectionBackgroundColor(const QColor &color)
Sets the background color for the currently selected cells.
void activeCellChanged()
Emitted whenever the active (or selected) cell changes in the widget.
Qt::Alignment selectionHorizontalAlignment()
Returns the horizontal alignment for the currently selected cells.
QgsTextFormat selectionTextFormat()
Returns the text format for the currently selected cells.
void insertColumnsAfter()
Inserts new columns after the current selection.
double selectionRowHeight()
Returns the height (in millimeters) of the rows associated with the current selection,...
void clearSelectedCells()
Clears the contents of the currently selected cells.
bool hasMixedSelectionNumericFormat()
Returns true if the current selection has a mix of numeric formats.
QList< int > rowsAssociatedWithSelection()
Returns a list of the rows associated with the current table selected cells.
QList< int > columnsAssociatedWithSelection()
Returns a list of the columns associated with the current table selected cells.
void deleteRows()
Deletes all rows associated with the current selected cells.
void setSelectionHorizontalAlignment(Qt::Alignment alignment)
Sets the horizontal alignment for the currently selected cells.
void setSelectionRowHeight(double height)
Sets the row height (in millimeters) for the currently selected rows, or 0 for automatic row height.
void insertRowsBelow()
Inserts new rows below the current selection.
double tableRowHeight(int row)
Returns the configured row height for the specified row, or 0 if an automatic height should be used f...
QColor selectionBackgroundColor()
Returns the background color for the currently selected cells.
QgsTableEditorWidget(QWidget *parent=nullptr)
Constructor for QgsTableEditorWidget with the specified parent widget.
bool canMergeSelection() const
Returns true if a selection has been made which can be merged.
Qt::Alignment selectionVerticalAlignment()
Returns the horizontal alignment for the currently selected cells.
void insertRowsAbove()
Inserts new rows above the current selection.
void setTableColumnWidth(int column, double width)
Sets the configured column width for the specified column.
void setSelectionCellProperty(const QgsProperty &property)
Sets the cell contents QgsProperty for the currently selected cells.
void setSelectionColumnWidth(double height)
Sets the column width (in millimeters) for the currently selected columns, or 0 for automatic column ...
double selectionColumnWidth()
Returns the width (in millimeters) of the columns associated with the current selection,...
bool canSplitSelection() const
Returns true if a selection has been made which can be split.
void expandRowSelection()
Expands out the selection to include whole rows associated with the current selected cells.
double tableColumnWidth(int column)
Returns the configured column width for the specified column, or 0 if an automatic width should be us...
void setIncludeTableHeader(bool included)
Sets whether the table includes a header row.
QgsNumericFormat * selectionNumericFormat()
Returns the numeric format used for the currently selected cells, or nullptr if the selection has no ...
void setTableRowHeight(int row, double height)
Sets the configured row height for the specified row.
void keyPressEvent(QKeyEvent *event) override
QgsTableContents tableContents() const
Returns the current contents of the editor widget table.
void deleteColumns()
Deletes all columns associated with the current selected cells.
bool isHeaderCellSelected() const
Returns true if any header cells are selected.
void setTableContents(const QgsTableContents &contents)
Sets the contents to show in the editor widget.
void tableChanged()
Emitted whenever the table contents are changed.
void insertColumnsBefore()
Inserts new columns before the current selection.
void mergeSelectedCells()
Merges selected table cells.
void setSelectionNumericFormat(QgsNumericFormat *format)
Sets the numeric format to use for the currently selected cells.
void expandColumnSelection()
Expands out the selection to include whole columns associated with the current selected cells.
Q_DECL_DEPRECATED QColor selectionForegroundColor()
Returns the foreground color for the currently selected cells.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
bool isValid() const
Returns true if the format is valid.
QColor color() const
Returns the color that text will be rendered in.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QVector< QgsTableRow > QgsTableContents
A set of table rows.
QVector< QgsTableCell > QgsTableRow
A row of table cells.