mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
809 lines
22 KiB
C++
809 lines
22 KiB
C++
/***************************************************************************
|
|
qgsattributetable.cpp - description
|
|
-------------------
|
|
begin : Sat Nov 23 2002
|
|
copyright : (C) 2002 by Gary E.Sherman
|
|
email : sherman at mrcc dot com
|
|
Romans 3:23=>Romans 6:23=>Romans 5:8=>Romans 10:9,10=>Romans 12
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
/* $Id$ */
|
|
#include <QLineEdit>
|
|
#include <QValidator>
|
|
|
|
#include "qgsattributetable.h"
|
|
#include "qgsfield.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsvectordataprovider.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgisapp.h"
|
|
#include "qgsmapcanvas.h"
|
|
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QHeaderView>
|
|
#include <QKeyEvent>
|
|
#include <QMenu>
|
|
#include <QSettings>
|
|
|
|
|
|
QgsAttributeTableItemDelegate::QgsAttributeTableItemDelegate( QgsAttributeTable *table, QObject *parent )
|
|
: QItemDelegate( parent ), mTable( table )
|
|
{
|
|
}
|
|
|
|
QWidget *QgsAttributeTableItemDelegate::createEditor(
|
|
QWidget *parent,
|
|
const QStyleOptionViewItem &option,
|
|
const QModelIndex &index ) const
|
|
{
|
|
QWidget *editor = QItemDelegate::createEditor( parent, option, index );
|
|
QLineEdit *le = dynamic_cast<QLineEdit*>( editor );
|
|
if ( !le )
|
|
return editor;
|
|
|
|
int col = index.column();
|
|
QTableWidgetItem *twi = mTable->horizontalHeaderItem( col );
|
|
if ( !twi )
|
|
{
|
|
QgsDebugMsg( QString( "horizontalHeaderItem %1 not found" ).arg( col ) );
|
|
return editor;
|
|
}
|
|
|
|
int type = twi->data( QgsAttributeTable::AttributeType ).toInt();
|
|
if ( type == QVariant::Int )
|
|
{
|
|
le->setValidator( new QIntValidator( le ) );
|
|
}
|
|
else if ( type == QVariant::Double )
|
|
{
|
|
le->setValidator( new QDoubleValidator( le ) );
|
|
}
|
|
|
|
return editor;
|
|
}
|
|
|
|
|
|
QgsAttributeTable::QgsAttributeTable( QWidget * parent ) :
|
|
QTableWidget( parent ),
|
|
lockKeyPressed( false ),
|
|
mEditable( false ),
|
|
mEdited( false ),
|
|
mActionPopup( 0 ),
|
|
mPreviousSortIndicatorColumn( -1 )
|
|
{
|
|
QFont f( font() );
|
|
#ifdef Q_WS_MAC
|
|
// The default application font for OS X is Lucida Grande 13 point; for small, use 11 point
|
|
f.setPointSize( 11 );
|
|
#else
|
|
f.setFamily( "Helvetica" );
|
|
f.setPointSize( 9 );
|
|
#endif
|
|
setFont( f );
|
|
mDelegate = new QgsAttributeTableItemDelegate( this );
|
|
setItemDelegate( mDelegate );
|
|
setSelectionBehavior( QAbstractItemView::SelectRows );
|
|
connect( this, SIGNAL( itemSelectionChanged() ), this, SLOT( handleChangedSelections() ) );
|
|
connect( horizontalHeader(), SIGNAL( sectionClicked( int ) ), this, SLOT( columnClicked( int ) ) );
|
|
connect( verticalHeader(), SIGNAL( sectionClicked( int ) ), this, SLOT( rowClicked( int ) ) );
|
|
setFocus();
|
|
}
|
|
|
|
QgsAttributeTable::~QgsAttributeTable()
|
|
{
|
|
delete mActionPopup;
|
|
delete mDelegate;
|
|
}
|
|
|
|
void QgsAttributeTable::setReadOnly( bool b )
|
|
{
|
|
blockSignals( true );
|
|
|
|
setEditTriggers( b ? QAbstractItemView::NoEditTriggers :
|
|
QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed );
|
|
if ( !b )
|
|
{
|
|
setColumnReadOnly( 0, true );
|
|
}
|
|
|
|
blockSignals( false );
|
|
}
|
|
|
|
void QgsAttributeTable::setColumnReadOnly( int col, bool ro )
|
|
{
|
|
for ( int i = 0; i < rowCount(); ++i )
|
|
{
|
|
QTableWidgetItem *twi = item( i, col );
|
|
twi->setFlags( ro ? twi->flags() & ~Qt::ItemIsEditable : twi->flags() | Qt::ItemIsEditable );
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::columnClicked( int col )
|
|
{
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
//store the ids of the selected rows in a list
|
|
QList<int> idsOfSelected;
|
|
QList<QTableWidgetSelectionRange> selection = selectedRanges();
|
|
for ( int i = 0; i < selection.count(); i++ )
|
|
{
|
|
for ( int j = selection.at( i ).topRow(); j <= selection.at( i ).bottomRow(); j++ )
|
|
{
|
|
idsOfSelected.append( item( j, 0 )->text().toInt() );
|
|
}
|
|
}
|
|
|
|
QHeaderView *header = horizontalHeader();
|
|
if ( !header->isSortIndicatorShown() )
|
|
{
|
|
header->setSortIndicatorShown( true );
|
|
header->setSortIndicator( col, Qt::AscendingOrder );
|
|
}
|
|
if ( col != mPreviousSortIndicatorColumn )
|
|
{
|
|
// Workaround for QTableView sortIndicator displayed in wrong direction
|
|
header->setSortIndicator( col, header->sortIndicatorOrder() == Qt::AscendingOrder ?
|
|
Qt::DescendingOrder : Qt::AscendingOrder );
|
|
}
|
|
mPreviousSortIndicatorColumn = col;
|
|
sortColumn( col, header->sortIndicatorOrder() == Qt::DescendingOrder );
|
|
|
|
//clear and rebuild rowIdMap. Overwrite sortColumn later and sort rowIdMap there
|
|
rowIdMap.clear();
|
|
int id;
|
|
for ( int i = 0; i < rowCount(); i++ )
|
|
{
|
|
id = item( i, 0 )->text().toInt();
|
|
rowIdMap.insert( id, i );
|
|
}
|
|
|
|
disconnect( this, SIGNAL( itemSelectionChanged() ), this, SLOT( handleChangedSelections() ) );
|
|
clearSelection();
|
|
|
|
//select the rows again after sorting
|
|
|
|
QList < int >::iterator it;
|
|
for ( it = idsOfSelected.begin(); it != idsOfSelected.end(); ++it )
|
|
{
|
|
selectRowWithId( *it );
|
|
}
|
|
connect( this, SIGNAL( itemSelectionChanged() ), this, SLOT( handleChangedSelections() ) );
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
void QgsAttributeTable::keyPressEvent( QKeyEvent * ev )
|
|
{
|
|
if ( ev->key() == Qt::Key_Control || ev->key() == Qt::Key_Shift )
|
|
{
|
|
lockKeyPressed = true;
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::keyReleaseEvent( QKeyEvent * ev )
|
|
{
|
|
if ( ev->key() == Qt::Key_Control || ev->key() == Qt::Key_Shift )
|
|
{
|
|
lockKeyPressed = false;
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::handleChangedSelections()
|
|
{
|
|
emit selectionRemoved( false );
|
|
|
|
QList<QTableWidgetSelectionRange> selectedItemRanges = selectedRanges();
|
|
QList<QTableWidgetSelectionRange>::const_iterator range_it = selectedItemRanges.constBegin();
|
|
for ( ; range_it != selectedItemRanges.constEnd(); ++range_it )
|
|
{
|
|
for ( int index = range_it->topRow(); index <= range_it->bottomRow(); index++ )
|
|
{
|
|
emit selected( item( index, 0 )->text().toInt(), false );
|
|
}
|
|
}
|
|
|
|
//don't send the signal repaintRequested() from here
|
|
//but in mouseReleaseEvent() and rowClicked(int)
|
|
//todo: don't repaint in case of double clicks
|
|
}
|
|
|
|
void QgsAttributeTable::insertFeatureId( int id, int row )
|
|
{
|
|
rowIdMap.insert( id, row );
|
|
}
|
|
|
|
void QgsAttributeTable::selectRowWithId( int id )
|
|
{
|
|
QMap < int, int >::iterator it = rowIdMap.find( id );
|
|
setRangeSelected( QTableWidgetSelectionRange( it.value(), 0, it.value(), columnCount() - 1 ), true );
|
|
}
|
|
|
|
void QgsAttributeTable::sortColumn( int col, bool ascending )
|
|
{
|
|
int type = horizontalHeaderItem( col )->data( AttributeType ).toInt();
|
|
qsort( 0, rowCount() - 1, col, ascending, type != QVariant::Int && type != QVariant::Double );
|
|
}
|
|
|
|
|
|
/**
|
|
XXX Doesn't QString have something ilke this already?
|
|
*/
|
|
int QgsAttributeTable::compareItems( QString s1, QString s2, bool ascending, bool alphanumeric )
|
|
{
|
|
if ( alphanumeric )
|
|
{
|
|
if ( s1 > s2 )
|
|
{
|
|
if ( ascending )
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
else if ( s1 < s2 )
|
|
{
|
|
if ( ascending )
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else if ( s1 == s2 )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else //numeric
|
|
{
|
|
double d1 = s1.toDouble();
|
|
double d2 = s2.toDouble();
|
|
if ( d1 > d2 )
|
|
{
|
|
if ( ascending )
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
else if ( d1 < d2 )
|
|
{
|
|
if ( ascending )
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else if ( d1 == d2 )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0; // XXX has to return something; is this reasonable?
|
|
}
|
|
|
|
void QgsAttributeTable::qsort( int lower, int upper, int col, bool ascending, bool alphanumeric )
|
|
{
|
|
int i, j;
|
|
QString v;
|
|
if ( upper > lower )
|
|
{
|
|
//chose a random element (this avoids n^2 worst case)
|
|
int element = int (( double )rand() / ( double )RAND_MAX * ( upper - lower ) + lower );
|
|
swapRows( element, upper );
|
|
v = item( upper, col )->text();
|
|
i = lower - 1;
|
|
j = upper;
|
|
for ( ;; )
|
|
{
|
|
while ( compareItems( item( ++i, col )->text(), v, ascending, alphanumeric ) == -1 )
|
|
;
|
|
while ( compareItems( item( --j, col )->text(), v, ascending, alphanumeric ) == 1 && j > 0 ); //make sure that j does not get negative
|
|
if ( i >= j )
|
|
{
|
|
break;
|
|
}
|
|
swapRows( i, j );
|
|
}
|
|
swapRows( i, upper );
|
|
qsort( lower, i - 1, col, ascending, alphanumeric );
|
|
qsort( i + 1, upper, col, ascending, alphanumeric );
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::swapRows( int row1, int row2 )
|
|
{
|
|
for ( int col = 0; col < columnCount(); col++ )
|
|
{
|
|
QTableWidgetItem *item = takeItem( row1, col );
|
|
setItem( row1, col, takeItem( row2, col ) );
|
|
setItem( row2, col, item );
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::contextMenuEvent( QContextMenuEvent *event )
|
|
{
|
|
const QPoint& pos = event->globalPos();
|
|
int row = rowAt( pos.x() );
|
|
int col = columnAt( pos.y() );
|
|
|
|
// Duplication of code in qgsidentufyresults.cpp. Consider placing
|
|
// in a seperate class
|
|
if ( mActionPopup == 0 )
|
|
{
|
|
mActionPopup = new QMenu();
|
|
mActionPopup->addAction( tr( "Run action" ) );
|
|
mActionPopup->addSeparator();
|
|
|
|
QgsAttributeAction::aIter iter = mActions.begin();
|
|
for ( int j = 0; iter != mActions.end(); ++iter, ++j )
|
|
{
|
|
QAction* a = mActionPopup->addAction( iter->name() );
|
|
// The menu action stores an integer that is used later on to
|
|
// associate an menu action with an actual qgis action.
|
|
a->setData( QVariant::fromValue( j ) );
|
|
}
|
|
connect( mActionPopup, SIGNAL( triggered( QAction* ) ),
|
|
this, SLOT( popupItemSelected( QAction* ) ) );
|
|
}
|
|
|
|
// Get and store the attribute values and their column names are
|
|
// these are needed for substituting into the actions if the user
|
|
// chooses one.
|
|
mActionValues.clear();
|
|
|
|
for ( int i = 0; i < columnCount(); ++i )
|
|
{
|
|
if ( row >= 0 ) //prevent crash if row is negative, see ticket #1149
|
|
{
|
|
mActionValues.push_back(
|
|
std::make_pair(
|
|
horizontalHeaderItem( i )->text(),
|
|
item( row, i )->text() ) );
|
|
}
|
|
}
|
|
// The item that was clicked on, stored as an index into the
|
|
// mActionValues vector.
|
|
mClickedOnValue = col;
|
|
|
|
if ( mActions.size() > 0 )
|
|
mActionPopup->popup( pos );
|
|
}
|
|
|
|
void QgsAttributeTable::popupItemSelected( QAction* menuAction )
|
|
{
|
|
int id = menuAction->data().toInt();
|
|
mActions.doAction( id, mActionValues, mClickedOnValue );
|
|
}
|
|
|
|
/* Deprecated: See QgisApp::editCopy() instead */
|
|
void QgsAttributeTable::copySelectedRows()
|
|
{
|
|
// Copy selected rows to the clipboard
|
|
|
|
QString toClipboard;
|
|
const char fieldSep = '\t';
|
|
|
|
// Pick up the headers first
|
|
for ( int i = 0; i < columnCount(); ++i )
|
|
toClipboard += horizontalHeaderItem( i )->text() + fieldSep;
|
|
toClipboard += '\n';
|
|
|
|
QList<QTableWidgetSelectionRange> selection = selectedRanges();
|
|
// Then populate with the cell contents
|
|
for ( int i = 0; i < selection.count(); ++i )
|
|
{
|
|
QTableWidgetSelectionRange sel = selection.at( i );
|
|
for ( int row = sel.topRow(); row < sel.topRow() + sel.rowCount(); ++row )
|
|
{
|
|
for ( int column = 0; column < columnCount(); ++column )
|
|
toClipboard += item( row, column )->text() + fieldSep;
|
|
toClipboard += '\n';
|
|
}
|
|
}
|
|
#ifdef QGISDEBUG
|
|
std::cerr << "Selected data in table is:\n" << toClipboard.data(); // OK
|
|
#endif
|
|
// And then copy to the clipboard
|
|
QClipboard* clipboard = QApplication::clipboard();
|
|
|
|
// With qgis running under Linux, but with a Windows based X
|
|
// server (Xwin32), ::Selection was necessary to get the data into
|
|
// the Windows clipboard (which seems contrary to the Qt
|
|
// docs). With a Linux X server, ::Clipboard was required.
|
|
// The simple solution was to put the text into both clipboards.
|
|
|
|
// The ::Selection setText() below one may need placing inside so
|
|
// #ifdef so that it doesn't get compiled under Windows.
|
|
clipboard->setText( toClipboard, QClipboard::Selection );
|
|
clipboard->setText( toClipboard, QClipboard::Clipboard );
|
|
}
|
|
|
|
void QgsAttributeTable::addFeatureToTable( QgsVectorLayer *layer, int id )
|
|
{
|
|
blockSignals( true );
|
|
|
|
QgsFeature f;
|
|
if ( layer->featureAtId( id, f, false, true ) )
|
|
{
|
|
putFeatureInTable( rowCount(), f );
|
|
}
|
|
|
|
blockSignals( false );
|
|
}
|
|
|
|
void QgsAttributeTable::fillTable( QgsVectorLayer *layer )
|
|
{
|
|
blockSignals( true );
|
|
|
|
const QgsFieldMap &fields = layer->pendingFields();
|
|
|
|
// set up the column headers
|
|
setColumnCount( fields.size() + 1 );
|
|
|
|
setHorizontalHeaderItem( 0, new QTableWidgetItem( "id" ) ); //label for the id-column
|
|
|
|
int h = 1;
|
|
for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); fldIt++, h++ )
|
|
{
|
|
QgsDebugMsg( QString( "%1: field %2: %3 | %4" )
|
|
.arg( h ).arg( fldIt.key() ).arg( fldIt->name() ).arg( QVariant::typeToName( fldIt->type() ) ) );
|
|
|
|
QTableWidgetItem *twi = new QTableWidgetItem( fldIt->name() );
|
|
twi->setData( AttributeIndex, fldIt.key() );
|
|
twi->setData( AttributeName, fldIt->name() );
|
|
twi->setData( AttributeType, ( int ) fldIt->type() );
|
|
setHorizontalHeaderItem( h, twi );
|
|
|
|
mAttrIdxMap.insert( fldIt.key(), h );
|
|
}
|
|
|
|
QgsFeatureList features;
|
|
|
|
QSettings settings;
|
|
int behaviour = settings.value( "/qgis/attributeTableBehaviour", 0 ).toInt();
|
|
|
|
if ( behaviour == 1 )
|
|
{
|
|
features = layer->selectedFeatures();
|
|
}
|
|
else
|
|
{
|
|
QgsRectangle rect;
|
|
if ( behaviour == 2 )
|
|
{
|
|
// current canvas only
|
|
rect = QgisApp::instance()->mapCanvas()->extent();
|
|
}
|
|
|
|
layer->select( layer->pendingAllAttributesList(), rect, false );
|
|
|
|
QgsFeature f;
|
|
while ( layer->nextFeature( f ) )
|
|
features << f;
|
|
}
|
|
|
|
setRowCount( features.size() );
|
|
|
|
for ( int i = 0; i < features.size(); i++ )
|
|
putFeatureInTable( i, features[i] );
|
|
|
|
// Default row height is too tall
|
|
resizeRowsToContents();
|
|
|
|
// Make each column wide enough to show all the contents
|
|
for ( int i = 0; i < columnCount(); i++ )
|
|
resizeColumnToContents( i );
|
|
|
|
blockSignals( false );
|
|
}
|
|
|
|
void QgsAttributeTable::putFeatureInTable( int row, const QgsFeature& fet )
|
|
{
|
|
// Prevent a crash if a provider doesn't update the feature count properly
|
|
if ( row >= rowCount() )
|
|
{
|
|
setRowCount( row + 1 );
|
|
}
|
|
|
|
//id-field
|
|
int id = fet.id();
|
|
QTableWidgetItem *twi = new QTableWidgetItem( QString::number( id ) );
|
|
twi->setTextAlignment( Qt::AlignRight | Qt::AlignVCenter );
|
|
setItem( row, 0, twi );
|
|
insertFeatureId( id, row ); //insert the id into the search tree of qgsattributetable
|
|
|
|
const QgsAttributeMap& attr = fet.attributeMap();
|
|
|
|
for ( QgsAttributeMap::const_iterator it = attr.begin(); it != attr.end(); ++it )
|
|
{
|
|
if ( !mAttrIdxMap.contains( it.key() ) )
|
|
continue;
|
|
|
|
int h = mAttrIdxMap[ it.key()];
|
|
|
|
twi = horizontalHeaderItem( h );
|
|
if ( !twi )
|
|
{
|
|
QgsDebugMsg( "header item not found." );
|
|
continue;
|
|
}
|
|
|
|
int type = twi->data( AttributeType ).toInt();
|
|
bool isNum = ( type == QVariant::Double || type == QVariant::Int );
|
|
|
|
QString value;
|
|
// get the field values
|
|
if ( it->isNull() )
|
|
{
|
|
if ( isNum )
|
|
value = "";
|
|
else
|
|
value = "NULL";
|
|
}
|
|
else
|
|
{
|
|
value = it->toString();
|
|
}
|
|
|
|
twi = new QTableWidgetItem( value );
|
|
if ( isNum )
|
|
twi->setTextAlignment( Qt::AlignRight | Qt::AlignVCenter );
|
|
setItem( row, h, twi );
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::bringSelectedToTop()
|
|
{
|
|
blockSignals( true );
|
|
horizontalHeader()->setSortIndicatorShown( false );
|
|
mPreviousSortIndicatorColumn = -1;
|
|
int swaptorow = 0;
|
|
QList<QTableWidgetSelectionRange> selections = selectedRanges();
|
|
bool removeselection;
|
|
|
|
for ( QList<QTableWidgetSelectionRange>::iterator iter = selections.begin();iter != selections.end();++iter )
|
|
{
|
|
removeselection = true;
|
|
while ( swaptorow < rowCount() && item( swaptorow, 0 )->isSelected() )//selections are not necessary stored in ascending order
|
|
{
|
|
++swaptorow;
|
|
}
|
|
|
|
for ( int j = iter->topRow();j <= iter->bottomRow();++j )
|
|
{
|
|
if ( j > swaptorow )//selections are not necessary stored in ascending order
|
|
{
|
|
swapRows( j, swaptorow );
|
|
setRangeSelected( QTableWidgetSelectionRange( swaptorow, 0, swaptorow, columnCount() - 1 ), true );
|
|
++swaptorow;
|
|
|
|
}
|
|
else
|
|
{
|
|
removeselection = false;//keep selection
|
|
}
|
|
}
|
|
if ( removeselection )
|
|
{
|
|
setRangeSelected( *iter, false );
|
|
}
|
|
}
|
|
|
|
//clear and rebuild rowIdMap.
|
|
rowIdMap.clear();
|
|
int id;
|
|
for ( int i = 0; i < rowCount(); i++ )
|
|
{
|
|
id = item( i, 0 )->text().toInt();
|
|
rowIdMap.insert( id, i );
|
|
}
|
|
|
|
blockSignals( false );
|
|
}
|
|
|
|
void QgsAttributeTable::selectRowsWithId( const QgsFeatureIds &ids, QgsVectorLayer *layer )
|
|
{
|
|
#if 0
|
|
// if selecting rows takes too much time we can use progress dialog
|
|
QProgressDialog progress( tr( "Updating selection..." ), tr( "Abort" ), 0, mSelected.size(), tabledisplay );
|
|
int i = 0;
|
|
for ( std::set<int>::iterator iter = mSelected.begin();iter != mSelected.end();++iter )
|
|
{
|
|
++i;
|
|
progress.setValue( i );
|
|
qApp->processEvents();
|
|
if ( progress.wasCanceled() )
|
|
{
|
|
//deselect the remaining features if action was canceled
|
|
mSelected.erase( iter, --mSelected.end() );
|
|
break;
|
|
}
|
|
selectRowWithId( *iter );//todo: avoid that the table gets repainted during each selection
|
|
}
|
|
#endif
|
|
|
|
// to select more rows at once effectively, we stop sending signals to handleChangedSelections()
|
|
// otherwise it will repaint map everytime row is selected
|
|
|
|
disconnect( this, SIGNAL( itemSelectionChanged() ), this, SLOT( handleChangedSelections() ) );
|
|
|
|
clearSelection();
|
|
QgsFeatureIds::const_iterator it;
|
|
for ( it = ids.begin(); it != ids.end(); it++ )
|
|
{
|
|
if ( layer && !rowIdMap.contains( *it ) )
|
|
{
|
|
// add feature if we do not already have it
|
|
addFeatureToTable( layer, *it );
|
|
}
|
|
|
|
selectRowWithId( *it );
|
|
}
|
|
|
|
connect( this, SIGNAL( itemSelectionChanged() ), this, SLOT( handleChangedSelections() ) );
|
|
emit repaintRequested();
|
|
}
|
|
|
|
void QgsAttributeTable::showRowsWithId( const QgsFeatureIds& ids )
|
|
{
|
|
setUpdatesEnabled( false );
|
|
|
|
// hide all rows first
|
|
for ( int i = 0; i < rowCount(); i++ )
|
|
hideRow( i );
|
|
|
|
// show only matching rows
|
|
QgsFeatureIds::const_iterator it;
|
|
for ( it = ids.begin(); it != ids.end(); it++ )
|
|
{
|
|
showRow( rowIdMap[*it] );
|
|
}
|
|
|
|
clearSelection(); // deselect all
|
|
setUpdatesEnabled( true );
|
|
}
|
|
|
|
void QgsAttributeTable::showAllRows()
|
|
{
|
|
for ( int i = 0; i < rowCount(); i++ )
|
|
showRow( i );
|
|
}
|
|
|
|
void QgsAttributeTable::rowClicked( int row )
|
|
{
|
|
if ( checkSelectionChanges() )//only repaint the canvas if the selection has changed
|
|
{
|
|
emit repaintRequested();
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::mouseReleaseEvent( QMouseEvent* e )
|
|
{
|
|
QTableWidget::mouseReleaseEvent( e );
|
|
if ( checkSelectionChanges() )//only repaint the canvas if the selection has changed
|
|
{
|
|
emit repaintRequested();
|
|
}
|
|
}
|
|
|
|
bool QgsAttributeTable::checkSelectionChanges()
|
|
{
|
|
std::set<int> theCurrentSelection;
|
|
QList<QTableWidgetSelectionRange> selectedItemRanges = selectedRanges();
|
|
QList<QTableWidgetSelectionRange>::const_iterator range_it = selectedItemRanges.constBegin();
|
|
for ( ; range_it != selectedItemRanges.constEnd(); ++range_it )
|
|
{
|
|
for ( int index = range_it->topRow(); index <= range_it->bottomRow(); index++ )
|
|
{
|
|
theCurrentSelection.insert( index );
|
|
}
|
|
}
|
|
|
|
if ( theCurrentSelection == mLastSelectedRows )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
mLastSelectedRows = theCurrentSelection;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void QgsAttributeTable::attributeValueChanged( int fid, int idx, const QVariant &value )
|
|
{
|
|
if ( !rowIdMap.contains( fid ) )
|
|
return;
|
|
|
|
if ( !mAttrIdxMap.contains( idx ) )
|
|
return;
|
|
|
|
QTableWidgetItem *twi = horizontalHeaderItem( mAttrIdxMap[ idx ] );
|
|
if ( !twi )
|
|
{
|
|
QgsDebugMsg( "header item not found." );
|
|
return;
|
|
}
|
|
|
|
int type = twi->data( AttributeType ).toInt();
|
|
bool isNum = ( type == QVariant::Double || type == QVariant::Int );
|
|
|
|
QString v;
|
|
// get the field values
|
|
if ( value.isNull() )
|
|
{
|
|
if ( isNum )
|
|
v = "";
|
|
else
|
|
v = "NULL";
|
|
}
|
|
else
|
|
{
|
|
v = value.toString();
|
|
}
|
|
|
|
item( rowIdMap[fid], mAttrIdxMap[idx] )->setText( v );
|
|
}
|
|
|
|
void QgsAttributeTable::featureDeleted( int fid )
|
|
{
|
|
if ( !rowIdMap.contains( fid ) )
|
|
return;
|
|
|
|
int row = rowIdMap[fid];
|
|
|
|
removeRow( row );
|
|
|
|
for ( QMap<int, int>::iterator it = rowIdMap.begin(); it != rowIdMap.end(); it++ )
|
|
if ( it.value() > row )
|
|
rowIdMap[ it.key()]--;
|
|
}
|
|
|
|
void QgsAttributeTable::addAttribute( int attr, const QgsField &fld )
|
|
{
|
|
QTableWidgetItem *twi = new QTableWidgetItem( fld.name() );
|
|
twi->setData( AttributeIndex, attr );
|
|
twi->setData( AttributeName, fld.name() );
|
|
twi->setData( AttributeType, ( int ) fld.type() );
|
|
|
|
insertColumn( columnCount() );
|
|
setHorizontalHeaderItem( columnCount() - 1, twi );
|
|
|
|
mAttrIdxMap.insert( attr, columnCount() - 1 );
|
|
}
|
|
|
|
void QgsAttributeTable::deleteAttribute( int attr )
|
|
{
|
|
int column = mAttrIdxMap[attr];
|
|
|
|
removeColumn( column );
|
|
mAttrIdxMap.remove( attr );
|
|
for ( QMap<int, int>::iterator it = mAttrIdxMap.begin(); it != mAttrIdxMap.end(); it++ )
|
|
{
|
|
if ( it.value() > column )
|
|
mAttrIdxMap[ it.key()]--;
|
|
}
|
|
}
|