1
/***************************************************************************
2
QgsAttributeTableDialog.cpp
6
email : weetya (at) gmail.com
8
***************************************************************************
10
* This program is free software; you can redistribute it and/or modify *
11
* it under the terms of the GNU General Public License as published by *
12
* the Free Software Foundation; either version 2 of the License, or *
13
* (at your option) any later version. *
15
***************************************************************************/
19
#include "qgsattributetabledialog.h"
20
#include "qgsattributetablemodel.h"
21
#include "qgsattributetablefiltermodel.h"
22
#include "qgsattributetableview.h"
24
#include <qgsapplication.h>
25
#include <qgsvectordataprovider.h>
26
#include <qgsvectorlayer.h>
27
#include <qgssearchstring.h>
28
#include <qgssearchtreenode.h>
31
#include "qgsaddattrdialog.h"
32
#include "qgsdelattrdialog.h"
33
#include "qgssearchquerybuilder.h"
34
#include "qgslogger.h"
35
#include "qgsmapcanvas.h"
36
#include "qgsfieldcalculator.h"
38
class QgsAttributeTableDock : public QDockWidget
41
QgsAttributeTableDock( const QString & title, QWidget * parent = 0, Qt::WindowFlags flags = 0 )
42
: QDockWidget( title, parent, flags )
44
setObjectName( "AttributeTable" ); // set object name so the position can be saved
47
virtual void closeEvent( QCloseEvent * ev )
54
QgsAttributeTableDialog::QgsAttributeTableDialog( QgsVectorLayer *theLayer, QWidget *parent, Qt::WindowFlags flags )
55
: QDialog( parent, flags ), mDock( NULL )
61
setAttribute( Qt::WA_DeleteOnClose );
64
restoreGeometry( settings.value( "/Windows/BetterAttributeTable/geometry" ).toByteArray() );
66
mView->setLayer( mLayer );
67
mFilterModel = ( QgsAttributeTableFilterModel * ) mView->model();
68
mModel = ( QgsAttributeTableModel * )(( QgsAttributeTableFilterModel * )mView->model() )->sourceModel();
71
mColumnBox = columnBox;
75
bool myDockFlag = mySettings.value( "/qgis/dockAttributeTable", false ).toBool();
78
mDock = new QgsAttributeTableDock( tr( "Attribute table - %1" ).arg( mLayer->name() ), QgisApp::instance() );
79
mDock->setAllowedAreas( Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea );
80
mDock->setWidget( this );
81
connect( this, SIGNAL( destroyed() ), mDock, SLOT( close() ) );
82
QgisApp::instance()->addDockWidget( Qt::BottomDockWidgetArea, mDock );
85
setWindowTitle( tr( "Attribute table - %1" ).arg( mLayer->name() ) );
87
mRemoveSelectionButton->setIcon( getThemeIcon( "/mActionUnselectAttributes.png" ) );
88
mSelectedToTopButton->setIcon( getThemeIcon( "/mActionSelectedToTop.png" ) );
89
mCopySelectedRowsButton->setIcon( getThemeIcon( "/mActionCopySelected.png" ) );
90
mZoomMapToSelectedRowsButton->setIcon( getThemeIcon( "/mActionZoomToSelected.png" ) );
91
mInvertSelectionButton->setIcon( getThemeIcon( "/mActionInvertSelection.png" ) );
92
mToggleEditingButton->setIcon( getThemeIcon( "/mActionToggleEditing.png" ) );
93
mDeleteSelectedButton->setIcon( getThemeIcon( "/mActionDeleteSelected.png" ) );
94
mOpenFieldCalculator->setIcon( getThemeIcon( "/mActionCalculateField.png" ) );
95
mAddAttribute->setIcon( getThemeIcon( "/mActionNewAttribute.png" ) );
96
mRemoveAttribute->setIcon( getThemeIcon( "/mActionDeleteAttribute.png" ) );
99
bool canChangeAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
100
bool canDeleteFeatures = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures;
101
bool canAddAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::AddAttributes;
102
bool canDeleteAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteAttributes;
103
mToggleEditingButton->setCheckable( true );
104
mToggleEditingButton->setChecked( mLayer->isEditable() );
105
mToggleEditingButton->setEnabled( canChangeAttributes );
106
mOpenFieldCalculator->setEnabled( canChangeAttributes && mLayer->isEditable() );
107
mDeleteSelectedButton->setEnabled( canDeleteFeatures && mLayer->isEditable() );
108
mAddAttribute->setEnabled( canAddAttributes && mLayer->isEditable() );
109
mRemoveAttribute->setEnabled( canDeleteAttributes && mLayer->isEditable() );
111
// info from table to application
112
connect( this, SIGNAL( editingToggled( QgsMapLayer * ) ), QgisApp::instance(), SLOT( toggleEditing( QgsMapLayer * ) ) );
113
// info from layer to table
114
connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( editingToggled() ) );
115
connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) );
117
connect( searchButton, SIGNAL( clicked() ), this, SLOT( search() ) );
119
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
120
connect( mLayer, SIGNAL( layerDeleted() ), this, SLOT( close() ) );
121
connect( mView->verticalHeader(), SIGNAL( sectionClicked( int ) ), this, SLOT( updateRowSelection( int ) ) );
122
connect( mView->verticalHeader(), SIGNAL( sectionPressed( int ) ), this, SLOT( updateRowPressed( int ) ) );
123
connect( mModel, SIGNAL( modelChanged() ), this, SLOT( updateSelection() ) );
125
mLastClickedHeaderIndex = 0;
126
mSelectionModel = new QItemSelectionModel( mFilterModel );
127
updateSelectionFromLayer();
129
//make sure to show all recs on first load
130
on_cbxShowSelectedOnly_toggled( false );
133
QgsAttributeTableDialog::~QgsAttributeTableDialog()
135
delete mSelectionModel;
138
void QgsAttributeTableDialog::closeEvent( QCloseEvent* event )
140
QDialog::closeEvent( event );
145
settings.setValue( "/Windows/BetterAttributeTable/geometry", saveGeometry() );
150
QIcon QgsAttributeTableDialog::getThemeIcon( const QString theName )
152
// copied from QgisApp::getThemeIcon. To be removed when merged to SVN
154
QString myPreferredPath = QgsApplication::activeThemePath() + QDir::separator() + theName;
155
QString myDefaultPath = QgsApplication::defaultThemePath() + QDir::separator() + theName;
156
if ( QFile::exists( myPreferredPath ) )
158
return QIcon( myPreferredPath );
160
else if ( QFile::exists( myDefaultPath ) )
162
//could still return an empty icon if it
163
//doesnt exist in the default theme either!
164
return QIcon( myDefaultPath );
172
void QgsAttributeTableDialog::showAdvanced()
174
mMenuActions->exec( QCursor::pos() );
177
void QgsAttributeTableDialog::on_mSelectedToTopButton_clicked()
181
//QgsFeatureIds fids = mSelectedFeatures;
182
//QgsFeatureIds::Iterator it = fids.begin();
184
mModel->incomingChangeLayout();
186
QgsFeatureIds::Iterator it = mSelectedFeatures.begin();
187
for ( ; it != mSelectedFeatures.end(); ++it, ++freeIndex )
189
QModelIndex sourceIndex = mFilterModel->mapToSource( mFilterModel->index( freeIndex, 0 ) );
190
mModel->swapRows( mModel->rowToId( sourceIndex.row() ), *it );
194
while (it != fids.end())
196
//mModel->swapRows(mModel->rowToId(freeIndex), *it);
197
//QModelIndex index = mFilterModel->mapFromSource(mModel->index(mModel->idToRow(*it), 0));
198
QModelIndex sourceIndex = mFilterModel->mapToSource(mFilterModel->index(freeIndex, 0));
199
mModel->swapRows(mModel->rowToId(sourceIndex.row()), *it);
200
//mModel->swapRows(freeIndex, *it);
210
// just select proper rows
211
//mModel->reload(mModel->index(0,0), mModel->index(mModel->rowCount(), mModel->columnCount()));
212
//mModel->changeLayout();
213
mModel->resetModel();
217
void QgsAttributeTableDialog::on_mCopySelectedRowsButton_clicked()
219
QgisApp::instance()->editCopy( mLayer );
222
void QgsAttributeTableDialog::on_mZoomMapToSelectedRowsButton_clicked()
224
QgisApp::instance()->mapCanvas()->zoomToSelected( mLayer );
227
void QgsAttributeTableDialog::on_mInvertSelectionButton_clicked()
229
mLayer->invertSelection();
232
void QgsAttributeTableDialog::on_mRemoveSelectionButton_clicked()
234
mLayer->removeSelection();
237
void QgsAttributeTableDialog::on_mDeleteSelectedButton_clicked()
239
QgisApp::instance()->deleteSelected( mLayer );
242
void QgsAttributeTableDialog::on_cbxShowSelectedOnly_toggled( bool theFlag )
244
mFilterModel->setHideUnselected( theFlag );
245
mFilterModel->invalidate();
247
//mModel->changeLayout();
251
void QgsAttributeTableDialog::columnBoxInit()
253
QgsFieldMap fieldMap = mLayer->dataProvider()->fields();
254
QgsFieldMap::Iterator it = fieldMap.begin();
256
for ( ; it != fieldMap.end(); ++it )
257
mColumnBox->addItem( it.value().name() );
260
int QgsAttributeTableDialog::columnBoxColumnId()
262
QgsFieldMap fieldMap = mLayer->dataProvider()->fields();
263
QgsFieldMap::Iterator it = fieldMap.begin();
265
for ( ; it != fieldMap.end(); ++it )
266
if ( it.value().name() == mColumnBox->currentText() )
272
void QgsAttributeTableDialog::updateSelection()
275
QItemSelection selection;
277
QgsFeatureIds::Iterator it = mSelectedFeatures.begin();
278
for ( ; it != mSelectedFeatures.end(); ++it )
280
QModelIndex leftUpIndex = mFilterModel->mapFromSource( mModel->index( mModel->idToRow( *it ), 0 ) );
281
QModelIndex rightBottomIndex = mFilterModel->mapFromSource( mModel->index( mModel->idToRow( *it ), mModel->columnCount() - 1 ) );
282
selection.append( QItemSelectionRange( leftUpIndex, rightBottomIndex ) );
283
//selection.append(QItemSelectionRange(leftUpIndex, leftUpIndex));
286
mSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );// | QItemSelectionModel::Columns);
287
mView->setSelectionModel( mSelectionModel );
289
/*for (int i = 0; i < mModel->rowCount(); ++i)
291
int id = mModel->rowToId(i);
294
QgsDebugMsg("--------------");
298
void QgsAttributeTableDialog::updateRowPressed( int index )
300
mView->setSelectionMode( QAbstractItemView::ExtendedSelection );
301
mIndexPressed = index;
304
void QgsAttributeTableDialog::updateRowSelection( int index )
308
if ( mIndexPressed == index )
314
if ( QApplication::keyboardModifiers() == Qt::ControlModifier )
316
else if ( QApplication::keyboardModifiers() == Qt::ShiftModifier )
323
if ( mIndexPressed < index )
325
first = mIndexPressed;
326
mLastClickedHeaderIndex = last = index;
330
last = mIndexPressed;
331
mLastClickedHeaderIndex = first = index;
334
updateRowSelection( first, last, 3 );
335
mView->setSelectionMode( QAbstractItemView::NoSelection );
340
if ( key == "Shift" )
342
QgsDebugMsg( "shift" );
343
// get the first and last index of the rows to be selected/deselected
346
if ( index > mLastClickedHeaderIndex )
348
first = mLastClickedHeaderIndex;
351
else if ( index == mLastClickedHeaderIndex )
353
// row was selected and now it is shift-clicked
354
first = last = index;
359
last = mLastClickedHeaderIndex;
362
// for all the rows update the selection, without starting a new selection
364
updateRowSelection( first, last, 1 );
366
else if ( key == "Control" )
368
QgsDebugMsg( "ctrl" );
369
// update the single row selection, without starting a new selection
370
updateRowSelection( index, index, 2 );
372
// the next shift would start from here
373
mLastClickedHeaderIndex = index;
377
// Start a new selection if the row was not selected
378
updateRowSelection( index, index, 0 );
380
// the next shift would start from here
381
mLastClickedHeaderIndex = index;
384
mView->setSelectionMode( QAbstractItemView::NoSelection );
387
void QgsAttributeTableDialog::updateRowSelection( int first, int last, int clickType )
389
// clickType= 0:Single click, 1:Shift, 2:Ctrl, 3: Dragged click
390
disconnect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
392
// Id must be mapped to table/view row
393
QModelIndex index = mFilterModel->mapToSource( mFilterModel->index( first, 0 ) );
394
int fid = mModel->rowToId( index.row() );
395
bool wasSelected = mSelectedFeatures.contains( fid );
397
// new selection should be created
398
if ( clickType == 0 ) // Single click
400
if ( mSelectedFeatures.size() == 1 && wasSelected ) // One item selected
401
return; // Click over a selected item doesn't do anything
403
mView->setCurrentIndex( mFilterModel->index( first, 0 ) );
404
mView->selectRow( first );
406
mSelectedFeatures.clear();
407
mSelectedFeatures.insert( fid );
408
mLayer->removeSelection();
409
mLayer->select( fid );
410
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
413
else if ( clickType == 1 ) // Shift
415
QgsFeatureIds newSelection;
417
for ( int i = first; i <= last; ++i )
421
// Id must be mapped to table/view row
422
index = mFilterModel->mapToSource( mFilterModel->index( i, 0 ) );
423
fid = mModel->rowToId( index.row() );
425
newSelection.insert( fid );
428
// Remove items in mSelectedFeatures if they aren't in mNewSelection
429
QgsFeatureIds::Iterator it = mSelectedFeatures.begin();
430
while ( it != mSelectedFeatures.end() )
432
if ( !newSelection.contains( *it ) )
434
it = mSelectedFeatures.erase( it );
442
// Append the other fids in range first-last to mSelectedFeatures
443
QgsFeatureIds::Iterator itNew = newSelection.begin();
444
for ( ; itNew != newSelection.end(); ++itNew )
446
if ( !mSelectedFeatures.contains( *itNew ) )
448
mSelectedFeatures.insert( *itNew );
452
else if ( clickType == 2 ) // Ctrl
454
// existing selection should be updated
456
mSelectedFeatures.remove( fid );
458
mSelectedFeatures.insert( fid );
460
else if ( clickType == 3 ) // Dragged click
462
QgsFeatureIds newSelection;
464
for ( int i = first; i <= last; ++i )
468
// Id must be mapped to table/view row
469
index = mFilterModel->mapToSource( mFilterModel->index( i, 0 ) );
470
fid = mModel->rowToId( index.row() );
472
newSelection.insert( fid );
475
// Remove items in mSelectedFeatures if they aren't in mNewSelection
476
QgsFeatureIds::Iterator it = mSelectedFeatures.begin();
477
while ( it != mSelectedFeatures.end() )
479
if ( !newSelection.contains( *it ) )
481
it = mSelectedFeatures.erase( it );
489
// Append the other fids in range first-last to mSelectedFeatures
490
QgsFeatureIds::Iterator itNew = newSelection.begin();
491
for ( ; itNew != newSelection.end(); ++itNew )
493
if ( !mSelectedFeatures.contains( *itNew ) )
495
mSelectedFeatures.insert( *itNew );
501
mLayer->setSelectedFeatures( mSelectedFeatures );
502
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
505
void QgsAttributeTableDialog::updateSelectionFromLayer()
507
QgsDebugMsg( "updateFromLayer" );
508
mSelectedFeatures = mLayer->selectedFeaturesIds();
510
if ( cbxShowSelectedOnly->isChecked() )
511
mFilterModel->invalidate();
516
void QgsAttributeTableDialog::doSearch( QString searchString )
518
// parse search string and build parsed tree
519
QgsSearchString search;
520
if ( !search.setString( searchString ) )
522
QMessageBox::critical( this, tr( "Search string parsing error" ), search.parserErrorMsg() );
526
QgsSearchTreeNode* searchTree = search.tree();
527
if ( searchTree == NULL )
529
QMessageBox::information( this, tr( "Search results" ), tr( "You've supplied an empty search string." ) );
533
QApplication::setOverrideCursor( Qt::WaitCursor );
534
mSelectedFeatures.clear();
536
if ( cbxSearchSelectedOnly->isChecked() )
538
QgsFeatureList selectedFeatures = mLayer->selectedFeatures();
539
for ( QgsFeatureList::ConstIterator it = selectedFeatures.begin(); it != selectedFeatures.end(); ++it )
541
if ( searchTree->checkAgainst( mLayer->pendingFields(), it->attributeMap() ) )
542
mSelectedFeatures << it->id();
544
// check if there were errors during evaluating
545
if ( searchTree->hasError() )
551
mLayer->select( mLayer->pendingAllAttributesList(), QgsRectangle(), false );
554
while ( mLayer->nextFeature( f ) )
556
if ( searchTree->checkAgainst( mLayer->pendingFields(), f.attributeMap() ) )
557
mSelectedFeatures << f.id();
559
// check if there were errors during evaluating
560
if ( searchTree->hasError() )
565
QApplication::restoreOverrideCursor();
567
if ( searchTree->hasError() )
569
QMessageBox::critical( this, tr( "Error during search" ), searchTree->errorMsg() );
576
disconnect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
577
mLayer->setSelectedFeatures( mSelectedFeatures );
578
connect( mLayer, SIGNAL( selectionChanged() ), this, SLOT( updateSelectionFromLayer() ) );
581
if ( mSelectedFeatures.size() )
582
str.sprintf( tr( "Found %d matching features.", "", mSelectedFeatures.size() ).toUtf8(), mSelectedFeatures.size() );
584
str = tr( "No matching features found." );
586
QgisApp::instance()->statusBar()->showMessage( str );
589
void QgsAttributeTableDialog::search()
592
QString str = mColumnBox->currentText();
594
const QgsFieldMap& flds = mLayer->dataProvider()->fields();
595
int fldIndex = mLayer->dataProvider()->fieldNameIndex( str );
596
QVariant::Type fldType = flds[fldIndex].type();
597
bool numeric = ( fldType == QVariant::Int || fldType == QVariant::Double );
604
str += mQuery->displayText();
610
void QgsAttributeTableDialog::on_mAdvancedSearchButton_clicked()
612
QgsSearchQueryBuilder dlg( mLayer, this );
613
dlg.setSearchString( mQuery->displayText() );
616
doSearch( dlg.searchString() );
619
void QgsAttributeTableDialog::on_mToggleEditingButton_toggled()
621
emit editingToggled( mLayer );
624
void QgsAttributeTableDialog::editingToggled()
626
mToggleEditingButton->blockSignals( true );
627
mToggleEditingButton->setChecked( mLayer->isEditable() );
628
mToggleEditingButton->blockSignals( false );
630
bool canChangeAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
631
bool canDeleteFeatures = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures;
632
bool canAddAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::AddAttributes;
633
bool canDeleteAttributes = mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteAttributes;
634
mOpenFieldCalculator->setEnabled( canChangeAttributes && mLayer->isEditable() );
635
mDeleteSelectedButton->setEnabled( canDeleteFeatures && mLayer->isEditable() );
636
mAddAttribute->setEnabled( canAddAttributes && mLayer->isEditable() );
637
mRemoveAttribute->setEnabled( canDeleteAttributes && mLayer->isEditable() );
639
// (probably reload data if user stopped editing - possible revert)
640
mModel->reload( mModel->index( 0, 0 ), mModel->index( mModel->rowCount(), mModel->columnCount() ) );
642
// not necessary to set table read only if layer is not editable
643
// because model always reflects actual state when returning item flags
647
void QgsAttributeTableDialog::startEditing()
649
mLayer->startEditing();
653
void QgsAttributeTableDialog::submit()
655
mLayer->commitChanges();
659
void QgsAttributeTableDialog::revert()
663
mModel->reload( mModel->index( 0, 0 ), mModel->index( mModel->rowCount(), mModel->columnCount() ) );
666
void QgsAttributeTableDialog::on_mAddAttribute_clicked()
673
QgsAddAttrDialog dialog( mLayer->dataProvider(), this );
674
if ( dialog.exec() == QDialog::Accepted )
676
mLayer->beginEditCommand( tr( "Attribute added" ) );
677
if ( mLayer->addAttribute( dialog.field() ) )
679
mLayer->endEditCommand();
683
QMessageBox::critical( 0, tr( "Attribute Error" ), tr( "The attribute could not be added to the layer" ) );
684
mLayer->destroyEditCommand();
686
// update model - a field has been added or updated
687
mModel->reload( mModel->index( 0, 0 ), mModel->index( mModel->rowCount(), mModel->columnCount() ) );
691
void QgsAttributeTableDialog::on_mRemoveAttribute_clicked()
698
QgsDelAttrDialog dialog( mLayer );
699
if ( dialog.exec() == QDialog::Accepted )
701
QList<int> attributes = dialog.selectedAttributes();
702
if ( attributes.size() < 1 )
707
mLayer->beginEditCommand( tr( "Deleted attribute" ) );
708
bool deleted = false;
709
QList<int>::const_iterator it = attributes.constBegin();
710
for ( ; it != attributes.constEnd(); ++it )
712
if ( mLayer->deleteAttribute( *it ) )
720
mLayer->endEditCommand();
724
QMessageBox::critical( 0, tr( "Attribute Error" ), tr( "The attribute(s) could not be deleted" ) );
725
mLayer->destroyEditCommand();
727
// update model - a field has been added or updated
728
mModel->reload( mModel->index( 0, 0 ), mModel->index( mModel->rowCount(), mModel->columnCount() ) );
732
void QgsAttributeTableDialog::on_mOpenFieldCalculator_clicked()
734
QgsFieldCalculator calc( mLayer );
735
if ( calc.exec() == QDialog::Accepted )
737
// update model - a field has been added or updated
738
mModel->reload( mModel->index( 0, 0 ), mModel->index( mModel->rowCount(), mModel->columnCount() ) );