QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 if ( !mBlockSignals )
36 emit tableChanged();
37 } );
38
39 setContextMenuPolicy( Qt::CustomContextMenu );
40 connect( this, &QWidget::customContextMenuRequested, this, [=]( const QPoint &point ) {
41 mCellMenu->clear();
42 if ( canMergeSelection() )
43 {
44 QAction *mergeCells = mCellMenu->addAction( tr( "Merge Selected Cells" ) );
45 connect( mergeCells, &QAction::triggered, this, &QgsTableEditorWidget::mergeSelectedCells );
46 }
47 if ( canSplitSelection() )
48 {
49 QAction *splitCells = mCellMenu->addAction( tr( "Split Selected Cells" ) );
50 connect( splitCells, &QAction::triggered, this, &QgsTableEditorWidget::splitSelectedCells );
51 }
52 if ( !mCellMenu->isEmpty() )
53 mCellMenu->popup( mapToGlobal( point ) );
54 } );
55
56
57 horizontalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
58 connect( horizontalHeader(), &QWidget::customContextMenuRequested, this, [=]( const QPoint &point ) {
59 const int column = horizontalHeader()->logicalIndexAt( point.x() );
60
61 QSet<int> selectedColumns;
62 for ( const QModelIndex &index : selectedIndexes() )
63 {
64 selectedColumns.insert( index.column() );
65 }
66 int minCol = 0;
67 int maxCol = 0;
68 bool isConsecutive = collectConsecutiveColumnRange( selectedIndexes(), minCol, maxCol );
69
70 // this is modeled off Libreoffice calc!
71 if ( selectedIndexes().count() == 1 )
72 {
73 // select whole column
74 selectColumn( column );
75 isConsecutive = true;
76 }
77 else if ( !selectedColumns.contains( column ) )
78 {
79 // select whole column
80 selectColumn( column );
81 isConsecutive = true;
82 }
83
84 mHeaderMenu->clear();
85 if ( isConsecutive )
86 {
87 QAction *insertBefore = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Insert %n Column(s) Before", nullptr, selectedColumns.size() ) : tr( "Insert Column Before" ) );
88 connect( insertBefore, &QAction::triggered, this, &QgsTableEditorWidget::insertColumnsBefore );
89 QAction *insertAfter = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Insert %n Column(s) After", nullptr, selectedColumns.size() ) : tr( "Insert Column After" ) );
90 connect( insertAfter, &QAction::triggered, this, &QgsTableEditorWidget::insertColumnsAfter );
91 }
92 QAction *deleteSelected = mHeaderMenu->addAction( selectedColumns.size() > 1 ? tr( "Delete %n Column(s)", nullptr, selectedColumns.size() ) : tr( "Delete Column" ) );
93 connect( deleteSelected, &QAction::triggered, this, &QgsTableEditorWidget::deleteColumns );
94
95 mHeaderMenu->popup( horizontalHeader()->mapToGlobal( point ) );
96 } );
97
98 verticalHeader()->setContextMenuPolicy( Qt::CustomContextMenu );
99 connect( verticalHeader(), &QWidget::customContextMenuRequested, this, [=]( const QPoint &point ) {
100 const int row = verticalHeader()->logicalIndexAt( point.y() );
101
102 QSet<int> selectedRows;
103 for ( const QModelIndex &index : selectedIndexes() )
104 {
105 selectedRows.insert( index.row() );
106 }
107 int minRow = 0;
108 int maxRow = 0;
109 bool isConsecutive = collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow );
110
111 // this is modeled off Libreoffice calc!
112 if ( selectedIndexes().count() == 1 )
113 {
114 // select whole row
115 selectRow( row );
116 isConsecutive = true;
117 }
118 else if ( !selectedRows.contains( row ) )
119 {
120 // select whole row
121 selectRow( row );
122 isConsecutive = true;
123 }
124
125 mHeaderMenu->clear();
126 if ( isConsecutive )
127 {
128 QAction *insertBefore = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Insert %n Row(s) Above", nullptr, selectedRows.size() ) : tr( "Insert Row Above" ) );
129 connect( insertBefore, &QAction::triggered, this, &QgsTableEditorWidget::insertRowsAbove );
130 QAction *insertAfter = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Insert %n Row(s) Below", nullptr, selectedRows.size() ) : tr( "Insert Row Below" ) );
131 connect( insertAfter, &QAction::triggered, this, &QgsTableEditorWidget::insertRowsBelow );
132 }
133 QAction *deleteSelected = mHeaderMenu->addAction( selectedRows.size() > 1 ? tr( "Delete %n Row(s)", nullptr, selectedRows.size() ) : tr( "Delete Row" ) );
134 connect( deleteSelected, &QAction::triggered, this, &QgsTableEditorWidget::deleteRows );
135
136 mHeaderMenu->popup( verticalHeader()->mapToGlobal( point ) );
137 } );
138
139
140 QgsTableEditorDelegate *delegate = new QgsTableEditorDelegate( this );
141 connect( delegate, &QgsTableEditorDelegate::updateNumericFormatForIndex, this, &QgsTableEditorWidget::updateNumericFormatForIndex );
142 setItemDelegate( delegate );
143
144
145 connect( this, &QTableWidget::cellDoubleClicked, this, [=] {
146 if ( QgsTableEditorDelegate *d = qobject_cast<QgsTableEditorDelegate *>( itemDelegate() ) )
147 {
148 d->setWeakEditorMode( false );
149 }
150 } );
151
152 connect( selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsTableEditorWidget::activeCellChanged );
153}
154
156{
157 qDeleteAll( mNumericFormats );
158}
159
160void QgsTableEditorWidget::updateNumericFormatForIndex( const QModelIndex &index )
161{
162 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
163 {
164 if ( QgsNumericFormat *format = mNumericFormats.value( i ) )
165 {
166 i->setData( Qt::DisplayRole, format->formatDouble( index.data( CellContent ).toDouble(), QgsNumericFormatContext() ) );
167 }
168 }
169}
170
171void QgsTableEditorWidget::updateHeaders()
172{
173 QStringList headers;
174 QStringList letters;
175
176 QString first;
177 QString current;
178
179 for ( char c = 'A'; c <= 'Z'; c++ )
180 {
181 letters.push_back( QString( c ) );
182 }
183
184 int len = letters.length();
185 int index = 0;
186 int fIndex = 0;
187
188 for ( int i = 0; i < 1000; i++ )
189 {
190 if ( index == len )
191 {
192 index = 0;
193
194 first = letters.at( fIndex );
195 fIndex++;
196
197 if ( fIndex == len )
198 {
199 fIndex = 0;
200 }
201 }
202
203 current = first;
204 current += letters.at( index );
205 headers.push_back( current );
206 current.clear();
207
208 index++;
209 }
210
211 setHorizontalHeaderLabels( headers );
212
213 headers.clear();
214 if ( mIncludeHeader )
215 headers << tr( "Header" );
216 for ( int i = 1; i <= 1000; i++ )
217 {
218 headers << QString::number( i );
219 }
220
221 setVerticalHeaderLabels( headers );
222}
223
224bool QgsTableEditorWidget::collectConsecutiveRowRange( const QModelIndexList &list, int &minRow, int &maxRow ) const
225{
226 QSet<int> includedRows;
227 minRow = std::numeric_limits<int>::max();
228 maxRow = -1;
229 for ( const QModelIndex &index : list )
230 {
231 includedRows.insert( index.row() );
232 minRow = std::min( minRow, index.row() );
233 maxRow = std::max( maxRow, index.row() );
234 }
235
236 // test that selection is consecutive rows
237 for ( int r = minRow + 1; r < maxRow; r++ )
238 {
239 if ( !includedRows.contains( r ) )
240 return false;
241 }
242 return true;
243}
244
245bool QgsTableEditorWidget::collectConsecutiveColumnRange( const QModelIndexList &list, int &minColumn, int &maxColumn ) const
246{
247 QSet<int> includedColumns;
248 minColumn = std::numeric_limits<int>::max();
249 maxColumn = -1;
250 for ( const QModelIndex &index : list )
251 {
252 includedColumns.insert( index.column() );
253 minColumn = std::min( minColumn, index.column() );
254 maxColumn = std::max( maxColumn, index.column() );
255 }
256
257 // test that selection is consecutive columns
258 for ( int r = minColumn + 1; r < maxColumn; r++ )
259 {
260 if ( !includedColumns.contains( r ) )
261 return false;
262 }
263 return true;
264}
265
266QList<int> QgsTableEditorWidget::collectUniqueRows( const QModelIndexList &list ) const
267{
268 QList<int> res;
269 for ( const QModelIndex &index : list )
270 {
271 if ( !res.contains( index.row() ) )
272 res << index.row();
273 }
274 std::sort( res.begin(), res.end() );
275 return res;
276}
277
278QList<int> QgsTableEditorWidget::collectUniqueColumns( const QModelIndexList &list ) const
279{
280 QList<int> res;
281 for ( const QModelIndex &index : list )
282 {
283 if ( !res.contains( index.column() ) )
284 res << index.column();
285 }
286 std::sort( res.begin(), res.end() );
287 return res;
288}
289
290bool QgsTableEditorWidget::isRectangularSelection( const QModelIndexList &list ) const
291{
292 if ( list.empty() )
293 return false;
294
295 int minRow = -1;
296 int maxRow = -1;
297 int minCol = -1;
298 int maxCol = -1;
299 QSet<QPair<int, int>> selectedSet;
300 for ( const QModelIndex &index : list )
301 {
302 if ( minRow == -1 || index.row() < minRow )
303 minRow = index.row();
304 if ( maxRow == -1 || index.row() > maxRow )
305 maxRow = index.row();
306 if ( minCol == -1 || index.column() < minCol )
307 minCol = index.column();
308 if ( maxCol == -1 || index.column() > maxCol )
309 maxCol = index.column();
310 selectedSet.insert( qMakePair( index.row(), index.column() ) );
311 }
312
313 // check if the number of cells matches the expected rectangle size
314 if ( list.size() != ( maxRow - minRow + 1 ) * ( maxCol - minCol + 1 ) )
315 return false;
316
317 // check if all cells within the rectangle are selected
318 QSet<QPair<int, int>> expectedSet;
319 for ( int row = minRow; row <= maxRow; ++row )
320 {
321 for ( int col = minCol; col <= maxCol; ++col )
322 {
323 expectedSet.insert( qMakePair( row, col ) );
324 }
325 }
326 return selectedSet == expectedSet;
327}
328
329bool QgsTableEditorWidget::hasMergedCells( const QModelIndexList &list ) const
330{
331 for ( const QModelIndex &index : list )
332 {
333 if ( rowSpan( index.row(), index.column() ) > 1
334 || columnSpan( index.row(), index.column() ) > 1 )
335 return true;
336 }
337 return false;
338}
339
341{
342 switch ( event->key() )
343 {
344 case Qt::Key_Enter:
345 case Qt::Key_Return:
346 {
347 //Enter or return keys moves to next row
348 QTableWidget::keyPressEvent( event );
349 setCurrentCell( currentRow() + 1, currentColumn() );
350 break;
351 }
352
353 case Qt::Key_Delete:
354 {
356 break;
357 }
358
359 default:
360 QTableWidget::keyPressEvent( event );
361 }
362 if ( QgsTableEditorDelegate *d = qobject_cast<QgsTableEditorDelegate *>( itemDelegate() ) )
363 {
364 d->setWeakEditorMode( true );
365 }
366}
367
369{
370 mBlockSignals++;
371 qDeleteAll( mNumericFormats );
372 mNumericFormats.clear();
373
374 QgsNumericFormatContext numericContext;
375 int rowNumber = mIncludeHeader ? 1 : 0;
376 bool first = true;
377 setRowCount( contents.size() + rowNumber );
378 for ( const QgsTableRow &row : contents )
379 {
380 if ( first )
381 {
382 setColumnCount( row.size() );
383 first = false;
384 }
385
386 int colNumber = 0;
387 for ( const QgsTableCell &col : row )
388 {
389 QTableWidgetItem *item = new QTableWidgetItem( col.content().value<QgsProperty>().isActive() ? col.content().value<QgsProperty>().asExpression() : col.content().toString() );
390 item->setData( CellContent, col.content() ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
391 item->setData( Qt::BackgroundRole, col.backgroundColor().isValid() ? col.backgroundColor() : QColor( 255, 255, 255 ) );
392 item->setData( PresetBackgroundColorRole, col.backgroundColor().isValid() ? col.backgroundColor() : QVariant() );
393 item->setData( Qt::ForegroundRole, col.textFormat().isValid() ? col.textFormat().color() : QVariant() );
394 item->setData( TextFormat, QVariant::fromValue( col.textFormat() ) );
395 item->setData( HorizontalAlignment, static_cast<int>( col.horizontalAlignment() ) );
396 item->setData( VerticalAlignment, static_cast<int>( col.verticalAlignment() ) );
397 item->setData( CellProperty, QVariant::fromValue( col.content().value<QgsProperty>() ) );
398
399 if ( col.content().value<QgsProperty>().isActive() )
400 item->setFlags( item->flags() & ( ~Qt::ItemIsEditable ) );
401
402 if ( auto *lNumericFormat = col.numericFormat() )
403 {
404 mNumericFormats.insert( item, lNumericFormat->clone() );
405 item->setData( Qt::DisplayRole, mNumericFormats.value( item )->formatDouble( col.content().toDouble(), numericContext ) );
406 }
407 setItem( rowNumber, colNumber, item );
408
409 if ( col.rowSpan() > 1 || col.columnSpan() > 1 )
410 setSpan( rowNumber, colNumber, col.rowSpan(), col.columnSpan() );
411
412 colNumber++;
413 }
414 rowNumber++;
415 }
416
417 mBlockSignals--;
418 updateHeaders();
419
420 if ( mFirstSet )
421 {
422 resizeColumnsToContents();
423 resizeRowsToContents();
424 mFirstSet = false;
425 }
426 emit tableChanged();
427}
428
430{
431 QgsTableContents items;
432 items.reserve( rowCount() );
433
434 QSet<QPair<int, int>> spannedCells;
435 for ( int r = mIncludeHeader ? 1 : 0; r < rowCount(); r++ )
436 {
437 QgsTableRow row;
438 row.reserve( columnCount() );
439 for ( int c = 0; c < columnCount(); c++ )
440 {
441 QgsTableCell cell;
442 if ( QTableWidgetItem *i = item( r, c ) )
443 {
444 cell.setContent( i->data( CellProperty ).value<QgsProperty>().isActive() ? i->data( CellProperty ) : i->data( CellContent ) );
445 cell.setBackgroundColor( i->data( PresetBackgroundColorRole ).value<QColor>() );
446 cell.setTextFormat( i->data( TextFormat ).value<QgsTextFormat>() );
447 cell.setHorizontalAlignment( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) );
448 cell.setVerticalAlignment( static_cast<Qt::Alignment>( i->data( VerticalAlignment ).toInt() ) );
449
450 // we only want to set row/col span > 1 for the left/top most cells in a span:
451 if ( !spannedCells.contains( qMakePair( r, c ) ) )
452 {
453 const int rowsSpan = rowSpan( r, c );
454 const int colsSpan = columnSpan( r, c );
455 for ( int spannedRow = r; spannedRow < r + rowsSpan; ++spannedRow )
456 {
457 for ( int spannedCol = c; spannedCol < c + colsSpan; ++spannedCol )
458 {
459 spannedCells.insert( qMakePair( spannedRow, spannedCol ) );
460 }
461 }
462 cell.setSpan( rowSpan( r, c ), columnSpan( r, c ) );
463 }
464 else
465 {
466 cell.setSpan( 1, 1 );
467 }
468
469 if ( mNumericFormats.value( i ) )
470 {
471 cell.setNumericFormat( mNumericFormats.value( i )->clone() );
472 }
473 }
474 row.push_back( cell );
475 }
476 items.push_back( row );
477 }
478
479 return items;
480}
481
483{
484 bool changed = false;
485 mBlockSignals++;
486 std::unique_ptr<QgsNumericFormat> newFormat( format );
487 const QModelIndexList selection = selectedIndexes();
488 QgsNumericFormatContext numericContext;
489 for ( const QModelIndex &index : selection )
490 {
491 if ( index.row() == 0 && mIncludeHeader )
492 continue;
493
494 QTableWidgetItem *i = item( index.row(), index.column() );
495 if ( !i )
496 {
497 i = new QTableWidgetItem();
498 setItem( index.row(), index.column(), i );
499 }
500 if ( !mNumericFormats.value( i ) && newFormat )
501 {
502 changed = true;
503 mNumericFormats.insert( i, newFormat->clone() );
504 }
505 else if ( mNumericFormats.value( i ) && !newFormat )
506 {
507 changed = true;
508 delete mNumericFormats.value( i );
509 mNumericFormats.remove( i );
510 }
511 else if ( newFormat && *newFormat != *mNumericFormats.value( i ) )
512 {
513 changed = true;
514 delete mNumericFormats.value( i );
515 mNumericFormats.insert( i, newFormat->clone() );
516 }
517 i->setData( Qt::DisplayRole, newFormat ? mNumericFormats.value( i )->formatDouble( i->data( CellContent ).toDouble(), numericContext ) : i->data( CellContent ) );
518 }
519 mBlockSignals--;
520 if ( changed && !mBlockSignals )
521 emit tableChanged();
522}
523
525{
526 QgsNumericFormat *f = nullptr;
527 bool first = true;
528 const QModelIndexList selection = selectedIndexes();
529 for ( const QModelIndex &index : selection )
530 {
531 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
532 {
533 if ( first )
534 {
535 f = mNumericFormats.value( i );
536 first = false;
537 }
538 else if ( ( !f && !mNumericFormats.value( i ) )
539 || ( f && mNumericFormats.value( i ) && *f == *mNumericFormats.value( i ) ) )
540 continue;
541 else
542 {
543 return nullptr;
544 }
545 }
546 else
547 {
548 return nullptr;
549 }
550 }
551 return f;
552}
553
555{
556 QgsNumericFormat *f = nullptr;
557 bool first = true;
558 const QModelIndexList selection = selectedIndexes();
559 for ( const QModelIndex &index : selection )
560 {
561 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
562 {
563 if ( first )
564 {
565 f = mNumericFormats.value( i );
566 first = false;
567 }
568 else if ( ( !f && !mNumericFormats.value( i ) )
569 || ( 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 = 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
868 && isRectangularSelection( selectedIndexes() );
869}
870
872{
873 return !selectedIndexes().empty()
875 && hasMergedCells( selectedIndexes() );
876}
877
879{
880 if ( rowCount() == 0 )
881 {
882 insertRow( 0 );
883 return;
884 }
885
886 int minRow = 0;
887 int maxRow = 0;
888 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
889 return;
890
891 const int rowsToInsert = maxRow - minRow + 1;
892 for ( int i = 0; i < rowsToInsert; ++i )
893 insertRow( maxRow + 1 );
894
895 updateHeaders();
896 if ( !mBlockSignals )
897 emit tableChanged();
898}
899
901{
902 if ( rowCount() == 0 )
903 {
904 insertRow( 0 );
905 return;
906 }
907
908 int minRow = 0;
909 int maxRow = 0;
910 if ( !collectConsecutiveRowRange( selectedIndexes(), minRow, maxRow ) )
911 return;
912
913 const int rowsToInsert = maxRow - minRow + 1;
914 for ( int i = 0; i < rowsToInsert; ++i )
915 insertRow( minRow );
916
917 updateHeaders();
918 if ( !mBlockSignals )
919 emit tableChanged();
920}
921
923{
924 if ( columnCount() == 0 )
925 {
926 insertColumn( 0 );
927 return;
928 }
929
930 int minColumn = 0;
931 int maxColumn = 0;
932 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
933 return;
934
935 const int columnsToInsert = maxColumn - minColumn + 1;
936 for ( int i = 0; i < columnsToInsert; ++i )
937 insertColumn( minColumn );
938
939 updateHeaders();
940 if ( !mBlockSignals )
941 emit tableChanged();
942}
943
945{
946 if ( columnCount() == 0 )
947 {
948 insertColumn( 0 );
949 return;
950 }
951
952 int minColumn = 0;
953 int maxColumn = 0;
954 if ( !collectConsecutiveColumnRange( selectedIndexes(), minColumn, maxColumn ) )
955 return;
956
957 const int columnsToInsert = maxColumn - minColumn + 1;
958 for ( int i = 0; i < columnsToInsert; ++i )
959 insertColumn( maxColumn + 1 );
960
961 updateHeaders();
962 if ( !mBlockSignals )
963 emit tableChanged();
964}
965
967{
968 const QList<int> rows = rowsAssociatedWithSelection();
969 if ( rows.empty() )
970 return;
971
972 bool changed = false;
973 for ( int i = rows.size() - 1; i >= 0 && rowCount() > 1; i-- )
974 {
975 removeRow( rows.at( i ) );
976 changed = true;
977 }
978 updateHeaders();
979 if ( changed && !mBlockSignals )
980 emit tableChanged();
981}
982
984{
985 const QList<int> columns = columnsAssociatedWithSelection();
986 if ( columns.empty() )
987 return;
988
989 bool changed = false;
990 for ( int i = columns.size() - 1; i >= 0 && columnCount() > 1; i-- )
991 {
992 removeColumn( columns.at( i ) );
993 changed = true;
994 }
995 updateHeaders();
996 if ( !mBlockSignals && changed )
997 emit tableChanged();
998}
999
1001{
1002 const QModelIndexList s = selectedIndexes();
1003 for ( const QModelIndex &index : s )
1004 {
1005 selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::Select );
1006 }
1007}
1008
1010{
1011 const QModelIndexList s = selectedIndexes();
1012 for ( const QModelIndex &index : s )
1013 {
1014 selectionModel()->select( index, QItemSelectionModel::Columns | QItemSelectionModel::Select );
1015 }
1016}
1017
1019{
1020 const QModelIndexList selection = selectedIndexes();
1021 bool changed = false;
1022 mBlockSignals++;
1023 for ( const QModelIndex &index : selection )
1024 {
1025 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1026 {
1027 i->setText( QString() );
1028 i->setData( CellContent, QVariant() );
1029 changed = true;
1030 }
1031 }
1032 mBlockSignals--;
1033 if ( changed && !mBlockSignals )
1034 emit tableChanged();
1035}
1036
1038{
1039 const QModelIndexList selection = selectedIndexes();
1040 bool changed = false;
1041 mBlockSignals++;
1042 for ( const QModelIndex &index : selection )
1043 {
1044 if ( index.row() == 0 && mIncludeHeader )
1045 continue;
1046
1047 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1048 {
1049 if ( i->data( Qt::ForegroundRole ).value<QColor>() != color )
1050 {
1051 i->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1052 QgsTextFormat f = i->data( TextFormat ).value<QgsTextFormat>();
1053 f.setColor( color );
1054 i->setData( TextFormat, QVariant::fromValue( f ) );
1055 changed = true;
1056 }
1057 }
1058 else
1059 {
1060 QTableWidgetItem *newItem = new QTableWidgetItem();
1061 newItem->setData( Qt::ForegroundRole, color.isValid() ? color : QVariant() );
1062 QgsTextFormat f;
1063 f.setColor( color );
1064 newItem->setData( TextFormat, QVariant::fromValue( f ) );
1065 setItem( index.row(), index.column(), newItem );
1066 changed = true;
1067 }
1068 }
1069 mBlockSignals--;
1070 if ( changed && !mBlockSignals )
1071 emit tableChanged();
1072}
1073
1075{
1076 const QModelIndexList selection = selectedIndexes();
1077 bool changed = false;
1078 mBlockSignals++;
1079 for ( const QModelIndex &index : selection )
1080 {
1081 if ( index.row() == 0 && mIncludeHeader )
1082 continue;
1083
1084 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1085 {
1086 if ( i->data( PresetBackgroundColorRole ).value<QColor>() != color )
1087 {
1088 i->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1089 i->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1090 changed = true;
1091 }
1092 }
1093 else
1094 {
1095 QTableWidgetItem *newItem = new QTableWidgetItem();
1096 newItem->setData( Qt::BackgroundRole, color.isValid() ? color : QVariant() );
1097 newItem->setData( PresetBackgroundColorRole, color.isValid() ? color : QVariant() );
1098 setItem( index.row(), index.column(), newItem );
1099 changed = true;
1100 }
1101 }
1102 mBlockSignals--;
1103 if ( changed && !mBlockSignals )
1104 emit tableChanged();
1105}
1106
1108{
1109 const QModelIndexList selection = selectedIndexes();
1110 bool changed = false;
1111 mBlockSignals++;
1112 for ( const QModelIndex &index : selection )
1113 {
1114 if ( index.row() == 0 && mIncludeHeader )
1115 continue;
1116
1117 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1118 {
1119 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1120 {
1121 i->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1122 changed = true;
1123 }
1124 }
1125 else
1126 {
1127 QTableWidgetItem *newItem = new QTableWidgetItem();
1128 newItem->setData( HorizontalAlignment, static_cast<int>( alignment ) );
1129 setItem( index.row(), index.column(), newItem );
1130 changed = true;
1131 }
1132 }
1133 mBlockSignals--;
1134 if ( changed && !mBlockSignals )
1135 emit tableChanged();
1136}
1137
1139{
1140 const QModelIndexList selection = selectedIndexes();
1141 bool changed = false;
1142 mBlockSignals++;
1143 for ( const QModelIndex &index : selection )
1144 {
1145 if ( index.row() == 0 && mIncludeHeader )
1146 continue;
1147
1148 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1149 {
1150 if ( static_cast<Qt::Alignment>( i->data( HorizontalAlignment ).toInt() ) != alignment )
1151 {
1152 i->setData( VerticalAlignment, static_cast<int>( alignment ) );
1153 changed = true;
1154 }
1155 }
1156 else
1157 {
1158 QTableWidgetItem *newItem = new QTableWidgetItem();
1159 newItem->setData( VerticalAlignment, static_cast<int>( alignment ) );
1160 setItem( index.row(), index.column(), newItem );
1161 changed = true;
1162 }
1163 }
1164 mBlockSignals--;
1165 if ( changed && !mBlockSignals )
1166 emit tableChanged();
1167}
1168
1170{
1171 const QModelIndexList selection = selectedIndexes();
1172 bool changed = false;
1173 mBlockSignals++;
1174 for ( const QModelIndex &index : selection )
1175 {
1176 if ( index.row() == 0 && mIncludeHeader )
1177 continue;
1178
1179 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1180 {
1181 if ( i->data( CellProperty ).value<QgsProperty>() != property )
1182 {
1183 if ( property.isActive() )
1184 {
1185 i->setData( CellProperty, QVariant::fromValue( property ) );
1186 i->setText( property.asExpression() );
1187 i->setFlags( i->flags() & ( ~Qt::ItemIsEditable ) );
1188 }
1189 else
1190 {
1191 i->setData( CellProperty, QVariant() );
1192 i->setText( QString() );
1193 i->setFlags( i->flags() | Qt::ItemIsEditable );
1194 }
1195 changed = true;
1196 }
1197 }
1198 else
1199 {
1200 QTableWidgetItem *newItem = new QTableWidgetItem( property.asExpression() );
1201 if ( property.isActive() )
1202 {
1203 newItem->setData( CellProperty, QVariant::fromValue( property ) );
1204 newItem->setFlags( newItem->flags() & ( ~Qt::ItemIsEditable ) );
1205 }
1206 else
1207 {
1208 newItem->setData( CellProperty, QVariant() );
1209 newItem->setFlags( newItem->flags() | Qt::ItemIsEditable );
1210 }
1211 setItem( index.row(), index.column(), newItem );
1212 changed = true;
1213 }
1214 }
1215 mBlockSignals--;
1216 if ( changed && !mBlockSignals )
1217 emit tableChanged();
1218}
1219
1221{
1222 const QModelIndexList selection = selectedIndexes();
1223 bool changed = false;
1224 mBlockSignals++;
1225 for ( const QModelIndex &index : selection )
1226 {
1227 if ( index.row() == 0 && mIncludeHeader )
1228 continue;
1229
1230 if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
1231 {
1232 i->setData( TextFormat, QVariant::fromValue( format ) );
1233 i->setData( Qt::ForegroundRole, format.color() );
1234 changed = true;
1235 }
1236 else
1237 {
1238 QTableWidgetItem *newItem = new QTableWidgetItem();
1239 newItem->setData( TextFormat, QVariant::fromValue( format ) );
1240 newItem->setData( Qt::ForegroundRole, format.color() );
1241 setItem( index.row(), index.column(), newItem );
1242 changed = true;
1243 }
1244 }
1245 mBlockSignals--;
1246 if ( changed && !mBlockSignals )
1247 emit tableChanged();
1248}
1249
1251{
1252 bool changed = false;
1253 mBlockSignals++;
1254 const QList<int> rows = rowsAssociatedWithSelection();
1255 for ( int row : rows )
1256 {
1257 if ( row == 0 && mIncludeHeader )
1258 continue;
1259
1260 for ( int col = 0; col < columnCount(); ++col )
1261 {
1262 if ( QTableWidgetItem *i = item( row, col ) )
1263 {
1264 if ( i->data( RowHeight ).toDouble() != height )
1265 {
1266 i->setData( RowHeight, height );
1267 changed = true;
1268 }
1269 }
1270 else
1271 {
1272 QTableWidgetItem *newItem = new QTableWidgetItem();
1273 newItem->setData( RowHeight, height );
1274 setItem( row, col, newItem );
1275 changed = true;
1276 }
1277 }
1278 }
1279 mBlockSignals--;
1280 if ( changed && !mBlockSignals )
1281 emit tableChanged();
1282}
1283
1285{
1286 bool changed = false;
1287 mBlockSignals++;
1288 const QList<int> cols = columnsAssociatedWithSelection();
1289 for ( int col : cols )
1290 {
1291 for ( int row = 0; row < rowCount(); ++row )
1292 {
1293 if ( QTableWidgetItem *i = item( row, col ) )
1294 {
1295 if ( i->data( ColumnWidth ).toDouble() != width )
1296 {
1297 i->setData( ColumnWidth, width );
1298 changed = true;
1299 }
1300 }
1301 else
1302 {
1303 QTableWidgetItem *newItem = new QTableWidgetItem();
1304 newItem->setData( ColumnWidth, width );
1305 setItem( row, col, newItem );
1306 changed = true;
1307 }
1308 }
1309 }
1310 mBlockSignals--;
1311 if ( changed && !mBlockSignals )
1312 emit tableChanged();
1313}
1314
1316{
1317 if ( included == mIncludeHeader )
1318 return;
1319
1320 mIncludeHeader = included;
1321
1322 if ( mIncludeHeader )
1323 insertRow( 0 );
1324 else
1325 removeRow( 0 );
1326 updateHeaders();
1327}
1328
1329void QgsTableEditorWidget::setTableHeaders( const QVariantList &headers )
1330{
1331 if ( !mIncludeHeader )
1332 return;
1333
1334 mBlockSignals++;
1335
1336 for ( int col = 0; col < columnCount(); ++col )
1337 {
1338 if ( QTableWidgetItem *i = item( 0, col ) )
1339 {
1340 i->setText( headers.value( col ).toString() );
1341 i->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1342 }
1343 else
1344 {
1345 QTableWidgetItem *item = new QTableWidgetItem( headers.value( col ).toString() );
1346 item->setData( CellContent, headers.value( col ) ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
1347 setItem( 0, col, item );
1348 }
1349 }
1350 mBlockSignals--;
1351}
1352
1354{
1355 const QModelIndexList selection = selectedIndexes();
1356 if ( selection.size() < 2 )
1357 return;
1358
1359 int minRow = -1;
1360 int maxRow = -1;
1361 int minCol = -1;
1362 int maxCol = -1;
1363 for ( const QModelIndex &index : selection )
1364 {
1365 if ( minRow == -1 || index.row() < minRow )
1366 minRow = index.row();
1367 if ( maxRow == -1 || index.row() > maxRow )
1368 maxRow = index.row();
1369 if ( minCol == -1 || index.column() < minCol )
1370 minCol = index.column();
1371 if ( maxCol == -1 || index.column() > maxCol )
1372 maxCol = index.column();
1373 }
1374 QStringList mergedCellText;
1375 for ( int row = minRow; row <= maxRow; ++row )
1376 {
1377 for ( int col = minCol; col <= maxCol; ++col )
1378 {
1379 if ( QTableWidgetItem *i = item( row, col ) )
1380 {
1381 const QString content = i->data( CellContent ).toString();
1382 if ( !content.isEmpty() )
1383 {
1384 mergedCellText.append( content );
1385 }
1386 }
1387 }
1388 }
1389
1390 setSpan( minRow, minCol, maxRow - minRow + 1, maxCol - minCol + 1 );
1391
1392 // set merged cell text to concatenate original cell text
1393 if ( !mergedCellText.isEmpty() )
1394 {
1395 if ( QTableWidgetItem *i = item( minRow, minCol ) )
1396 {
1397 i->setText( mergedCellText.join( ' ' ) );
1398 i->setData( CellContent, i->text() );
1399 }
1400 }
1401
1402 if ( !mBlockSignals )
1403 emit tableChanged();
1404}
1405
1407{
1408 const QModelIndexList selection = selectedIndexes();
1409 for ( const QModelIndex &index : selection )
1410 {
1411 if ( rowSpan( index.row(), index.column() ) > 1 || columnSpan( index.row(), index.column() ) > 1 )
1412 setSpan( index.row(), index.column(), 1, 1 );
1413 }
1414
1415 if ( !mBlockSignals )
1416 emit tableChanged();
1417}
1418
1420
1421QgsTableEditorTextEdit::QgsTableEditorTextEdit( QWidget *parent )
1422 : QPlainTextEdit( parent )
1423{
1424 // narrower default margins
1425 document()->setDocumentMargin( document()->documentMargin() / 2 );
1426
1427 connect( this, &QPlainTextEdit::textChanged, this, &QgsTableEditorTextEdit::resizeToContents );
1428 updateMinimumSize();
1429}
1430
1431void QgsTableEditorTextEdit::keyPressEvent( QKeyEvent *event )
1432{
1433 switch ( event->key() )
1434 {
1435 case Qt::Key_Enter:
1436 case Qt::Key_Return:
1437 {
1438 if ( event->modifiers() & Qt::ControlModifier )
1439 {
1440 // ctrl+enter inserts a line break
1441 insertPlainText( QString( '\n' ) );
1442 resizeToContents();
1443 }
1444 else
1445 {
1446 // closes editor
1447 event->ignore();
1448 }
1449 break;
1450 }
1451
1452 case Qt::Key_Right:
1453 case Qt::Key_Left:
1454 case Qt::Key_Up:
1455 case Qt::Key_Down:
1456 {
1457 if ( mWeakEditorMode )
1458 {
1459 // close editor and defer to table
1460 event->ignore();
1461 }
1462 else
1463 {
1464 QPlainTextEdit::keyPressEvent( event );
1465 }
1466 break;
1467 }
1468
1469 case Qt::Key_Tab:
1470 {
1471 if ( event->modifiers() & Qt::ControlModifier )
1472 {
1473 // if tab is pressed then defer to table, unless ctrl modifier is also held
1474 // (emulate spreadsheet behavior)
1475 insertPlainText( QString( '\t' ) );
1476 resizeToContents();
1477 }
1478 else
1479 {
1480 event->ignore();
1481 }
1482 break;
1483 }
1484
1485 default:
1486 QPlainTextEdit::keyPressEvent( event );
1487 }
1488}
1489
1490void QgsTableEditorTextEdit::updateMinimumSize()
1491{
1492 const double tm = document()->documentMargin();
1493 const QMargins cm = contentsMargins();
1494 const int width = tm * 2 + cm.left() + cm.right() + 30;
1495 const int height = tm * 2 + cm.top() + cm.bottom() + 4;
1496 QStyleOptionFrame opt;
1497 initStyleOption( &opt );
1498 const QSize sizeFromContent = style()->sizeFromContents( QStyle::CT_LineEdit, &opt, QSize( width, height ), this );
1499 setMinimumWidth( sizeFromContent.width() );
1500 setMinimumHeight( sizeFromContent.height() );
1501}
1502
1503void QgsTableEditorTextEdit::setWeakEditorMode( bool weakEditorMode )
1504{
1505 mWeakEditorMode = weakEditorMode;
1506}
1507
1508void QgsTableEditorTextEdit::resizeToContents()
1509{
1510 int oldWidth = width();
1511 int oldHeight = height();
1512 if ( mOriginalWidth == -1 )
1513 mOriginalWidth = oldWidth;
1514 if ( mOriginalHeight == -1 )
1515 mOriginalHeight = oldHeight;
1516
1517 if ( QWidget *parent = parentWidget() )
1518 {
1519 QPoint position = pos();
1520 QFontMetrics fm( font() );
1521
1522 const QStringList lines = toPlainText().split( '\n' );
1523 int maxTextLineWidth = 0;
1524 int totalTextHeight = 0;
1525 for ( const QString &line : lines )
1526 {
1527 const QRect bounds = fontMetrics().boundingRect( line );
1528 maxTextLineWidth = std::max( maxTextLineWidth, bounds.width() );
1529 totalTextHeight += fm.height();
1530 }
1531
1532 int hintWidth = minimumWidth() + maxTextLineWidth;
1533 int hintHeight = minimumHeight() + totalTextHeight;
1534 int parentWidth = parent->width();
1535 int maxWidth = isRightToLeft() ? position.x() + oldWidth : parentWidth - position.x();
1536 int maxHeight = parent->height() - position.y();
1537 int newWidth = std::clamp( hintWidth, mOriginalWidth, maxWidth );
1538 int newHeight = std::clamp( hintHeight, mOriginalHeight, maxHeight );
1539
1540 if ( mWidgetOwnsGeometry )
1541 {
1542 setMaximumWidth( newWidth );
1543 setMaximumHeight( newHeight );
1544 }
1545 if ( isRightToLeft() )
1546 move( position.x() - newWidth + oldWidth, position.y() );
1547 resize( newWidth, newHeight );
1548 }
1549}
1550
1551void QgsTableEditorTextEdit::changeEvent( QEvent *e )
1552{
1553 switch ( e->type() )
1554 {
1555 case QEvent::FontChange:
1556 case QEvent::StyleChange:
1557 case QEvent::ContentsRectChange:
1558 updateMinimumSize();
1559 break;
1560 default:
1561 break;
1562 }
1563 QPlainTextEdit::changeEvent( e );
1564}
1565
1566QgsTableEditorDelegate::QgsTableEditorDelegate( QObject *parent )
1567 : QStyledItemDelegate( parent )
1568{
1569}
1570
1571void QgsTableEditorDelegate::setWeakEditorMode( bool weakEditorMode )
1572{
1573 mWeakEditorMode = weakEditorMode;
1574}
1575
1576QWidget *QgsTableEditorDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const
1577{
1578 QgsTableEditorTextEdit *w = new QgsTableEditorTextEdit( parent );
1579 w->setWeakEditorMode( mWeakEditorMode );
1580
1581 if ( !w->style()->styleHint( QStyle::SH_ItemView_DrawDelegateFrame, 0, w ) )
1582 w->setFrameShape( QFrame::NoFrame );
1583 if ( !w->style()->styleHint( QStyle::SH_ItemView_ShowDecorationSelected, 0, w ) )
1584 w->setWidgetOwnsGeometry( true );
1585
1586 return w;
1587}
1588
1589void QgsTableEditorDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
1590{
1591 QVariant value = index.model()->data( index, QgsTableEditorWidget::CellContent );
1592 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1593 {
1594 if ( index != mLastIndex || lineEdit->toPlainText() != value.toString() )
1595 {
1596 lineEdit->setPlainText( value.toString() );
1597 lineEdit->selectAll();
1598 }
1599 }
1600 mLastIndex = index;
1601}
1602
1603void QgsTableEditorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
1604{
1605 if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit *>( editor ) )
1606 {
1607 const QString text = lineEdit->toPlainText();
1608 if ( text != model->data( index, QgsTableEditorWidget::CellContent ).toString() && !model->data( index, QgsTableEditorWidget::CellProperty ).value<QgsProperty>().isActive() )
1609 {
1610 model->setData( index, text, QgsTableEditorWidget::CellContent );
1611 model->setData( index, text, Qt::DisplayRole );
1612 emit updateNumericFormatForIndex( index );
1613 }
1614 }
1615}
1616
1617
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.