1 /***************************************************************************
2     qgstableeditordialog.cpp
3     ------------------------
4     begin                : January 2020
5     copyright            : (C) 2020 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgstableeditordialog.h"
17 #include "qgstableeditorwidget.h"
18 #include "qgsmessagebar.h"
19 #include "qgsgui.h"
20 #include "qgsdockwidget.h"
21 #include "qgspanelwidgetstack.h"
22 #include "qgstableeditorformattingwidget.h"
23 #include "qgssettings.h"
24 
25 #include <QClipboard>
26 #include <QMessageBox>
27 
QgsTableEditorDialog(QWidget * parent)28 QgsTableEditorDialog::QgsTableEditorDialog( QWidget *parent )
29   : QMainWindow( parent )
30 {
31   setupUi( this );
32   setWindowTitle( tr( "Table Designer" ) );
33 
34   setAttribute( Qt::WA_DeleteOnClose );
35   setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
36 
37   QgsGui::enableAutoGeometryRestore( this );
38 
39   QGridLayout *viewLayout = new QGridLayout();
40   viewLayout->setSpacing( 0 );
41   viewLayout->setContentsMargins( 0, 0, 0, 0 );
42   centralWidget()->layout()->setSpacing( 0 );
43   centralWidget()->layout()->setContentsMargins( 0, 0, 0, 0 );
44 
45   mMessageBar = new QgsMessageBar( centralWidget() );
46   mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
47   static_cast< QGridLayout * >( centralWidget()->layout() )->addWidget( mMessageBar, 0, 0, 1, 1, Qt::AlignTop );
48 
49   mTableWidget = new QgsTableEditorWidget();
50   mTableWidget->setContentsMargins( 0, 0, 0, 0 );
51   viewLayout->addWidget( mTableWidget, 0, 0 );
52   mViewFrame->setLayout( viewLayout );
53   mViewFrame->setContentsMargins( 0, 0, 0, 0 );
54 
55   mTableWidget->setFocus();
56   mTableWidget->setTableContents( QgsTableContents() << ( QgsTableRow() << QgsTableCell() ) );
57 
58   connect( mTableWidget, &QgsTableEditorWidget::tableChanged, this, [ = ]
59   {
60     if ( !mBlockSignals )
61       emit tableChanged();
62   } );
63 
64   const int minDockWidth( fontMetrics().boundingRect( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ).width() );
65 
66   mPropertiesDock = new QgsDockWidget( tr( "Cell Contents" ), this );
67   mPropertiesDock->setObjectName( QStringLiteral( "FormattingDock" ) );
68   mPropertiesStack = new QgsPanelWidgetStack();
69   mPropertiesDock->setWidget( mPropertiesStack );
70   mPropertiesDock->setMinimumWidth( minDockWidth );
71 
72   mFormattingWidget = new QgsTableEditorFormattingWidget();
73   mFormattingWidget->setDockMode( true );
74   mPropertiesStack->setMainPanel( mFormattingWidget );
75 
76   mPropertiesDock->setFeatures( QDockWidget::NoDockWidgetFeatures );
77 
78   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::backgroundColorChanged, mTableWidget, &QgsTableEditorWidget::setSelectionBackgroundColor );
79 
80   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::horizontalAlignmentChanged, mTableWidget, &QgsTableEditorWidget::setSelectionHorizontalAlignment );
81   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::verticalAlignmentChanged, mTableWidget, &QgsTableEditorWidget::setSelectionVerticalAlignment );
82   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::cellPropertyChanged, mTableWidget, &QgsTableEditorWidget::setSelectionCellProperty );
83 
84   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::textFormatChanged, this, [ = ]
85   {
86     mTableWidget->setSelectionTextFormat( mFormattingWidget->textFormat() );
87   } );
88 
89   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::numberFormatChanged, this, [ = ]
90   {
91     mTableWidget->setSelectionNumericFormat( mFormattingWidget->numericFormat() );
92   } );
93   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::rowHeightChanged, mTableWidget, &QgsTableEditorWidget::setSelectionRowHeight );
94   connect( mFormattingWidget, &QgsTableEditorFormattingWidget::columnWidthChanged, mTableWidget, &QgsTableEditorWidget::setSelectionColumnWidth );
95 
96   connect( mTableWidget, &QgsTableEditorWidget::activeCellChanged, this, [ = ]
97   {
98     mFormattingWidget->setBackgroundColor( mTableWidget->selectionBackgroundColor() );
99     mFormattingWidget->setNumericFormat( mTableWidget->selectionNumericFormat(), mTableWidget->hasMixedSelectionNumericFormat() );
100     mFormattingWidget->setRowHeight( mTableWidget->selectionRowHeight() );
101     mFormattingWidget->setColumnWidth( mTableWidget->selectionColumnWidth() );
102     mFormattingWidget->setTextFormat( mTableWidget->selectionTextFormat() );
103     mFormattingWidget->setHorizontalAlignment( mTableWidget->selectionHorizontalAlignment() );
104     mFormattingWidget->setVerticalAlignment( mTableWidget->selectionVerticalAlignment() );
105     mFormattingWidget->setCellProperty( mTableWidget->selectionCellProperty() );
106 
107     updateActionNamesFromSelection();
108 
109     mFormattingWidget->setEnabled( !mTableWidget->isHeaderCellSelected() );
110   } );
111   updateActionNamesFromSelection();
112 
113   addDockWidget( Qt::RightDockWidgetArea, mPropertiesDock );
114 
115   mActionImportFromClipboard->setEnabled( !QApplication::clipboard()->text().isEmpty() );
116   connect( QApplication::clipboard(), &QClipboard::dataChanged, this, [ = ]() { mActionImportFromClipboard->setEnabled( !QApplication::clipboard()->text().isEmpty() ); } );
117 
118   connect( mActionImportFromClipboard, &QAction::triggered, this, &QgsTableEditorDialog::setTableContentsFromClipboard );
119   connect( mActionClose, &QAction::triggered, this, &QMainWindow::close );
120   connect( mActionInsertRowsAbove, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::insertRowsAbove );
121   connect( mActionInsertRowsBelow, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::insertRowsBelow );
122   connect( mActionInsertColumnsBefore, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::insertColumnsBefore );
123   connect( mActionInsertColumnsAfter, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::insertColumnsAfter );
124   connect( mActionDeleteRows, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::deleteRows );
125   connect( mActionDeleteColumns, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::deleteColumns );
126   connect( mActionSelectRow, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::expandRowSelection );
127   connect( mActionSelectColumn, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::expandColumnSelection );
128   connect( mActionSelectAll, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::selectAll );
129   connect( mActionClear, &QAction::triggered, mTableWidget, &QgsTableEditorWidget::clearSelectedCells );
130   connect( mActionIncludeHeader, &QAction::toggled, this, [ = ]( bool checked )
131   {
132     mTableWidget->setIncludeTableHeader( checked );
133     emit includeHeaderChanged( checked );
134   } );
135 
136   // restore the toolbar and dock widgets positions using Qt settings API
137   const QgsSettings settings;
138 
139   const QByteArray state = settings.value( QStringLiteral( "LayoutDesigner/tableEditorState" ), QByteArray(), QgsSettings::App ).toByteArray();
140   if ( !state.isEmpty() && !restoreState( state ) )
141   {
142     QgsDebugMsg( QStringLiteral( "restore of table editor dialog UI state failed" ) );
143   }
144 }
145 
closeEvent(QCloseEvent *)146 void QgsTableEditorDialog::closeEvent( QCloseEvent * )
147 {
148   QgsSettings settings;
149   // store the toolbar/dock widget settings using Qt settings API
150   settings.setValue( QStringLiteral( "LayoutDesigner/tableEditorState" ), saveState(), QgsSettings::App );
151 }
152 
setTableContentsFromClipboard()153 bool QgsTableEditorDialog::setTableContentsFromClipboard()
154 {
155   if ( QApplication::clipboard()->text().isEmpty() )
156     return false;
157 
158   if ( QMessageBox::question( this, tr( "Import Content From Clipboard" ),
159                               tr( "Importing content from clipboard will overwrite current table content. Are you sure?" ) ) != QMessageBox::Yes )
160     return false;
161 
162   QgsTableContents contents;
163   const QStringList lines = QApplication::clipboard()->text().split( '\n' );
164   for ( const QString &line : lines )
165   {
166     if ( !line.isEmpty() )
167     {
168       QgsTableRow row;
169       const QStringList cells = line.split( '\t' );
170       for ( const QString &text : cells )
171       {
172         const QgsTableCell cell( text );
173         row << cell;
174       }
175       contents << row;
176     }
177   }
178 
179   if ( !contents.isEmpty() )
180   {
181     setTableContents( contents );
182     emit tableChanged();
183     return true;
184   }
185 
186   return false;
187 }
188 
setTableContents(const QgsTableContents & contents)189 void QgsTableEditorDialog::setTableContents( const QgsTableContents &contents )
190 {
191   mBlockSignals = true;
192   mTableWidget->setTableContents( contents );
193   mTableWidget->resizeRowsToContents();
194   mTableWidget->resizeColumnsToContents();
195   mBlockSignals = false;
196 }
197 
tableContents() const198 QgsTableContents QgsTableEditorDialog::tableContents() const
199 {
200   return mTableWidget->tableContents();
201 }
202 
tableRowHeight(int row)203 double QgsTableEditorDialog::tableRowHeight( int row )
204 {
205   return mTableWidget->tableRowHeight( row );
206 }
207 
tableColumnWidth(int column)208 double QgsTableEditorDialog::tableColumnWidth( int column )
209 {
210   return mTableWidget->tableColumnWidth( column );
211 }
212 
setTableRowHeight(int row,double height)213 void QgsTableEditorDialog::setTableRowHeight( int row, double height )
214 {
215   mTableWidget->setTableRowHeight( row, height );
216 }
217 
setTableColumnWidth(int column,double width)218 void QgsTableEditorDialog::setTableColumnWidth( int column, double width )
219 {
220   mTableWidget->setTableColumnWidth( column, width );
221 }
222 
includeTableHeader() const223 bool QgsTableEditorDialog::includeTableHeader() const
224 {
225   return mActionIncludeHeader->isChecked();
226 }
227 
setIncludeTableHeader(bool included)228 void QgsTableEditorDialog::setIncludeTableHeader( bool included )
229 {
230   mActionIncludeHeader->setChecked( included );
231 }
232 
tableHeaders() const233 QVariantList QgsTableEditorDialog::tableHeaders() const
234 {
235   return mTableWidget->tableHeaders();
236 }
237 
setTableHeaders(const QVariantList & headers)238 void QgsTableEditorDialog::setTableHeaders( const QVariantList &headers )
239 {
240   mTableWidget->setTableHeaders( headers );
241 }
242 
registerExpressionContextGenerator(QgsExpressionContextGenerator * generator)243 void QgsTableEditorDialog::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator )
244 {
245   mFormattingWidget->registerExpressionContextGenerator( generator );
246 }
247 
updateActionNamesFromSelection()248 void QgsTableEditorDialog::updateActionNamesFromSelection()
249 {
250   const int rowCount = mTableWidget->rowsAssociatedWithSelection().size();
251   const int columnCount = mTableWidget->columnsAssociatedWithSelection().size();
252 
253   mActionInsertRowsAbove->setEnabled( rowCount > 0 );
254   mActionInsertRowsBelow->setEnabled( rowCount > 0 );
255   mActionDeleteRows->setEnabled( rowCount > 0 );
256   mActionSelectRow->setEnabled( rowCount > 0 );
257   if ( rowCount == 0 )
258   {
259     mActionInsertRowsAbove->setText( tr( "Rows Above" ) );
260     mActionInsertRowsBelow->setText( tr( "Rows Below" ) );
261     mActionDeleteRows->setText( tr( "Delete Rows" ) );
262     mActionSelectRow->setText( tr( "Select Rows" ) );
263   }
264   else if ( rowCount == 1 )
265   {
266     mActionInsertRowsAbove->setText( tr( "Row Above" ) );
267     mActionInsertRowsBelow->setText( tr( "Row Below" ) );
268     mActionDeleteRows->setText( tr( "Delete Row" ) );
269     mActionSelectRow->setText( tr( "Select Row" ) );
270   }
271   else
272   {
273     mActionInsertRowsAbove->setText( tr( "%1 Rows Above" ).arg( rowCount ) );
274     mActionInsertRowsBelow->setText( tr( "%1 Rows Below" ).arg( rowCount ) );
275     mActionDeleteRows->setText( tr( "Delete %1 Rows" ).arg( rowCount ) );
276     mActionSelectRow->setText( tr( "Select %1 Rows" ).arg( rowCount ) );
277   }
278 
279   mActionInsertColumnsBefore->setEnabled( columnCount > 0 );
280   mActionInsertColumnsAfter->setEnabled( columnCount > 0 );
281   mActionDeleteColumns->setEnabled( columnCount > 0 );
282   mActionSelectColumn->setEnabled( columnCount > 0 );
283   if ( columnCount == 0 )
284   {
285     mActionInsertColumnsBefore->setText( tr( "Columns Before" ) );
286     mActionInsertColumnsAfter->setText( tr( "Columns After" ) );
287     mActionDeleteColumns->setText( tr( "Delete Columns" ) );
288     mActionSelectColumn->setText( tr( "Select Columns" ) );
289   }
290   else if ( columnCount == 1 )
291   {
292     mActionInsertColumnsBefore->setText( tr( "Column Before" ) );
293     mActionInsertColumnsAfter->setText( tr( "Column After" ) );
294     mActionDeleteColumns->setText( tr( "Delete Column" ) );
295     mActionSelectColumn->setText( tr( "Select Column" ) );
296   }
297   else
298   {
299     mActionInsertColumnsBefore->setText( tr( "%1 Columns Before" ).arg( columnCount ) );
300     mActionInsertColumnsAfter->setText( tr( "%1 Columns After" ).arg( columnCount ) );
301     mActionDeleteColumns->setText( tr( "Delete %1 Columns" ).arg( columnCount ) );
302     mActionSelectColumn->setText( tr( "Select %1 Columns" ).arg( columnCount ) );
303   }
304 }
305 
306 #include "qgstableeditordialog.h"
307