QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 || columnSpan( index.row(), index.column() ) > 1 )
337 return true;
338 }
339 return false;
340}
341
343{
344 switch ( event->key() )
345 {
346 case Qt::Key_Enter:
347 case Qt::Key_Return:
348 {
349 //Enter or return keys moves to next row
350 QTableWidget::keyPressEvent( event );
351 setCurrentCell( currentRow() + 1, currentColumn() );
352 break;
353 }
354
355 case Qt::Key_Delete:
356 {
358 break;
359 }
360
361 default:
362 QTableWidget::keyPressEvent( event );
363 }
364 if ( QgsTableEditorDelegate *d = qobject_cast<QgsTableEditorDelegate *>( itemDelegate() ) )
365 {
366 d->setWeakEditorMode( true );
367 }
368}
369
371{
372 mBlockSignals++;
373 qDeleteAll( mNumericFormats );
374 mNumericFormats.clear();
375
376 QgsNumericFormatContext numericContext;
377 int rowNumber = mIncludeHeader ? 1 : 0;
378 bool first = true;
379 setRowCount( contents.size() + rowNumber );
380 for ( const QgsTableRow &row : contents )
381 {
382 if ( first )
383 {
384 setColumnCount( row.size() );
385 first = false;
386 }
387
388 int colNumber = 0;
389 for ( const QgsTableCell &col : row )
390 {
391 QTableWidgetItem *item = new QTableWidgetItem( col.content().value<QgsProperty>().isActive() ? col.content().value<QgsProperty>().asExpression() : col.content().toString() );
392 item->setData( CellContent, col.content() ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
393 item->setData( Qt::BackgroundRole, col.backgroundColor().isValid() ? col.backgroundColor() : QColor( 255, 255, 255 ) );
394 item->setData( PresetBackgroundColorRole, col.backgroundColor().isValid() ? col.backgroundColor() : QVariant() );
395 item->setData( Qt::ForegroundRole, col.textFormat().isValid() ? col.textFormat().color() : QVariant() );
396 item->setData( TextFormat, QVariant::fromValue( col.textFormat() ) );
397 item->setData( HorizontalAlignment, static_cast<int>( col.horizontalAlignment() ) );
398 item->setData( VerticalAlignment, static_cast<int>( col.verticalAlignment() ) );
399 item->setData( CellProperty, QVariant::fromValue( col.content().value<QgsProperty>() ) );
400
401 if ( col.content().value<QgsProperty>().isActive() )
402 item->setFlags( item->flags() & ( ~Qt::ItemIsEditable ) );
403
404 if ( auto *lNumericFormat = col.numericFormat() )
405 {
406 mNumericFormats.insert( item, lNumericFormat->clone() );
407 item->setData( Qt::DisplayRole, mNumericFormats.value( item )->formatDouble( col.content().toDouble(), numericContext ) );
408 }
409 setItem( rowNumber, colNumber, item );
410
411 if ( col.rowSpan() > 1 || col.columnSpan() > 1 )
412 setSpan( rowNumber, colNumber, col.rowSpan(), col.columnSpan() );
413
414 colNumber++;
415 }
416 rowNumber++;
417 }
418
419 mBlockSignals--;
420 updateHeaders();
421
422 if ( mFirstSet )
423 {
424 resizeColumnsToContents();
425 resizeRowsToContents();
426 mFirstSet = false;
427 }
428 emit tableChanged();
429}
430
432{
433 QgsTableContents items;
434 items.reserve( rowCount() );
435
436 QSet<QPair<int, int>> spannedCells;
437 for ( int r = mIncludeHeader ? 1 : 0; r < rowCount(); r++ )
438 {
439 QgsTableRow row;
440 row.reserve( columnCount() );
441 for ( int c = 0; c < columnCount(); c++ )
442 {
443 QgsTableCell cell;
444 if ( QTableWidgetItem *i = item( r, c ) )
445 {
446 cell.setContent( i->data( CellProperty ).value<QgsProperty>().isActive() ? i->data( CellProperty ) : i->data( CellContent ) );
447 cell.setBackgroundColor( i->data( PresetBackgroundColorRole ).value<QColor>() );
448 cell.setTextFormat( i->data( TextFormat ).value<QgsTextFormat>() );
449 cell.setHorizontalAlignment( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) );
450 cell.setVerticalAlignment( static_cast<Qt::Alignment>( i->data( VerticalAlignment ).toInt() ) );
451
452 // we only want to set row/col span > 1 for the left/top most cells in a span:
453 if ( !spannedCells.contains( qMakePair( r, c ) ) )
454 {
455 const int rowsSpan = rowSpan( r, c );
456 const int colsSpan = columnSpan( r, c );
457 for ( int spannedRow = r; spannedRow < r + rowsSpan; ++spannedRow )
458 {
459 for ( int spannedCol = c; spannedCol < c + colsSpan; ++spannedCol )
460 {
461 spannedCells.insert( qMakePair( spannedRow, spannedCol ) );
462 }
463 }
464 cell.setSpan( rowSpan( r, c ), columnSpan( r, c ) );
465 }
466 else
467 {
468 cell.setSpan( 1, 1 );
469 }
470
471 if ( mNumericFormats.value( i ) )
472 {
473 cell.setNumericFormat( mNumericFormats.value( i )->clone() );
474 }
475 }
476 row.push_back( cell );
477 }
478 items.push_back( row );
479 }
480
481 return items;
482}
483
485{
486 bool changed = false;
487 mBlockSignals++;
488 std::unique_ptr<QgsNumericFormat> newFormat( format );
489 const QModelIndexList selection = selectedIndexes();
490 QgsNumericFormatContext numericContext;
491 for ( const QModelIndex &index : selection )
492 {
493 if ( index.row() == 0 && mIncludeHeader )
494 continue;
495
496 QTableWidgetItem *i = item( index.row(), index.column() );
497 if ( !i )
498 {
499 i = new QTableWidgetItem();
500 setItem( index.row(), index.column(), i );
501 }
502 if ( !mNumericFormats.value( i ) && newFormat )
503 {
504 changed = true;
505 mNumericFormats.insert( i, newFormat->clone() );
506 }
507 else if ( mNumericFormats.value( i ) && !newFormat )
508 {
509 changed = true;
510 delete mNumericFormats.value( i );
511 mNumericFormats.remove( i );
512 }
513 else if ( newFormat && *newFormat != *mNumericFormats.value( i ) )
514 {
515 changed = true;
516 delete mNumericFormats.value( i );
517 mNumericFormats.insert( i, newFormat->clone() );
518 }
519 i->setData( Qt::DisplayRole, newFormat ? mNumericFormats.value( i )->formatDouble( i->data( CellContent ).toDouble(), numericContext ) : i->data( CellContent ) );
520 }
521 mBlockSignals--;
522 if ( changed && !mBlockSignals )
523 emit tableChanged();
524}
525
527{
528 QgsNumericFormat *f = nullptr;
529 bool first = true;
530 const QModelIndexList selection = selectedIndexes();
531 for ( const QModelIndex &index : selection )
532 {
533 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
534 {
535 if ( first )
536 {
537 f = mNumericFormats.value( i );
538 first = false;
539 }
540 else if ( ( !f && !mNumericFormats.value( i ) ) || ( f && mNumericFormats.value( i ) && *f == *mNumericFormats.value( i ) ) )
541 continue;
542 else
543 {
544 return nullptr;
545 }
546 }
547 else
548 {
549 return nullptr;
550 }
551 }
552 return f;
553}
554
556{
557 QgsNumericFormat *f = nullptr;
558 bool first = true;
559 const QModelIndexList selection = selectedIndexes();
560 for ( const QModelIndex &index : selection )
561 {
562 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
563 {
564 if ( first )
565 {
566 f = mNumericFormats.value( i );
567 first = false;
568 }
569 else if ( ( !f && !mNumericFormats.value( i ) ) || ( f && mNumericFormats.value( i ) && *f == *mNumericFormats.value( i ) ) )
570 continue;
571 else
572 {
573 return true;
574 }
575 }
576 else if ( f )
577 {
578 return true;
579 }
580 }
581 return false;
582}
583
585{
587 return f.isValid() ? f.color() : QColor();
588}
589
591{
592 QColor c;
593 bool first = true;
594 const QModelIndexList selection = selectedIndexes();
595 for ( const QModelIndex &index : selection )
596 {
597 QColor indexColor = model()->data( index, PresetBackgroundColorRole ).isValid() ? model()->data( index, PresetBackgroundColorRole ).value<QColor>() : QColor();
598 if ( first )
599 {
600 c = indexColor;
601 first = false;
602 }
603 else if ( indexColor == c )
604 continue;
605 else
606 {
607 return QColor();
608 }
609 }
610 return c;
611}
612
614{
615 Qt::Alignment alignment = Qt::AlignLeft;
616 bool first = true;
617 const QModelIndexList selection = selectedIndexes();
618 for ( const QModelIndex &index : selection )
619 {
620 Qt::Alignment cellAlign = static_cast<Qt::Alignment>( model()->data( index, HorizontalAlignment ).toInt() );
621 if ( first )
622 {
623 alignment = cellAlign;
624 first = false;
625 }
626 else if ( cellAlign == alignment )
627 continue;
628 else
629 {
630 return Qt::AlignLeft | Qt::AlignTop;
631 }
632 }
633 return alignment;
634}
635
637{
638 Qt::Alignment alignment = Qt::AlignVCenter;
639 bool first = true;
640 const QModelIndexList selection = selectedIndexes();
641 for ( const QModelIndex &index : selection )
642 {
643 Qt::Alignment cellAlign = static_cast<Qt::Alignment>( model()->data( index, VerticalAlignment ).toInt() );
644 if ( first )
645 {
646 alignment = cellAlign;
647 first = false;
648 }
649 else if ( cellAlign == alignment )
650 continue;
651 else
652 {
653 return Qt::AlignLeft | Qt::AlignTop;
654 }
655 }
656 return alignment;
657}
658
660{
661 QgsProperty property;
662 bool first = true;
663 const QModelIndexList selection = selectedIndexes();
664 for ( const QModelIndex &index : selection )
665 {
666 const QgsProperty cellProperty = model()->data( index, CellProperty ).value<QgsProperty>();
667 if ( first )
668 {
669 property = cellProperty;
670 first = false;
671 }
672 else if ( cellProperty == property )
673 continue;
674 else
675 {
676 return QgsProperty();
677 }
678 }
679 return property;
680}
681
683{
684 QgsTextFormat format;
685 bool first = true;
686 const QModelIndexList selection = selectedIndexes();
687 for ( const QModelIndex &index : selection )
688 {
689 if ( !model()->data( index, TextFormat ).isValid() )
690 return QgsTextFormat();
691
692 QgsTextFormat cellFormat = model()->data( index, TextFormat ).value<QgsTextFormat>();
693 if ( first )
694 {
695 format = std::move( cellFormat );
696 first = false;
697 }
698 else if ( cellFormat == format )
699 continue;
700 else
701 return QgsTextFormat();
702 }
703 return format;
704}
705
707{
708 double height = 0;
709 bool first = true;
710 const QModelIndexList selection = selectedIndexes();
711 for ( const QModelIndex &index : selection )
712 {
713 double thisHeight = tableRowHeight( index.row() );
714 if ( first )
715 height = thisHeight;
716 else if ( thisHeight != height )
717 {
718 return -1;
719 }
720 first = false;
721 }
722 return height;
723}
724
726{
727 double width = 0;
728 bool first = true;
729 const QModelIndexList selection = selectedIndexes();
730 for ( const QModelIndex &index : selection )
731 {
732 double thisWidth = tableColumnWidth( index.column() );
733 if ( first )
734 width = thisWidth;
735 else if ( thisWidth != width )
736 {
737 return -1;
738 }
739 first = false;
740 }
741 return width;
742}
743
745{
746 double height = 0;
747 for ( int col = 0; col < columnCount(); ++col )
748 {
749 double thisHeight = model()->data( model()->index( row + ( mIncludeHeader ? 1 : 0 ), col ), RowHeight ).toDouble();
750 height = std::max( thisHeight, height );
751 }
752 return height;
753}
754
756{
757 double width = 0;
758 for ( int row = 0; row < rowCount(); ++row )
759 {
760 double thisWidth = model()->data( model()->index( row, column ), ColumnWidth ).toDouble();
761 width = std::max( thisWidth, width );
762 }
763 return width;
764}
765
766void QgsTableEditorWidget::setTableRowHeight( int row, double height )
767{
768 if ( row == 0 && mIncludeHeader )
769 return;
770
771 bool changed = false;
772 mBlockSignals++;
773
774 for ( int col = 0; col < columnCount(); ++col )
775 {
776 if ( QTableWidgetItem *i = item( row + ( mIncludeHeader ? 1 : 0 ), col ) )
777 {
778 if ( i->data( RowHeight ).toDouble() != height )
779 {
780 i->setData( RowHeight, height );
781 changed = true;
782 }
783 }
784 else
785 {
786 QTableWidgetItem *newItem = new QTableWidgetItem();
787 newItem->setData( RowHeight, height );
788 setItem( row + ( mIncludeHeader ? 1 : 0 ), col, newItem );
789 changed = true;
790 }
791 }
792
793 mBlockSignals--;
794 if ( changed && !mBlockSignals )
795 emit tableChanged();
796}
797
798void QgsTableEditorWidget::setTableColumnWidth( int col, double width )
799{
800 bool changed = false;
801 mBlockSignals++;
802 for ( int row = 0; row < rowCount(); ++row )
803 {
804 if ( QTableWidgetItem *i = item( row, col ) )
805 {
806 if ( i->data( ColumnWidth ).toDouble() != width )
807 {
808 i->setData( ColumnWidth, width );
809 changed = true;
810 }
811 }
812 else
813 {
814 QTableWidgetItem *newItem = new QTableWidgetItem();
815 newItem->setData( ColumnWidth, width );
816 setItem( row, col, newItem );
817 changed = true;
818 }
819 }
820 mBlockSignals--;
821 if ( changed && !mBlockSignals )
822 emit tableChanged();
823}
824
826{
827 return collectUniqueRows( selectedIndexes() );
828}
829
831{
832 return collectUniqueColumns( selectedIndexes() );
833}
834
836{
837 if ( !mIncludeHeader )
838 return QVariantList();
839
840 QVariantList res;
841 res.reserve( columnCount() );
842 for ( int col = 0; col < columnCount(); ++col )
843 {
844 if ( QTableWidgetItem *i = item( 0, col ) )
845 {
846 res << i->data( CellContent );
847 }
848 else
849 {
850 res << QVariant();
851 }
852 }
853 return res;
854}
855
857{
858 if ( !mIncludeHeader )
859 return false;
860
861 return collectUniqueRows( selectedIndexes() ).contains( 0 );
862}
863
865{
866 return selectedIndexes().size() > 1 && !isHeaderCellSelected() && isRectangularSelection( selectedIndexes() );
867}
868
870{
871 return !selectedIndexes().empty() && !isHeaderCellSelected() && hasMergedCells( selectedIndexes() );
872}
873
875{
876 if ( rowCount() == 0 )
877 {
878 insertRow( 0 );
879 return;
880 }
881
882 int minRow = 0;
883 int maxRow = 0;
884 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
885 return;
886
887 const int rowsToInsert = maxRow - minRow + 1;
888 for ( int i = 0; i < rowsToInsert; ++i )
889 insertRow( maxRow + 1 );
890
891 updateHeaders();
892 if ( !mBlockSignals )
893 emit tableChanged();
894}
895
897{
898 if ( rowCount() == 0 )
899 {
900 insertRow( 0 );
901 return;
902 }
903
904 int minRow = 0;
905 int maxRow = 0;
906 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
907 return;
908
909 const int rowsToInsert = maxRow - minRow + 1;
910 for ( int i = 0; i < rowsToInsert; ++i )
911 insertRow( minRow );
912
913 updateHeaders();
914 if ( !mBlockSignals )
915 emit tableChanged();
916}
917
919{
920 if ( columnCount() == 0 )
921 {
922 insertColumn( 0 );
923 return;
924 }
925
926 int minColumn = 0;
927 int maxColumn = 0;
928 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
929 return;
930
931 const int columnsToInsert = maxColumn - minColumn + 1;
932 for ( int i = 0; i < columnsToInsert; ++i )
933 insertColumn( minColumn );
934
935 updateHeaders();
936 if ( !mBlockSignals )
937 emit tableChanged();
938}
939
941{
942 if ( columnCount() == 0 )
943 {
944 insertColumn( 0 );
945 return;
946 }
947
948 int minColumn = 0;
949 int maxColumn = 0;
950 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
951 return;
952
953 const int columnsToInsert = maxColumn - minColumn + 1;
954 for ( int i = 0; i < columnsToInsert; ++i )
955 insertColumn( maxColumn + 1 );
956
957 updateHeaders();
958 if ( !mBlockSignals )
959 emit tableChanged();
960}
961
963{
964 const QList<int> rows = rowsAssociatedWithSelection();
965 if ( rows.empty() )
966 return;
967
968 bool changed = false;
969 for ( int i = rows.size() - 1; i >= 0 && rowCount() > 1; i-- )
970 {
971 removeRow( rows.at( i ) );
972 changed = true;
973 }
974 updateHeaders();
975 if ( changed && !mBlockSignals )
976 emit tableChanged();
977}
978
980{
981 const QList<int> columns = columnsAssociatedWithSelection();
982 if ( columns.empty() )
983 return;
984
985 bool changed = false;
986 for ( int i = columns.size() - 1; i >= 0 && columnCount() > 1; i-- )
987 {
988 removeColumn( columns.at( i ) );
989 changed = true;
990 }
991 updateHeaders();
992 if ( !mBlockSignals && changed )
993 emit tableChanged();
994}
995
997{
998 const QModelIndexList s = selectedIndexes();
999 for ( const QModelIndex &index : s )
1000 {
1001 selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1002 }
1003}
1004
1006{
1007 const QModelIndexList s = selectedIndexes();
1008 for ( const QModelIndex &index : s )
1009 {
1010 selectionModel()->select( index, QItemSelectionModel::Columns | QItemSelectionModel::Select );
1011 }
1012}
1013
1015{
1016 const QModelIndexList selection = selectedIndexes();
1017 bool changed = false;
1018 mBlockSignals++;
1019 for ( const QModelIndex &index : selection )
1020 {
1021 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1022 {
1023 i->setText( QString() );
1024 i->setData( CellContent, QVariant() );
1025 changed = true;
1026 }
1027 }
1028 mBlockSignals--;
1029 if ( changed && !mBlockSignals )
1030 emit tableChanged();
1031}
1032
1034{
1035 const QModelIndexList selection = selectedIndexes();
1036 bool changed = false;
1037 mBlockSignals++;
1038 for ( const QModelIndex &index : selection )
1039 {
1040 if ( index.row() == 0 && mIncludeHeader )
1041 continue;
1042
1043 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1044 {
1045 if ( i->data( Qt::ForegroundRole ).value<QColor>() != color )
1046 {
1047 i->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1048 QgsTextFormat f = i->data( TextFormat ).value<QgsTextFormat>();
1049 f.setColor( color );
1050 i->setData( TextFormat, QVariant::fromValue( f ) );
1051 changed = true;
1052 }
1053 }
1054 else
1055 {
1056 QTableWidgetItem *newItem = new QTableWidgetItem();
1057 newItem->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1058 QgsTextFormat f;
1059 f.setColor( color );
1060 newItem->setData( TextFormat, QVariant::fromValue( f ) );
1061 setItem( index.row(), index.column(), newItem );
1062 changed = true;
1063 }
1064 }
1065 mBlockSignals--;
1066 if ( changed && !mBlockSignals )
1067 emit tableChanged();
1068}
1069
1071{
1072 const QModelIndexList selection = selectedIndexes();
1073 bool changed = false;
1074 mBlockSignals++;
1075 for ( const QModelIndex &index : selection )
1076 {
1077 if ( index.row() == 0 && mIncludeHeader )
1078 continue;
1079
1080 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1081 {
1082 if ( i->data( PresetBackgroundColorRole ).value<QColor>() != color )
1083 {
1084 i->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1085 i->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1086 changed = true;
1087 }
1088 }
1089 else
1090 {
1091 QTableWidgetItem *newItem = new QTableWidgetItem();
1092 newItem->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1093 newItem->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1094 setItem( index.row(), index.column(), newItem );
1095 changed = true;
1096 }
1097 }
1098 mBlockSignals--;
1099 if ( changed && !mBlockSignals )
1100 emit tableChanged();
1101}
1102
1104{
1105 const QModelIndexList selection = selectedIndexes();
1106 bool changed = false;
1107 mBlockSignals++;
1108 for ( const QModelIndex &index : selection )
1109 {
1110 if ( index.row() == 0 && mIncludeHeader )
1111 continue;
1112
1113 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1114 {
1115 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1116 {
1117 i->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1118 changed = true;
1119 }
1120 }
1121 else
1122 {
1123 QTableWidgetItem *newItem = new QTableWidgetItem();
1124 newItem->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1125 setItem( index.row(), index.column(), newItem );
1126 changed = true;
1127 }
1128 }
1129 mBlockSignals--;
1130 if ( changed && !mBlockSignals )
1131 emit tableChanged();
1132}
1133
1135{
1136 const QModelIndexList selection = selectedIndexes();
1137 bool changed = false;
1138 mBlockSignals++;
1139 for ( const QModelIndex &index : selection )
1140 {
1141 if ( index.row() == 0 && mIncludeHeader )
1142 continue;
1143
1144 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1145 {
1146 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1147 {
1148 i->setData( VerticalAlignment, static_cast<int>( alignment ) );
1149 changed = true;
1150 }
1151 }
1152 else
1153 {
1154 QTableWidgetItem *newItem = new QTableWidgetItem();
1155 newItem->setData( VerticalAlignment, static_cast<int>( alignment ) );
1156 setItem( index.row(), index.column(), newItem );
1157 changed = true;
1158 }
1159 }
1160 mBlockSignals--;
1161 if ( changed && !mBlockSignals )
1162 emit tableChanged();
1163}
1164
1166{
1167 const QModelIndexList selection = selectedIndexes();
1168 bool changed = false;
1169 mBlockSignals++;
1170 for ( const QModelIndex &index : selection )
1171 {
1172 if ( index.row() == 0 && mIncludeHeader )
1173 continue;
1174
1175 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1176 {
1177 if ( i->data( CellProperty ).value<QgsProperty>() != property )
1178 {
1179 if ( property.isActive() )
1180 {
1181 i->setData( CellProperty, QVariant::fromValue( property ) );
1182 i->setText( property.asExpression() );
1183 i->setFlags( i->flags() & ( ~Qt::ItemIsEditable ) );
1184 }
1185 else
1186 {
1187 i->setData( CellProperty, QVariant() );
1188 i->setText( QString() );
1189 i->setFlags( i->flags() | Qt::ItemIsEditable );
1190 }
1191 changed = true;
1192 }
1193 }
1194 else
1195 {
1196 QTableWidgetItem *newItem = new QTableWidgetItem( property.asExpression() );
1197 if ( property.isActive() )
1198 {
1199 newItem->setData( CellProperty, QVariant::fromValue( property ) );
1200 newItem->setFlags( newItem->flags() & ( ~Qt::ItemIsEditable ) );
1201 }
1202 else
1203 {
1204 newItem->setData( CellProperty, QVariant() );
1205 newItem->setFlags( newItem->flags() | Qt::ItemIsEditable );
1206 }
1207 setItem( index.row(), index.column(), newItem );
1208 changed = true;
1209 }
1210 }
1211 mBlockSignals--;
1212 if ( changed && !mBlockSignals )
1213 emit tableChanged();
1214}
1215
1217{
1218 const QModelIndexList selection = selectedIndexes();
1219 bool changed = false;
1220 mBlockSignals++;
1221 for ( const QModelIndex &index : selection )
1222 {
1223 if ( index.row() == 0 && mIncludeHeader )
1224 continue;
1225
1226 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1227 {
1228 i->setData( TextFormat, QVariant::fromValue( format ) );
1229 i->setData( Qt::ForegroundRole, format.color() );
1230 changed = true;
1231 }
1232 else
1233 {
1234 QTableWidgetItem *newItem = new QTableWidgetItem();
1235 newItem->setData( TextFormat, QVariant::fromValue( format ) );
1236 newItem->setData( Qt::ForegroundRole, format.color() );
1237 setItem( index.row(), index.column(), newItem );
1238 changed = true;
1239 }
1240 }
1241 mBlockSignals--;
1242 if ( changed && !mBlockSignals )
1243 emit tableChanged();
1244}
1245
1247{
1248 bool changed = false;
1249 mBlockSignals++;
1250 const QList<int> rows = rowsAssociatedWithSelection();
1251 for ( int row : rows )
1252 {
1253 if ( row == 0 && mIncludeHeader )
1254 continue;
1255
1256 for ( int col = 0; col < columnCount(); ++col )
1257 {
1258 if ( QTableWidgetItem *i = item( row, col ) )
1259 {
1260 if ( i->data( RowHeight ).toDouble() != height )
1261 {
1262 i->setData( RowHeight, height );
1263 changed = true;
1264 }
1265 }
1266 else
1267 {
1268 QTableWidgetItem *newItem = new QTableWidgetItem();
1269 newItem->setData( RowHeight, height );
1270 setItem( row, col, newItem );
1271 changed = true;
1272 }
1273 }
1274 }
1275 mBlockSignals--;
1276 if ( changed && !mBlockSignals )
1277 emit tableChanged();
1278}
1279
1281{
1282 bool changed = false;
1283 mBlockSignals++;
1284 const QList<int> cols = columnsAssociatedWithSelection();
1285 for ( int col : cols )
1286 {
1287 for ( int row = 0; row < rowCount(); ++row )
1288 {
1289 if ( QTableWidgetItem *i = item( row, col ) )
1290 {
1291 if ( i->data( ColumnWidth ).toDouble() != width )
1292 {
1293 i->setData( ColumnWidth, width );
1294 changed = true;
1295 }
1296 }
1297 else
1298 {
1299 QTableWidgetItem *newItem = new QTableWidgetItem();
1300 newItem->setData( ColumnWidth, width );
1301 setItem( row, col, newItem );
1302 changed = true;
1303 }
1304 }
1305 }
1306 mBlockSignals--;
1307 if ( changed && !mBlockSignals )
1308 emit tableChanged();
1309}
1310
1312{
1313 if ( included == mIncludeHeader )
1314 return;
1315
1316 mIncludeHeader = included;
1317
1318 if ( mIncludeHeader )
1319 insertRow( 0 );
1320 else
1321 removeRow( 0 );
1322 updateHeaders();
1323}
1324
1325void QgsTableEditorWidget::setTableHeaders( const QVariantList &headers )
1326{
1327 if ( !mIncludeHeader )
1328 return;
1329
1330 mBlockSignals++;
1331
1332 for ( int col = 0; col < columnCount(); ++col )
1333 {
1334 if ( QTableWidgetItem *i = item( 0, col ) )
1335 {
1336 i->setText( headers.value( col ).toString() );
1337 i->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1338 }
1339 else
1340 {
1341 QTableWidgetItem *item = new QTableWidgetItem( headers.value( col ).toString() );
1342 item->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1343 setItem( 0, col, item );
1344 }
1345 }
1346 mBlockSignals--;
1347}
1348
1350{
1351 const QModelIndexList selection = selectedIndexes();
1352 if ( selection.size() < 2 )
1353 return;
1354
1355 int minRow = -1;
1356 int maxRow = -1;
1357 int minCol = -1;
1358 int maxCol = -1;
1359 for ( const QModelIndex &index : selection )
1360 {
1361 if ( minRow == -1 || index.row() < minRow )
1362 minRow = index.row();
1363 if ( maxRow == -1 || index.row() > maxRow )
1364 maxRow = index.row();
1365 if ( minCol == -1 || index.column() < minCol )
1366 minCol = index.column();
1367 if ( maxCol == -1 || index.column() > maxCol )
1368 maxCol = index.column();
1369 }
1370 QStringList mergedCellText;
1371 for ( int row = minRow; row <= maxRow; ++row )
1372 {
1373 for ( int col = minCol; col <= maxCol; ++col )
1374 {
1375 if ( QTableWidgetItem *i = item( row, col ) )
1376 {
1377 const QString content = i->data( CellContent ).toString();
1378 if ( !content.isEmpty() )
1379 {
1380 mergedCellText.append( content );
1381 }
1382 }
1383 }
1384 }
1385
1386 setSpan( minRow, minCol, maxRow - minRow + 1, maxCol - minCol + 1 );
1387
1388 // set merged cell text to concatenate original cell text
1389 if ( !mergedCellText.isEmpty() )
1390 {
1391 if ( QTableWidgetItem *i = item( minRow, minCol ) )
1392 {
1393 i->setText( mergedCellText.join( ' ' ) );
1394 i->setData( CellContent, i->text() );
1395 }
1396 }
1397
1398 if ( !mBlockSignals )
1399 emit tableChanged();
1400}
1401
1403{
1404 const QModelIndexList selection = selectedIndexes();
1405 for ( const QModelIndex &index : selection )
1406 {
1407 if ( rowSpan( index.row(), index.column() ) > 1 || columnSpan( index.row(), index.column() ) > 1 )
1408 setSpan( index.row(), index.column(), 1, 1 );
1409 }
1410
1411 if ( !mBlockSignals )
1412 emit tableChanged();
1413}
1414
1416
1417QgsTableEditorTextEdit::QgsTableEditorTextEdit( QWidget *parent )
1418 : QPlainTextEdit( parent )
1419{
1420 // narrower default margins
1421 document()->setDocumentMargin( document()->documentMargin() / 2 );
1422
1423 connect( this, &QPlainTextEdit::textChanged, this, &QgsTableEditorTextEdit::resizeToContents );
1424 updateMinimumSize();
1425}
1426
1427void QgsTableEditorTextEdit::keyPressEvent( QKeyEvent *event )
1428{
1429 switch ( event->key() )
1430 {
1431 case Qt::Key_Enter:
1432 case Qt::Key_Return:
1433 {
1434 if ( event->modifiers() & Qt::ControlModifier )
1435 {
1436 // ctrl+enter inserts a line break
1437 insertPlainText( QString( '\n' ) );
1438 resizeToContents();
1439 }
1440 else
1441 {
1442 // closes editor
1443 event->ignore();
1444 }
1445 break;
1446 }
1447
1448 case Qt::Key_Right:
1449 case Qt::Key_Left:
1450 case Qt::Key_Up:
1451 case Qt::Key_Down:
1452 {
1453 if ( mWeakEditorMode )
1454 {
1455 // close editor and defer to table
1456 event->ignore();
1457 }
1458 else
1459 {
1460 QPlainTextEdit::keyPressEvent( event );
1461 }
1462 break;
1463 }
1464
1465 case Qt::Key_Tab:
1466 {
1467 if ( event->modifiers() & Qt::ControlModifier )
1468 {
1469 // if tab is pressed then defer to table, unless ctrl modifier is also held
1470 // (emulate spreadsheet behavior)
1471 insertPlainText( QString( '\t' ) );
1472 resizeToContents();
1473 }
1474 else
1475 {
1476 event->ignore();
1477 }
1478 break;
1479 }
1480
1481 default:
1482 QPlainTextEdit::keyPressEvent( event );
1483 }
1484}
1485
1486void QgsTableEditorTextEdit::updateMinimumSize()
1487{
1488 const double tm = document()->documentMargin();
1489 const QMargins cm = contentsMargins();
1490 const int width = tm * 2 + cm.left() + cm.right() + 30;
1491 const int height = tm * 2 + cm.top() + cm.bottom() + 4;
1492 QStyleOptionFrame opt;
1493 initStyleOption( &opt );
1494 const QSize sizeFromContent = style()->sizeFromContents( QStyle::CT_LineEdit, &opt, QSize( width, height ), this );
1495 setMinimumWidth( sizeFromContent.width() );
1496 setMinimumHeight( sizeFromContent.height() );
1497}
1498
1499void QgsTableEditorTextEdit::setWeakEditorMode( bool weakEditorMode )
1500{
1501 mWeakEditorMode = weakEditorMode;
1502}
1503
1504void QgsTableEditorTextEdit::resizeToContents()
1505{
1506 int oldWidth = width();
1507 int oldHeight = height();
1508 if ( mOriginalWidth == -1 )
1509 mOriginalWidth = oldWidth;
1510 if ( mOriginalHeight == -1 )
1511 mOriginalHeight = oldHeight;
1512
1513 if ( QWidget *parent = parentWidget() )
1514 {
1515 QPoint position = pos();
1516 QFontMetrics fm( font() );
1517
1518 const QStringList lines = toPlainText().split( '\n' );
1519 int maxTextLineWidth = 0;
1520 int totalTextHeight = 0;
1521 for ( const QString &line : lines )
1522 {
1523 const QRect bounds = fontMetrics().boundingRect( line );
1524 maxTextLineWidth = std::max( maxTextLineWidth, bounds.width() );
1525 totalTextHeight += fm.height();
1526 }
1527
1528 int hintWidth = minimumWidth() + maxTextLineWidth;
1529 int hintHeight = minimumHeight() + totalTextHeight;
1530 int parentWidth = parent->width();
1531 int maxWidth = isRightToLeft() ? position.x() + oldWidth : parentWidth - position.x();
1532 int maxHeight = parent->height() - position.y();
1533 int newWidth = std::clamp( hintWidth, mOriginalWidth, maxWidth );
1534 int newHeight = std::clamp( hintHeight, mOriginalHeight, maxHeight );
1535
1536 if ( mWidgetOwnsGeometry )
1537 {
1538 setMaximumWidth( newWidth );
1539 setMaximumHeight( newHeight );
1540 }
1541 if ( isRightToLeft() )
1542 move( position.x() - newWidth + oldWidth, position.y() );
1543 resize( newWidth, newHeight );
1544 }
1545}
1546
1547void QgsTableEditorTextEdit::changeEvent( QEvent *e )
1548{
1549 switch ( e->type() )
1550 {
1551 case QEvent::FontChange:
1552 case QEvent::StyleChange:
1553 case QEvent::ContentsRectChange:
1554 updateMinimumSize();
1555 break;
1556 default:
1557 break;
1558 }
1559 QPlainTextEdit::changeEvent( e );
1560}
1561
1562QgsTableEditorDelegate::QgsTableEditorDelegate( QObject *parent )
1563 : QStyledItemDelegate( parent )
1564{}
1565
1566void QgsTableEditorDelegate::setWeakEditorMode( bool weakEditorMode )
1567{
1568 mWeakEditorMode = weakEditorMode;
1569}
1570
1571QWidget *QgsTableEditorDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
1572{
1573 QgsTableEditorTextEdit *w = new QgsTableEditorTextEdit( parent );
1574 w->setWeakEditorMode( mWeakEditorMode );
1575
1576 if ( !w->style()->styleHint( QStyle::SH_ItemView_DrawDelegateFrame, nullptr, w ) )
1577 w->setFrameShape( QFrame::NoFrame );
1578 if ( !w->style()->styleHint( QStyle::SH_ItemView_ShowDecorationSelected, nullptr, w ) )
1579 w->setWidgetOwnsGeometry( true );
1580
1581 return w;
1582}
1583
1584void QgsTableEditorDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1585{
1586 QVariant value = index.model()->data( index, QgsTableEditorWidget::CellContent );
1587 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1588 {
1589 if ( index != mLastIndex || lineEdit->toPlainText() != value.toString() )
1590 {
1591 lineEdit->setPlainText( value.toString() );
1592 lineEdit->selectAll();
1593 }
1594 }
1595 mLastIndex = index;
1596}
1597
1598void QgsTableEditorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
1599{
1600 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1601 {
1602 const QString text = lineEdit->toPlainText();
1603 if ( text != model->data( index, QgsTableEditorWidget::CellContent ).toString() && !model->data( index, QgsTableEditorWidget::CellProperty ).value<QgsProperty>().isActive() )
1604 {
1605 model->setData( index, text, QgsTableEditorWidget::CellContent );
1606 model->setData( index, text, Qt::DisplayRole );
1607 emit updateNumericFormatForIndex( index );
1608 }
1609 }
1610}
1611
1612
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.