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