121 changes: 106 additions & 15 deletions src/core/qgsvectorlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
const QString displayField() const;

/** Set the preview expression, used to create a human readable preview string.
* Used e.g. in the attribute table feature list. Uses @link QgsExpression @endlink
* Used e.g. in the attribute table feature list. Uses { @link QgsExpression }.
*
* @param displayExpression The expression which will be used to preview features
* for this layer
Expand All @@ -268,9 +268,10 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer

/**
* Get the preview expression, used to create a human readable preview string.
* Uses @link QgsExpression @endlink
* Uses { @link QgsExpression }
*
* @return The expression which will be used to preview features for this layer
*
* @note added in 2.0
*/
const QString displayExpression();
Expand Down Expand Up @@ -308,25 +309,72 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer

QgsAttributeAction *actions() { return mActions; }

/** The number of features that are selected in this layer */
/**
* The number of features that are selected in this layer
*
* @return See description
*/
int selectedFeatureCount();

/** Select features found within the search rectangle (in layer's coordinates) */
void select( QgsRectangle & rect, bool lock );
/**
* Select features found within the search rectangle (in layer's coordinates)
*
* @param rect The search rectangle
* @param addToSelection If set to true will not clear before selecting
*
* @see invertSelectionInRectangle(QgsRectangle & rect)
*/
void select( QgsRectangle & rect, bool addToSelection );

/**
* Modifies the current selection on this layer
*
* @param selectIds Select these ids
* @param deselectIds Deselect these ids
*
* @see select(QgsFeatureIds)
* @see select(QgsFeatureId)
* @see deselect(QgsFeatureIds)
* @see deselect(QgsFeatureId)
*/
void modifySelection(QgsFeatureIds selectIds, QgsFeatureIds deselectIds );

/** Select not selected features and deselect selected ones */
void invertSelection();

/** Invert selection of features found within the search rectangle (in layer's coordinates) */
/**
* Invert selection of features found within the search rectangle (in layer's coordinates)
*
* @param rect The rectangle in which the selection of features will be inverted
*
* @see invertSelection()
*/
void invertSelectionInRectangle( QgsRectangle & rect );

/** Get a copy of the user-selected features */
/**
* Get a copy of the user-selected features
*
* @return A list of { @link QgsFeature } 's
*
* @see selectedFeaturesIds()
*/
QgsFeatureList selectedFeatures();

/** Return reference to identifiers of selected features */
/**
* Return reference to identifiers of selected features
*
* @return A list of { @link QgsFeatureId } 's
* @see selectedFeatures()
*/
const QgsFeatureIds &selectedFeaturesIds() const;

/** Change selection to the new set of features */
/**
* Change selection to the new set of features. Dismisses the current selection.
* Will emit the { @link selectionChanged( QgsFeatureIds, QgsFeatureIds, bool ) } signal with the
* clearAndSelect flag set.
*
* @param ids The ids which will be the new selection
*/
void setSelectedFeatures( const QgsFeatureIds &ids );

/** Returns the bounding box of the selected features. If there is no selection, QgsRectangle(0,0,0,0) is returned */
Expand Down Expand Up @@ -858,14 +906,48 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
QVariant maximumValue( int index );

public slots:
/** Select feature by its ID, optionally emit signal selectionChanged() */
void select( QgsFeatureId featureId, bool emitSignal = true );
/**
* Select feature by its ID
*
* @param featureId The id of the feature to select
*
* @see select(QgsFeatureIds)
*/
void select( const QgsFeatureId &featureId );

/**
* Select features by their ID
*
* @param featureIds The ids of the features to select
*
* @see select(QgsFeatureId)
*/
void select( const QgsFeatureIds& featureIds );

/**
* Deselect feature by its ID
*
* @param featureId The id of the feature to deselect
*
* @see deselect(QgsFeatureIds)
*/
void deselect( const QgsFeatureId featureId );

/** Deselect feature by its ID, optionally emit signal selectionChanged() */
void deselect( QgsFeatureId featureId, bool emitSignal = true );
/**
* Deselect features by their ID
*
* @param featureIds The ids of the features to deselect
*
* @see deselect(QgsFeatureId)
*/
void deselect(const QgsFeatureIds& featureIds );

/** Clear selection */
void removeSelection( bool emitSignal = true );
/**
* Clear selection
*
* @see setSelectedFeatures(const QgsFeatureIds&)
*/
void removeSelection();

void triggerRepaint();

Expand All @@ -890,6 +972,15 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer

signals:

/**
* This signal is emited when selection was changed
*
* @param selected Newly selected feature ids
* @param deselected Ids of all features which have previously been selected but are not any more
* @param clearAndSelect In case this is set to true, the old selection was dismissed and the new selection corresponds to selected
*/
void selectionChanged( const QgsFeatureIds selected, const QgsFeatureIds deselected, const bool clearAndSelect );

/** This signal is emited when selection was changed */
void selectionChanged();

Expand Down
4 changes: 4 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ attributetable/qgsattributetabledelegate.cpp
attributetable/qgsfeaturelistview.cpp
attributetable/qgsfeaturelistmodel.cpp
attributetable/qgsfeaturelistviewdelegate.cpp
attributetable/qgsfeatureselectionmodel.cpp
attributetable/qgsdualview.cpp

qgisgui.cpp
Expand Down Expand Up @@ -168,6 +169,7 @@ attributetable/qgsattributetablefiltermodel.h
attributetable/qgsattributetabledelegate.h
attributetable/qgsfeaturelistview.h
attributetable/qgsfeaturelistmodel.h
attributetable/qgsfeatureselectionmodel.h
attributetable/qgsfeaturelistviewdelegate.h
attributetable/qgsdualview.h

Expand Down Expand Up @@ -271,8 +273,10 @@ attributetable/qgsattributetableview.h
attributetable/qgsattributetablefiltermodel.h
attributetable/qgsattributetabledelegate.h
attributetable/qgsfeaturelistview.h
attributetable/qgsfeaturemodel.h
attributetable/qgsfeaturelistmodel.h
attributetable/qgsfeaturelistviewdelegate.h
attributetable/qgsfeatureselectionmodel.h
attributetable/qgsdualview.h
)

Expand Down
62 changes: 26 additions & 36 deletions src/gui/attributetable/qgsattributetabledelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <QComboBox>
#include <QPainter>

#include "qgsfeatureselectionmodel.h"
#include "qgsattributetableview.h"
#include "qgsattributetablemodel.h"
#include "qgsattributetablefiltermodel.h"
Expand All @@ -27,6 +28,7 @@
#include "qgslogger.h"



// TODO: Remove this casting orgy

QgsVectorLayer *QgsAttributeTableDelegate::layer( const QAbstractItemModel *model ) const
Expand All @@ -42,33 +44,6 @@ QgsVectorLayer *QgsAttributeTableDelegate::layer( const QAbstractItemModel *mode
return NULL;
}

int QgsAttributeTableDelegate::fieldIdx( const QModelIndex &index ) const
{
const QgsAttributeTableModel *tm = qobject_cast<const QgsAttributeTableModel *>( index.model() );
if ( tm )
return tm->fieldIdx( index.column() );

const QgsAttributeTableFilterModel *fm = dynamic_cast<const QgsAttributeTableFilterModel *>( index.model() );
if ( fm )
return fm->masterModel()->fieldIdx( index.column() );

return -1;
}

QgsFeatureId QgsAttributeTableDelegate::featureId( const QModelIndex &index ) const
{
const QgsAttributeTableModel *tm = qobject_cast<const QgsAttributeTableModel *>( index.model() );
if ( tm )
return tm->rowToId( index.row() );

const QgsAttributeTableFilterModel *fm = dynamic_cast<const QgsAttributeTableFilterModel *>( index.model() );
if ( fm )
return fm->masterModel()->rowToId( fm->mapToSource( index ).row() );

return -1;
}


QWidget *QgsAttributeTableDelegate::createEditor(
QWidget *parent,
const QStyleOptionViewItem &option,
Expand All @@ -79,15 +54,17 @@ QWidget *QgsAttributeTableDelegate::createEditor(
if ( !vl )
return NULL;

QWidget *w = QgsAttributeEditor::createAttributeEditor( parent, 0, vl, fieldIdx( index ), index.model()->data( index, Qt::EditRole ) );
int fieldIdx = index.model()->data( index, QgsAttributeTableModel::FieldIndexRole ).toInt();

QWidget *w = QgsAttributeEditor::createAttributeEditor( parent, 0, vl, fieldIdx, index.model()->data( index, Qt::EditRole ) );

if ( parent )
{
QgsAttributeTableView *tv = dynamic_cast<QgsAttributeTableView *>( parent->parentWidget() );
w->setMinimumWidth( tv->columnWidth( index.column() ) );

if ( vl->editType( fieldIdx( index ) ) == QgsVectorLayer::FileName ||
vl->editType( fieldIdx( index ) ) == QgsVectorLayer::Calendar )
if ( vl->editType( fieldIdx ) == QgsVectorLayer::FileName ||
vl->editType( fieldIdx ) == QgsVectorLayer::Calendar )
{
QLineEdit *le = w->findChild<QLineEdit*>();
le->adjustSize();
Expand All @@ -104,15 +81,15 @@ void QgsAttributeTableDelegate::setModelData( QWidget *editor, QAbstractItemMode
if ( vl == NULL )
return;

int idx = fieldIdx( index );
QgsFeatureId fid = featureId( index );
int fieldIdx = model->data( index, QgsAttributeTableModel::FieldIndexRole ).toInt();
QgsFeatureId fid = model->data( index, QgsAttributeTableModel::FeatureIdRole ).toInt();

QVariant value;
if ( !QgsAttributeEditor::retrieveValue( editor, vl, idx, value ) )
if ( !QgsAttributeEditor::retrieveValue( editor, vl, fieldIdx, value ) )
return;

vl->beginEditCommand( tr( "Attribute changed" ) );
vl->changeAttributeValue( fid, idx, value, true );
vl->changeAttributeValue( fid, fieldIdx, value, true );
vl->endEditCommand();
}

Expand All @@ -122,14 +99,27 @@ void QgsAttributeTableDelegate::setEditorData( QWidget *editor, const QModelInde
if ( vl == NULL )
return;

QgsAttributeEditor::setValue( editor, vl, fieldIdx( index ), index.model()->data( index, Qt::EditRole ) );
int fieldIdx = index.model()->data( index, QgsAttributeTableModel::FieldIndexRole ).toInt();
QgsAttributeEditor::setValue( editor, vl, fieldIdx, index.model()->data( index, Qt::EditRole ) );
}

void QgsAttributeTableDelegate::setFeatureSelectionModel( QgsFeatureSelectionModel *featureSelectionModel )
{
mFeatureSelectionModel = featureSelectionModel;
}

void QgsAttributeTableDelegate::paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QItemDelegate::paint( painter, option, index );
QgsFeatureId fid = index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toInt();

QStyleOptionViewItem myOpt = option;

if ( mFeatureSelectionModel->isSelected( fid ) )
myOpt.state |= QStyle::State_Selected;

QItemDelegate::paint( painter, myOpt, index );

if ( option.state & QStyle::State_HasFocus )
{
Expand Down
8 changes: 6 additions & 2 deletions src/gui/attributetable/qgsattributetabledelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <QItemDelegate>
#include "qgsfeature.h"

class QgsFeatureSelectionModel;
class QPainter;
class QgsVectorLayer;
class QgsAttributeTableView;
Expand All @@ -33,8 +34,6 @@ class QgsAttributeTableDelegate : public QItemDelegate
Q_OBJECT;

QgsVectorLayer *layer( const QAbstractItemModel *model ) const;
int fieldIdx( const QModelIndex &index ) const;
QgsFeatureId featureId( const QModelIndex &index ) const;

public:
/** Constructor
Expand Down Expand Up @@ -69,6 +68,11 @@ class QgsAttributeTableDelegate : public QItemDelegate
* @param index index of field which is to be retrieved
*/
void setEditorData( QWidget *editor, const QModelIndex &index ) const;

void setFeatureSelectionModel( QgsFeatureSelectionModel* featureSelectionModel );

private:
QgsFeatureSelectionModel* mFeatureSelectionModel;
};

#endif //QGSATTRIBUTETABLEDELEGATE_H
138 changes: 36 additions & 102 deletions src/gui/attributetable/qgsattributetablefiltermodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ bool QgsAttributeTableFilterModel::lessThan( const QModelIndex &left, const QMod
{
if ( mSelectedOnTop )
{
// TODO: does the model index (left/right) need to be converted to first?
bool leftSelected = layer()->selectedFeaturesIds().contains( masterModel()->rowToId( left.row() ) );
bool rightSelected = layer()->selectedFeaturesIds().contains( masterModel()->rowToId( right.row() ) );

Expand All @@ -63,26 +62,25 @@ bool QgsAttributeTableFilterModel::lessThan( const QModelIndex &left, const QMod

void QgsAttributeTableFilterModel::setSelectedOnTop( bool selectedOnTop )
{
mSelectedOnTop = selectedOnTop;
if ( sortColumn() == -1 )
if ( mSelectedOnTop != selectedOnTop )
{
sort( 0, sortOrder() );
mSelectedOnTop = selectedOnTop;

if ( sortColumn() == -1 )
{
sort( 0 );
}
invalidate();
}
invalidate();
}


void QgsAttributeTableFilterModel::setSourceModel( QgsAttributeTableModel* sourceModel )
{
mTableModel = sourceModel;
delete mMasterSelection;
mMasterSelection = new QItemSelectionModel( sourceModel, this );

QSortFilterProxyModel::setSourceModel( sourceModel );

connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( masterSelectionChanged( QItemSelection, QItemSelection ) ) );
// Issue a selectionChanged, so the selection gets synchronized to the map canvas selection
selectionChanged();
}

bool QgsAttributeTableFilterModel::selectedOnTop()
Expand All @@ -94,7 +92,7 @@ void QgsAttributeTableFilterModel::setFilteredFeatures( QgsFeatureIds ids )
{
mFilteredFeatures = ids;
mFilterMode = ShowFilteredList;
announcedInvalidateFilter();
invalidateFilter();
}

void QgsAttributeTableFilterModel::setFilterMode( FilterMode filterMode )
Expand All @@ -111,80 +109,14 @@ void QgsAttributeTableFilterModel::setFilterMode( FilterMode filterMode )
disconnect( SLOT( extentsChanged() ) );
}

mFilterMode = filterMode;
announcedInvalidateFilter();
}
}

void QgsAttributeTableFilterModel::selectionChanged()
{
disconnect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( masterSelectionChanged( QItemSelection, QItemSelection ) ) );
mMasterSelection->clear();

QItemSelection selection;

foreach ( QgsFeatureId fid, layer()->selectedFeaturesIds() )
{
selection.append( QItemSelectionRange( mTableModel->idToIndex( fid ) ) );
}

mMasterSelection->select( selection, QItemSelectionModel::ClearAndSelect );

if ( mFilterMode == ShowSelected )
{
announcedInvalidateFilter();
}

if ( mSelectedOnTop )
{
invalidate();
sort( sortColumn(), sortOrder() );
}

connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( masterSelectionChanged( QItemSelection, QItemSelection ) ) );
}

void QgsAttributeTableFilterModel::masterSelectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
{
disconnect( layer(), SIGNAL( selectionChanged() ), this, SLOT( selectionChanged() ) );

// Filter duplicate items (multiple columns)
QgsFeatureIds selectedFeatureIds;
QgsFeatureIds deselectedFeatureIds;

foreach ( QItemSelectionRange selectedRange, selected )
{
foreach ( QModelIndex selectedModelIdx, selectedRange.indexes() )
if ( filterMode == ShowSelected )
{
selectedFeatureIds << masterModel()->rowToId( selectedModelIdx.row() );
}
}

foreach ( QItemSelectionRange deselectedRange, deselected )
{
foreach ( QModelIndex deselectedModelIdx, deselectedRange.indexes() )
{
deselectedFeatureIds << masterModel()->rowToId( deselectedModelIdx.row() );
generateListOfVisibleFeatures();
}
}

// Change selection without emitting a signal
foreach ( QgsFeatureId seletedFid, selectedFeatureIds )
{
layer()->select( seletedFid, false );
}
foreach ( QgsFeatureId deselectedFid, deselectedFeatureIds )
{
layer()->deselect( deselectedFid, false );
}

// Now emit the signal
if ( mSyncSelection )
{
layer()->setSelectedFeatures( layer()->selectedFeaturesIds() );
mFilterMode = filterMode;
invalidateFilter();
}

connect( layer(), SIGNAL( selectionChanged() ), this, SLOT( selectionChanged() ) );
}

bool QgsAttributeTableFilterModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
Expand Down Expand Up @@ -233,14 +165,21 @@ bool QgsAttributeTableFilterModel::filterAcceptsRow( int sourceRow, const QModel
void QgsAttributeTableFilterModel::extentsChanged()
{
generateListOfVisibleFeatures();
announcedInvalidateFilter();
invalidateFilter();
}

void QgsAttributeTableFilterModel::announcedInvalidateFilter()
void QgsAttributeTableFilterModel::selectionChanged()
{
emit filterAboutToBeInvalidated();
invalidateFilter();
emit filterInvalidated();
if ( ShowSelected == mFilterMode )
{
generateListOfVisibleFeatures();
invalidateFilter();
}
else if ( mSelectedOnTop )
{
sort ( sortColumn(), sortOrder() );
invalidate();
}
}

void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
Expand Down Expand Up @@ -277,12 +216,13 @@ void QgsAttributeTableFilterModel::generateListOfVisibleFeatures()
renderContext.setExtent( mCanvas->mapRenderer()->rendererContext()->extent() );
renderContext.setMapToPixel( mCanvas->mapRenderer()->rendererContext()->mapToPixel() );
renderContext.setRendererScale( mCanvas->mapRenderer()->scale() );
renderer->startRender( renderContext, layer() );
}

filter = renderer && renderer->capabilities() & QgsFeatureRendererV2::Filter;
}

renderer->startRender( renderContext, layer() );

QgsFeatureIterator features = masterModel()->layerCache()->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setFilterRect( rect ) );

QgsFeature f;
Expand Down Expand Up @@ -319,20 +259,20 @@ QgsFeatureId QgsAttributeTableFilterModel::rowToId( const QModelIndex& row )
return masterModel()->rowToId( mapToSource( row ).row() );
}

QItemSelectionModel* QgsAttributeTableFilterModel::masterSelection()
QModelIndex QgsAttributeTableFilterModel::fidToIndex( QgsFeatureId fid )
{
return mMasterSelection;
return mapFromMaster( masterModel()->idToIndex( fid ) );
}

void QgsAttributeTableFilterModel::disableSelectionSync()
QModelIndexList QgsAttributeTableFilterModel::fidToIndexList( QgsFeatureId fid )
{
mSyncSelection = false;
}
QModelIndexList indexes;
foreach( QModelIndex idx, masterModel()->idToIndexList( fid ) )
{
indexes.append( mapFromMaster( idx ) );
}

void QgsAttributeTableFilterModel::enableSelectionSync()
{
mSyncSelection = true;
layer()->setSelectedFeatures( layer()->selectedFeaturesIds() );
return indexes;
}

QModelIndex QgsAttributeTableFilterModel::mapToMaster( const QModelIndex &proxyIndex ) const
Expand All @@ -346,9 +286,3 @@ QModelIndex QgsAttributeTableFilterModel::mapFromMaster( const QModelIndex &sour
// Master is source
return mapFromSource( sourceIndex );
}

QItemSelection QgsAttributeTableFilterModel::mapSelectionFromMaster( const QItemSelection& sourceSelection ) const
{
// Master is source
return mapSelectionFromMaster( sourceSelection );
}
67 changes: 6 additions & 61 deletions src/gui/attributetable/qgsattributetablefiltermodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@

#include "qgsvectorlayer.h" //QgsFeatureIds
#include "qgsattributetablemodel.h"
#include "qgsfeaturemodel.h"

class QgsVectorLayerCache;
class QgsMapCanvas;
class QItemSelectionModel;

class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel
class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, public QgsFeatureModel
{
Q_OBJECT

Expand All @@ -42,7 +43,6 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel
};

/**
*
*
* Make sure, the master model is already loaded, so the selection will get synchronized.
*
Expand Down Expand Up @@ -114,38 +114,13 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel
*/
QgsFeatureId rowToId( const QModelIndex& row );

/**
* Returns a selection model which is mapped to the sourceModel (tableModel) of this proxy.
* This selection also contains the features not visible because of the current filter.
* Views using this filter model may update this selection and subscribe to changes in
* this selection. This selection will synchronize itself with the selection on the map
* canvas.
*
* @return The master selection
*/
QItemSelectionModel* masterSelection();

/**
* Disables selection synchronisation with the map canvas. Changes to the selection in the master
* model are propagated to the layer, but no redraw is requested until @link enableSelectionSync() @endlink
* is called.
*/
void disableSelectionSync();

/**
* Enables selection synchronisation with the map canvas. Changes to the selection in the master
* are propagated and upon every change, a redraw will be requested. This method will update the
* selection to account for any cached selection change since @link disableSelectionSync() @endlink
* was called.
*/
void enableSelectionSync();
QModelIndex fidToIndex( QgsFeatureId fid );
QModelIndexList fidToIndexList( QgsFeatureId fid );

virtual QModelIndex mapToMaster( const QModelIndex &proxyIndex ) const;

virtual QModelIndex mapFromMaster( const QModelIndex &sourceIndex ) const;

virtual QItemSelection mapSelectionFromMaster( const QItemSelection& sourceSelection ) const;

protected:
/**
* Returns true if the source row will be accepted
Expand All @@ -167,45 +142,16 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel
*/
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;

/**
* Calls invalidateFilter on the underlying QSortFilterProxyModel, but emits the signals
* filterAboutToBeInvalidated before and the signal filterInvalidated after the changes on the
* filter happen.
*/
void announcedInvalidateFilter();



public slots:
/**
* Is called upon every change of the selection on the map canvas.
* When an update is signalled, the filter is updated and invalidated if needed.
*
*/
void selectionChanged();

void masterSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected );

/**
* Is called upon every change of the visible extents on the map canvas.
* When a change is signalled, the filter is updated and invalidated if needed.
*
*/
void extentsChanged();

signals:
/**
* This signal is emitted, before the filter is invalidated. With the help of this signal,
* selections of views attached to this can disable synchronisation with the master selection
* before items currently not visible with the filter get removed from the selection.
*/
void filterAboutToBeInvalidated();

/**
* Is called after the filter has been invalidated and recomputed.
* See filterAboutToBeInvalidated.
*/
void filterInvalidated();
private slots:
void selectionChanged();

private:
QgsFeatureIds mFilteredFeatures;
Expand All @@ -214,7 +160,6 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel
bool mSelectedOnTop;
QItemSelectionModel* mMasterSelection;
QgsAttributeTableModel* mTableModel;
bool mSyncSelection;
};

#endif
30 changes: 29 additions & 1 deletion src/gui/attributetable/qgsattributetablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,19 @@ QModelIndex QgsAttributeTableModel::idToIndex( QgsFeatureId id ) const
return index( idToRow( id ), 0 );
}

QModelIndexList QgsAttributeTableModel::idToIndexList(QgsFeatureId id) const
{
QModelIndexList indexes;

int row = idToRow( id );
for ( int column = 0; column < columnCount(); ++column )
{
indexes.append( index( row, column ) );
}

return indexes;
}

QgsFeatureId QgsAttributeTableModel::rowToId( const int row ) const
{
if ( !mRowIdMap.contains( row ) )
Expand Down Expand Up @@ -449,15 +462,30 @@ QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orient

QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() || ( role != Qt::TextAlignmentRole && role != Qt::DisplayRole && role != Qt::EditRole && role != SortRole ) )
if ( !index.isValid() ||
( role != Qt::TextAlignmentRole
&& role != Qt::DisplayRole
&& role != Qt::EditRole
&& role != SortRole
&& role != FeatureIdRole
&& role != FieldIndexRole
)
)
return QVariant();

QgsFeatureId rowId = rowToId( index.row() );

if ( role == FeatureIdRole )
return rowId;

if ( index.column() >= mFieldCount )
return role == Qt::DisplayRole ? rowId : QVariant();

int fieldId = mAttributes[ index.column()];

if ( role == FieldIndexRole )
return fieldId;

const QgsField& field = layer()->pendingFields()[ fieldId ];

QVariant::Type fldType = field.type();
Expand Down
6 changes: 5 additions & 1 deletion src/gui/attributetable/qgsattributetablemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
public:
enum Role
{
SortRole = Qt::UserRole + 1
SortRole = Qt::UserRole + 1,
FeatureIdRole = Qt::UserRole + 2,
FieldIndexRole = Qt::UserRole + 3
};

public:
Expand Down Expand Up @@ -130,6 +132,8 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel

QModelIndex idToIndex( QgsFeatureId id ) const;

QModelIndexList idToIndexList( QgsFeatureId id ) const;

/**
* get field index from column
*/
Expand Down
132 changes: 81 additions & 51 deletions src/gui/attributetable/qgsattributetableview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,32 @@
#include "qgsvectordataprovider.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsfeatureselectionmodel.h"

QgsAttributeTableView::QgsAttributeTableView( QWidget *parent )
: QTableView( parent ),
mMasterModel( NULL ),
mFilterModel( NULL ),
mActionPopup( NULL )
: QTableView( parent )
, mMasterModel( NULL )
, mFilterModel( NULL )
, mFeatureSelectionModel( NULL )
, mActionPopup( NULL )
{
QSettings settings;
restoreGeometry( settings.value( "/BetterAttributeTable/geometry" ).toByteArray() );

verticalHeader()->setDefaultSectionSize( 20 );
horizontalHeader()->setHighlightSections( false );

setItemDelegate( new QgsAttributeTableDelegate( this ) );
mTableDelegate = new QgsAttributeTableDelegate( this );
setItemDelegate( mTableDelegate );

setSelectionBehavior( QAbstractItemView::SelectRows );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setSortingEnabled( true );

verticalHeader()->viewport()->installEventFilter( this );

connect( verticalHeader(), SIGNAL( sectionPressed(int) ), this, SLOT(selectRow(int) ) );
connect( verticalHeader(), SIGNAL( sectionEntered(int) ), this, SLOT(_q_selectRow(int) ) );
}

QgsAttributeTableView::~QgsAttributeTableView()
Expand All @@ -70,6 +76,11 @@ void QgsAttributeTableView::setCanvasAndLayerCache( QgsMapCanvas *canvas, QgsVec

mFilterModel = new QgsAttributeTableFilterModel( canvas, mMasterModel, mMasterModel );
setModel( mFilterModel );
delete mFeatureSelectionModel;
mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, layerCache->layer (), mFilterModel );
connect( mFeatureSelectionModel, SIGNAL(requestRepaint(QModelIndexList)), this, SLOT( repaintRequested(QModelIndexList) ) );
connect( mFeatureSelectionModel, SIGNAL( requestRepaint() ), this, SLOT( repaintRequested() ) );
setSelectionModel ( mFeatureSelectionModel );

delete oldModel;
delete filterModel;
Expand All @@ -82,11 +93,11 @@ bool QgsAttributeTableView::eventFilter(QObject *object, QEvent *event)
switch ( event->type() )
{
case QEvent::MouseButtonPress:
mFilterModel->disableSelectionSync();
mFeatureSelectionModel->enableSync( false );
break;

case QEvent::MouseButtonRelease:
mFilterModel->enableSelectionSync();
mFeatureSelectionModel->enableSync( true );
break;

default:
Expand All @@ -101,34 +112,28 @@ void QgsAttributeTableView::setModel( QgsAttributeTableFilterModel* filterModel
if ( mFilterModel )
{
// Cleanup old model stuff if present
disconnect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );

disconnect( mFilterModel, SIGNAL( filterAboutToBeInvalidated() ), this, SLOT( onFilterAboutToBeInvalidated() ) );
disconnect( mFilterModel, SIGNAL( filterInvalidated() ), this, SLOT( onFilterInvalidated() ) );
}

mFilterModel = filterModel;
QTableView::setModel( filterModel );

delete mFeatureSelectionModel;
mFeatureSelectionModel = new QgsFeatureSelectionModel( mFilterModel, mFilterModel, mFilterModel->layer(), mFilterModel );
setSelectionModel ( mFeatureSelectionModel );
mTableDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
connect( mFeatureSelectionModel, SIGNAL(requestRepaint(QModelIndexList)), this, SLOT( repaintRequested(QModelIndexList) ) );
connect( mFeatureSelectionModel, SIGNAL(requestRepaint()), this, SLOT( repaintRequested() ) );

if ( filterModel )
{
// Connect new model stuff
mMasterSelection = mFilterModel->masterSelection();

connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );

connect( mFilterModel, SIGNAL( filterAboutToBeInvalidated() ), SLOT( onFilterAboutToBeInvalidated() ) );
connect( mFilterModel, SIGNAL( filterInvalidated() ), SLOT( onFilterInvalidated() ) );
}
}

QItemSelectionModel* QgsAttributeTableView::masterSelection()
{
return mMasterSelection;
}

void QgsAttributeTableView::closeEvent( QCloseEvent *e )
{
Q_UNUSED( e );
Expand Down Expand Up @@ -179,47 +184,24 @@ void QgsAttributeTableView::keyPressEvent( QKeyEvent *event )
}
}

void QgsAttributeTableView::onFilterAboutToBeInvalidated()
void QgsAttributeTableView::repaintRequested( QModelIndexList indexes )
{
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
}

void QgsAttributeTableView::onFilterInvalidated()
{
QItemSelection localSelection = mFilterModel->mapSelectionFromSource( mMasterSelection->selection() );
selectionModel()->select( localSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
}

void QgsAttributeTableView::onSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
{
disconnect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
QItemSelection masterSelected = mFilterModel->mapSelectionToSource( selected );
QItemSelection masterDeselected = mFilterModel->mapSelectionToSource( deselected );

mMasterSelection->select( masterSelected, QItemSelectionModel::Select );
mMasterSelection->select( masterDeselected, QItemSelectionModel::Deselect );
connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
foreach( const QModelIndex index, indexes )
{
update( index );
}
}

void QgsAttributeTableView::onMasterSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
void QgsAttributeTableView::repaintRequested()
{
Q_UNUSED( selected )
Q_UNUSED( deselected )
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );

// Synchronizing the whole selection seems to work faster than using the deltas (Deselecting takes pretty long)
QItemSelection localSelection = mFilterModel->mapSelectionFromSource( mMasterSelection->selection() );
selectionModel()->select( localSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );

connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
setDirtyRegion( viewport()->rect() );
}

void QgsAttributeTableView::selectAll()
{
QItemSelection selection;
selection.append( QItemSelectionRange( mFilterModel->index( 0, 0 ), mFilterModel->index( mFilterModel->rowCount() - 1, 0 ) ) );
selectionModel()->select( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
}

void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent* event )
Expand Down Expand Up @@ -252,3 +234,51 @@ void QgsAttributeTableView::contextMenuEvent( QContextMenuEvent* event )
mActionPopup->popup( event->globalPos() );
}
}

void QgsAttributeTableView::selectRow( int row )
{
selectRow( row, true );
}

void QgsAttributeTableView::_q_selectRow( int row )
{
selectRow( row, false );
}

void QgsAttributeTableView::selectRow( int row, bool anchor )
{
if ( selectionBehavior() == QTableView::SelectColumns
|| ( selectionMode() == QTableView::SingleSelection
&& selectionBehavior() == QTableView::SelectItems))
return;

if ( row >= 0 && row < model()->rowCount() )
{
int column = horizontalHeader()->logicalIndexAt( isRightToLeft() ? viewport()->width() : 0 );
QModelIndex index = model()->index( row, column );
QItemSelectionModel::SelectionFlags command = selectionCommand( index );
selectionModel()->setCurrentIndex( index, QItemSelectionModel::NoUpdate );
if ( ( anchor && !( command & QItemSelectionModel::Current ) )
|| ( selectionMode() == QTableView::SingleSelection ) )
mRowSectionAnchor = row;

if ( selectionMode() != QTableView::SingleSelection
&& command.testFlag( QItemSelectionModel::Toggle ) )
{
if ( anchor )
mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
command &= ~QItemSelectionModel::Toggle;
command |= mCtrlDragSelectionFlag;
if ( !anchor )
command |= QItemSelectionModel::Current;
}

QModelIndex tl = model()->index( qMin( mRowSectionAnchor, row ), 0 );
QModelIndex br = model()->index( qMax( mRowSectionAnchor, row ), model()->columnCount() - 1 );
if ( verticalHeader()->sectionsMoved() && tl.row() != br.row() )
setSelection( visualRect( tl ) | visualRect( br ), command );
else
mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
}
}
23 changes: 11 additions & 12 deletions src/gui/attributetable/qgsattributetableview.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
class QgsAttributeTableModel;
class QgsAttributeTableFilterModel;
class QgsVectorLayerCache;

class QgsFeatureSelectionModel;
class QgsAttributeTableDelegate;
class QgsMapCanvas;
class QgsVectorLayer;
class QMenu;
Expand All @@ -48,12 +49,6 @@ class GUI_EXPORT QgsAttributeTableView : public QTableView

virtual void setModel( QgsAttributeTableFilterModel* filterModel );

/**
* The selection used for synchronisation with other views.
*
*/
QItemSelectionModel* masterSelection();

/**
* Autocreates the models
* @param layerCache The @link QgsVectorLayerCache @endlink to use ( as backend )
Expand Down Expand Up @@ -137,19 +132,23 @@ class GUI_EXPORT QgsAttributeTableView : public QTableView
void finished();

public slots:
void onFilterAboutToBeInvalidated();
void onFilterInvalidated();
void onSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected );
void onMasterSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected );
void repaintRequested( QModelIndexList indexes );
void repaintRequested();
virtual void selectAll();
virtual void selectRow( int row );
virtual void _q_selectRow( int row );

private:
void selectRow( int row, bool anchor );
QgsAttributeTableModel* mMasterModel;
QgsAttributeTableFilterModel* mFilterModel;
QgsFeatureSelectionModel* mFeatureSelectionModel;
QgsAttributeTableDelegate* mTableDelegate;
QAbstractItemModel* mModel; // Most likely the filter model
QMenu *mActionPopup;
QgsVectorLayerCache* mLayerCache;
QItemSelectionModel* mMasterSelection;
int mRowSectionAnchor;
QItemSelectionModel::SelectionFlag mCtrlDragSelectionFlag;
};

#endif
3 changes: 3 additions & 0 deletions src/gui/attributetable/qgsdualview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ void QgsDualView::initModels( QgsMapCanvas* mapCanvas )

void QgsDualView::on_mFeatureList_currentEditSelectionChanged( const QgsFeature &feat )
{
if ( !feat.isValid() )
return;

// Backup old dialog and delete only after creating the new dialog, so we can "hot-swap" the contained QgsFeature
QgsAttributeDialog* oldDialog = mAttributeDialog;

Expand Down
15 changes: 5 additions & 10 deletions src/gui/attributetable/qgsfeaturelistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ QVariant QgsFeatureListModel::data( const QModelIndex &index, int role ) const
return QVariant::fromValue( featInfo );
}

return QVariant();
return sourceModel()->data( mapToSource( index ), role );
}

Qt::ItemFlags QgsFeatureListModel::flags( const QModelIndex &index ) const
Expand All @@ -102,11 +102,6 @@ QgsAttributeTableModel* QgsFeatureListModel::masterModel()
return mFilterModel->masterModel();
}

QItemSelectionModel* QgsFeatureListModel::masterSelection()
{
return mFilterModel->masterSelection();
}

bool QgsFeatureListModel::setDisplayExpression( const QString expression )
{
const QgsFields fields = mFilterModel->layer()->dataProvider()->fields();
Expand Down Expand Up @@ -239,12 +234,12 @@ int QgsFeatureListModel::rowCount( const QModelIndex& parent ) const
return sourceModel()->rowCount();
}

void QgsFeatureListModel::disableSelectionSync()
QModelIndex QgsFeatureListModel::fidToIndex(QgsFeatureId fid)
{
mFilterModel->disableSelectionSync();
return mapFromMaster( masterModel()->idToIndex( fid ) );
}

void QgsFeatureListModel::enableSelectionSync()
QModelIndexList QgsFeatureListModel::fidToIndexList(QgsFeatureId fid)
{
mFilterModel->enableSelectionSync();
return QModelIndexList() << fidToIndex( fid );
}
30 changes: 4 additions & 26 deletions src/gui/attributetable/qgsfeaturelistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
#include <QVariant>
#include <QItemSelectionModel>

#include "qgsfeaturemodel.h"
#include "qgsfeature.h" // QgsFeatureId

class QgsAttributeTableFilterModel;
class QgsAttributeTableModel;
class QgsVectorLayerCache;

class QgsFeatureListModel : public QAbstractProxyModel
class QgsFeatureListModel : public QAbstractProxyModel, public QgsFeatureModel
{
Q_OBJECT

Expand Down Expand Up @@ -41,17 +42,6 @@ class QgsFeatureListModel : public QAbstractProxyModel

QgsAttributeTableModel* masterModel();

/**
* Returns a selection model which is mapped to the sourceModel (tableModel) of this proxy.
* This selection also contains the features not visible because of the current filter.
* Views using this filter model may update this selection and subscribe to changes in
* this selection. This selection will synchronize itself with the selection on the map
* canvas.
*
* @return The master selection
*/
QItemSelectionModel* masterSelection();

/**
* @param expression A {@link QgsExpression} compatible string.
* @return true if the expression could be set, false if there was a parse error.
Expand Down Expand Up @@ -85,20 +75,8 @@ class QgsFeatureListModel : public QAbstractProxyModel
virtual int columnCount( const QModelIndex&parent = QModelIndex() ) const;
virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const;

/**
* Disables selection synchronisation with the map canvas. Changes to the selection in the master
* model are propagated to the layer, but no redraw is requested until @link enableSelectionSync() @endlink
* is called.
*/
void disableSelectionSync();

/**
* Enables selection synchronisation with the map canvas. Changes to the selection in the master
* are propagated and upon every change, a redraw will be requested. This method will update the
* selection to account for any cached selection change since @link disableSelectionSync() @endlink
* was called.
*/
void enableSelectionSync();
QModelIndex fidToIndex( QgsFeatureId fid );
QModelIndexList fidToIndexList( QgsFeatureId fid );

public slots:
void onBeginRemoveRows( const QModelIndex& parent, int first, int last );
Expand Down
216 changes: 140 additions & 76 deletions src/gui/attributetable/qgsfeaturelistview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
#include "qgsmapcanvas.h"
#include "qgsfeaturelistviewdelegate.h"
#include "qgsfeaturelistmodel.h"
#include "qgsfeatureselectionmodel.h"
#include <QSet>

QgsFeatureListView::QgsFeatureListView( QWidget *parent )
: QListView( parent ),
mCurrentEditSelectionModel( NULL ),
mItemDelegate( NULL ),
mEditSelectionDrag( false )
: QListView( parent )
, mCurrentEditSelectionModel( NULL )
, mFeatureSelectionModel( NULL )
, mItemDelegate( NULL )
, mEditSelectionDrag( false )
{
setSelectionMode( QAbstractItemView::ExtendedSelection );
}
Expand All @@ -49,13 +51,9 @@ void QgsFeatureListView::setModel( QgsFeatureListModel* featureListModel )
QListView::setModel( featureListModel );
mModel = featureListModel;

mMasterSelection = featureListModel->masterSelection();

connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );

connect( mModel->sourceModel(), SIGNAL( filterAboutToBeInvalidated() ), SLOT( onFilterAboutToBeInvalidated() ) );
connect( mModel->sourceModel(), SIGNAL( filterInvalidated() ), SLOT( onFilterInvalidated() ) );
delete mFeatureSelectionModel;
mFeatureSelectionModel = new QgsFeatureSelectionModel( featureListModel, featureListModel, featureListModel->layerCache ()->layer(), this );
setSelectionModel ( mFeatureSelectionModel );

mCurrentEditSelectionModel = new QItemSelectionModel( mModel->masterModel(), this );

Expand All @@ -68,6 +66,10 @@ void QgsFeatureListView::setModel( QgsFeatureListModel* featureListModel )
mItemDelegate->setEditSelectionModel( mCurrentEditSelectionModel );
setItemDelegate( mItemDelegate );

mItemDelegate->setFeatureSelectionModel( mFeatureSelectionModel );
connect( mFeatureSelectionModel, SIGNAL(requestRepaint(QModelIndexList)), this, SLOT( repaintRequested(QModelIndexList) ) );
connect( mFeatureSelectionModel, SIGNAL(requestRepaint()), this, SLOT( repaintRequested() ) );

connect( mCurrentEditSelectionModel, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( editSelectionChanged( QItemSelection, QItemSelection ) ) );
}

Expand All @@ -94,50 +96,21 @@ QString QgsFeatureListView::parserErrorString()
return mModel->parserErrorString();
}

void QgsFeatureListView::mouseMoveEvent( QMouseEvent *event )
{
QPoint pos = event->pos();

if ( mEditSelectionDrag )
{
QModelIndex index = mModel->mapToMaster( indexAt( pos ) );

mCurrentEditSelectionModel->select( index, QItemSelectionModel::ClearAndSelect );
}
else
{
QListView::mouseMoveEvent( event );
}
}

void QgsFeatureListView::mousePressEvent( QMouseEvent *event )
{
QPoint pos = event->pos();

QModelIndex index = mModel->mapToMaster( indexAt( pos ) );

if ( QgsFeatureListViewDelegate::EditElement == mItemDelegate->positionToElement( event->pos() ) )
{
mEditSelectionDrag = true;
QModelIndex index = mModel->mapToMaster( indexAt( pos ) );

mCurrentEditSelectionModel->select( index, QItemSelectionModel::ClearAndSelect );
}
else
{
mModel->disableSelectionSync();
QListView::mousePressEvent( event );
}
}

void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
{
if ( mEditSelectionDrag )
{
mEditSelectionDrag = false;
}
else
{
QListView::mouseReleaseEvent( event );
mModel->enableSelectionSync();
mFeatureSelectionModel->enableSync( false );
selectRow( index, true );
}
}

Expand All @@ -160,59 +133,150 @@ void QgsFeatureListView::editSelectionChanged( QItemSelection deselected, QItemS
}
}

void QgsFeatureListView::onFilterAboutToBeInvalidated()
void QgsFeatureListView::selectAll()
{
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
QItemSelection selection;
selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );

mFeatureSelectionModel->selectFeatures( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
}

void QgsFeatureListView::onFilterInvalidated()
void QgsFeatureListView::setEditSelection( const QgsFeatureIds &fids )
{
QItemSelection localSelection = mModel->mapSelectionFromMaster( mMasterSelection->selection() );
selectionModel()->select( localSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
QItemSelection selection;

foreach ( QgsFeatureId fid, fids )
{
selection.append( QItemSelectionRange( mModel->mapToMaster( mModel->fidToIdx( fid ) ) ) );
}

mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
}

void QgsFeatureListView::onSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
void QgsFeatureListView::repaintRequested( QModelIndexList indexes )
{
disconnect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
QItemSelection masterSelected = mModel->mapSelectionToMaster( selected );
QItemSelection masterDeselected = mModel->mapSelectionToMaster( deselected );
foreach( const QModelIndex index, indexes )
{
update( index );
}
}

mMasterSelection->select( masterSelected, QItemSelectionModel::Select );
mMasterSelection->select( masterDeselected, QItemSelectionModel::Deselect );
connect( mMasterSelection, SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onMasterSelectionChanged( QItemSelection, QItemSelection ) ) );
void QgsFeatureListView::repaintRequested()
{
setDirtyRegion( viewport()->rect() );
}

void QgsFeatureListView::onMasterSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
/*!
This function is called with the given \a event when a mouse move event is
sent to the widget. If a selection is in progress and new items are moved
over the selection is extended; if a drag is in progress it is continued.
*/

void QgsFeatureListView::mouseMoveEvent(QMouseEvent *event)
{
Q_UNUSED( selected )
Q_UNUSED( deselected )
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
QPoint pos = event->pos();

// Synchronizing the whole selection seems to work faster than using the deltas (Deselecting takes pretty long)
QItemSelection localSelection = mModel->mapSelectionFromMaster( mMasterSelection->selection() );
selectionModel()->select( localSelection, QItemSelectionModel::ClearAndSelect );
QModelIndex index = mModel->mapToMaster( indexAt( pos ) );

connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
if ( mEditSelectionDrag )
{
mCurrentEditSelectionModel->select( index, QItemSelectionModel::ClearAndSelect );
}
else
{
selectRow( index, false );
}
}

void QgsFeatureListView::selectAll()
/*!
This function is called with the given \a event when a mouse button is released,
after a mouse press event on the widget. If a user presses the mouse inside your
widget and then drags the mouse to another location before releasing the mouse button,
your widget receives the release event. The function will emit the clicked() signal if an
item was being pressed.
*/
void QgsFeatureListView::mouseReleaseEvent( QMouseEvent *event )
{
disconnect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
QItemSelection selection;
selection.append( QItemSelectionRange( mModel->index( 0, 0 ), mModel->index( mModel->rowCount() - 1, 0 ) ) );
selectionModel()->select( selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( onSelectionChanged( QItemSelection, QItemSelection ) ) );
Q_UNUSED( event );

if ( mEditSelectionDrag )
{
mEditSelectionDrag = false;
}
else
{
mFeatureSelectionModel->enableSync( true );
}
}

void QgsFeatureListView::setEditSelection( const QgsFeatureIds &fids )
void QgsFeatureListView::keyPressEvent( QKeyEvent *event )
{
QItemSelection selection;
if ( Qt::Key_Up == event->key () || Qt::Key_Down == event->key() )
{
int currentRow = 0;
if ( 0 != mCurrentEditSelectionModel->selectedIndexes().count() )
{
QModelIndex localIndex = mModel->mapFromMaster( mCurrentEditSelectionModel->selectedIndexes().first() );
currentRow = localIndex.row();
}

QModelIndex newLocalIndex;
QModelIndex newIndex;

switch ( event->key() )
{
case Qt::Key_Up:
newLocalIndex = mModel->index( currentRow - 1, 0 );
newIndex = mModel->mapToMaster( newLocalIndex );
if ( newIndex.isValid() )
{
mCurrentEditSelectionModel->select( newIndex, QItemSelectionModel::ClearAndSelect );
scrollTo( newLocalIndex );
}
break;

case Qt::Key_Down:
newLocalIndex = mModel->index( currentRow + 1, 0 );
newIndex = mModel->mapToMaster( newLocalIndex );
if ( newIndex.isValid() )
{
mCurrentEditSelectionModel->select( newIndex, QItemSelectionModel::ClearAndSelect );
scrollTo( newLocalIndex );
}
break;

default:
break;
}
}
else
{
QListView::keyPressEvent( event );
}
}

foreach ( QgsFeatureId fid, fids )
void QgsFeatureListView::selectRow( const QModelIndex& index, bool anchor )
{
QItemSelectionModel::SelectionFlags command = selectionCommand( index );
int row = index.row();

if ( anchor )
mRowAnchor = row;

if ( selectionMode() != QListView::SingleSelection
&& command.testFlag( QItemSelectionModel::Toggle ) )
{
selection.append( QItemSelectionRange( mModel->mapToMaster( mModel->fidToIdx( fid ) ) ) );
if ( anchor )
mCtrlDragSelectionFlag = mFeatureSelectionModel->isSelected( index )
? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
command &= ~QItemSelectionModel::Toggle;
command |= mCtrlDragSelectionFlag;
if ( !anchor )
command |= QItemSelectionModel::Current;
}

mCurrentEditSelectionModel->select( selection, QItemSelectionModel::ClearAndSelect );
QModelIndex tl = model()->index( qMin( mRowAnchor, row ), 0 );
QModelIndex br = model()->index( qMax( mRowAnchor, row ), model()->columnCount() - 1 );

mFeatureSelectionModel->selectFeatures( QItemSelection( tl, br ), command );
}
20 changes: 11 additions & 9 deletions src/gui/attributetable/qgsfeaturelistview.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
#define QGSATTRIBUTELISTVIEW_H

#include <QListView>
#include <qdebug.h>

#include "qgsfeature.h" // For QgsFeatureIds

class QgsAttributeTableFilterModel;
class QgsFeatureListModel;
class QgsFeatureSelectionModel;
class QgsAttributeTableModel;
class QgsVectorLayer;
class QgsVectorLayerCache;
Expand Down Expand Up @@ -102,6 +104,7 @@ class GUI_EXPORT QgsFeatureListView : public QListView
virtual void mouseMoveEvent( QMouseEvent *event );
virtual void mousePressEvent( QMouseEvent *event );
virtual void mouseReleaseEvent( QMouseEvent *event );
virtual void keyPressEvent( QKeyEvent *event );

signals:
/**
Expand Down Expand Up @@ -130,23 +133,22 @@ class GUI_EXPORT QgsFeatureListView : public QListView
*/
virtual void selectAll();

void repaintRequested( QModelIndexList indexes );
void repaintRequested();

private slots:
void editSelectionChanged( QItemSelection deselected, QItemSelection selected );

void onFilterAboutToBeInvalidated();

void onFilterInvalidated();

void onSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected );

void onMasterSelectionChanged( const QItemSelection& selected, const QItemSelection& deselected );

private:
void selectRow(const QModelIndex &index, bool anchor );

QgsFeatureListModel *mModel;
QItemSelectionModel* mCurrentEditSelectionModel;
QItemSelectionModel* mMasterSelection;
QgsFeatureSelectionModel* mFeatureSelectionModel;
QgsFeatureListViewDelegate* mItemDelegate;
bool mEditSelectionDrag; // Is set to true when the user initiated a left button click over an edit button and still keeps pressing /**< TODO */
int mRowAnchor;
QItemSelectionModel::SelectionFlags mCtrlDragSelectionFlag;
};

#endif
9 changes: 8 additions & 1 deletion src/gui/attributetable/qgsfeaturelistviewdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "qgsfeaturelistmodel.h"
#include "qgsapplication.h"
#include "qgsvectorlayereditbuffer.h"
#include "qgsfeatureselectionmodel.h"

#include <QHBoxLayout>
#include <QPushButton>
Expand All @@ -14,6 +15,7 @@

QgsFeatureListViewDelegate::QgsFeatureListViewDelegate( QgsFeatureListModel *listModel, QObject *parent )
: QItemDelegate( parent )
, mFeatureSelectionModel( NULL )
, mListModel( listModel )
{
}
Expand All @@ -30,6 +32,11 @@ QgsFeatureListViewDelegate::Element QgsFeatureListViewDelegate::positionToElemen
}
}

void QgsFeatureListViewDelegate::setFeatureSelectionModel(QgsFeatureSelectionModel *featureSelectionModel)
{
mFeatureSelectionModel = featureSelectionModel;
}

void QgsFeatureListViewDelegate::setEditSelectionModel( QItemSelectionModel* editSelectionModel )
{
mEditSelectionModel = editSelectionModel;
Expand All @@ -54,7 +61,7 @@ void QgsFeatureListViewDelegate::paint( QPainter *painter, const QStyleOptionVie

QPixmap icon;

if ( option.state.testFlag( QStyle::State_Selected ) )
if ( mFeatureSelectionModel->isSelected ( index ) )
{
// Item is selected
icon = QgsApplication::getThemePixmap( "/mIconSelected.svg" );
Expand Down
5 changes: 5 additions & 0 deletions src/gui/attributetable/qgsfeaturelistviewdelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class QgsVectorLayer;
class QgsFeatureListModel;
class QgsFeatureSelectionModel;
class QPosition;

class QgsFeatureListViewDelegate : public QItemDelegate
Expand All @@ -26,8 +27,11 @@ class QgsFeatureListViewDelegate : public QItemDelegate
explicit QgsFeatureListViewDelegate( QgsFeatureListModel* listModel, QObject *parent = 0 );

void setEditSelectionModel( QItemSelectionModel* editSelectionModel );

Element positionToElement( const QPoint& pos );

void setFeatureSelectionModel( QgsFeatureSelectionModel* featureSelectionModel );

signals:
void editButtonClicked( QModelIndex& index );

Expand All @@ -38,6 +42,7 @@ class QgsFeatureListViewDelegate : public QItemDelegate
virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;

private:
QgsFeatureSelectionModel* mFeatureSelectionModel;
QItemSelectionModel* mEditSelectionModel;
QgsFeatureListModel* mListModel;
};
Expand Down
13 changes: 13 additions & 0 deletions src/gui/attributetable/qgsfeaturemodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef QGSFEATUREMODEL_H
#define QGSFEATUREMODEL_H

#include "qgsfeature.h" // QgsFeatureId
#include <QModelIndex>

class QgsFeatureModel
{
public:
virtual QModelIndex fidToIndex( QgsFeatureId fid ) = 0;
};

#endif // QGSFEATUREMODEL_H
173 changes: 173 additions & 0 deletions src/gui/attributetable/qgsfeatureselectionmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "qgsattributetablemodel.h"
#include "qgsfeaturemodel.h"
#include "qgsfeatureselectionmodel.h"
#include "qgsvectorlayer.h"
#include <qdebug.h>

QgsFeatureSelectionModel::QgsFeatureSelectionModel( QAbstractItemModel* model, QgsFeatureModel* featureModel, QgsVectorLayer* layer, QObject* parent )
: QItemSelectionModel( model, parent )
, mFeatureModel( featureModel )
, mLayer( layer )
, mSyncEnabled( true )
, mClearAndSelectBuffer( false )
{
connect( mLayer, SIGNAL( selectionChanged(QgsFeatureIds,QgsFeatureIds,bool) ), this, SLOT( layerSelectionChanged(QgsFeatureIds,QgsFeatureIds,bool)) );
}

void QgsFeatureSelectionModel::enableSync( bool enable )
{
mSyncEnabled = enable;

if ( mSyncEnabled )
{
if ( mClearAndSelectBuffer )
{
mLayer->setSelectedFeatures( mSelectedBuffer );
}
else
{
mLayer->select( mSelectedBuffer );
mLayer->deselect( mDeselectedBuffer );
}

mSelectedBuffer.clear();
mDeselectedBuffer.clear();
mClearAndSelectBuffer = false;
}
}

bool QgsFeatureSelectionModel::isSelected( QgsFeatureId fid )
{
if ( mSelectedBuffer.contains( fid ) )
return true;

if ( mDeselectedBuffer.contains( fid ) )
return false;

if ( mLayer->selectedFeaturesIds().contains( fid ) )
return true;

return false;
}

bool QgsFeatureSelectionModel::isSelected( const QModelIndex &index )
{
return isSelected( index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toInt() );
}

void QgsFeatureSelectionModel::selectFeatures( const QItemSelection &selection, QItemSelectionModel::SelectionFlags command )
{
QgsFeatureIds ids;

foreach( const QModelIndex index, selection.indexes() )
{
QgsFeatureId id = index.model()->data( index, QgsAttributeTableModel::FeatureIdRole ).toInt();

ids << id;
}

disconnect( mLayer, SIGNAL( selectionChanged(QgsFeatureIds,QgsFeatureIds,bool) ), this, SLOT( layerSelectionChanged(QgsFeatureIds,QgsFeatureIds,bool)) );

if ( command.testFlag ( QItemSelectionModel::ClearAndSelect ) )
{
if ( !mSyncEnabled )
{
mClearAndSelectBuffer = true;
foreach ( QgsFeatureId id, ids )
{
if ( !mDeselectedBuffer.remove( id ) )
{
mSelectedBuffer.insert( id );
}
}
}
else
{
mLayer->setSelectedFeatures( ids );
}
}
else if ( command.testFlag ( QItemSelectionModel::Select ) )
{
if ( !mSyncEnabled )
{
foreach ( QgsFeatureId id, ids )
{
if ( !mDeselectedBuffer.remove( id ) )
{
mSelectedBuffer.insert( id );
}
}
}
else
{
mLayer->select( ids );
}
}
else if ( command.testFlag ( QItemSelectionModel::Deselect ) )
{
if ( !mSyncEnabled )
{
foreach ( QgsFeatureId id, ids )
{
if ( !mSelectedBuffer.remove( id ) )
{
mDeselectedBuffer.insert( id );
}
}
}
else
{
mLayer->deselect( ids );
}
}

connect( mLayer, SIGNAL( selectionChanged(QgsFeatureIds,QgsFeatureIds,bool) ), this, SLOT( layerSelectionChanged(QgsFeatureIds,QgsFeatureIds,bool)) );

QModelIndexList updatedIndexes;
foreach ( QModelIndex idx, selection.indexes() )
{
updatedIndexes.append( expandIndexToRow ( idx ) );
}

emit requestRepaint( updatedIndexes );
}

void QgsFeatureSelectionModel::layerSelectionChanged( QgsFeatureIds selected, QgsFeatureIds deselected, bool clearAndSelect )
{
if ( clearAndSelect )
{
emit requestRepaint();
}
else
{
QModelIndexList updatedIndexes;
foreach ( QgsFeatureId fid, selected )
{
updatedIndexes.append( expandIndexToRow( mFeatureModel->fidToIndex( fid ) ) );
}

foreach ( QgsFeatureId fid, deselected )
{
updatedIndexes.append( expandIndexToRow( mFeatureModel->fidToIndex( fid ) ) );
}

emit requestRepaint( updatedIndexes );
}
}

QModelIndexList QgsFeatureSelectionModel::expandIndexToRow( const QModelIndex& index ) const
{
QModelIndexList indexes;
const QAbstractItemModel* model = index.model();
int row = index.row();

if ( !model )
return indexes;

for( int column = 0; column < model->columnCount(); ++column )
{
indexes.append( model->index( row, column ) );
}

return indexes;
}
99 changes: 99 additions & 0 deletions src/gui/attributetable/qgsfeatureselectionmodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#ifndef QGSFEATURESELECTIONMODEL_H
#define QGSFEATURESELECTIONMODEL_H

#include <QItemSelectionModel>

#include "qgsfeature.h"

class QgsVectorLayer;
class QgsFeatureModel;

class QgsFeatureSelectionModel : public QItemSelectionModel
{
Q_OBJECT
public:
explicit QgsFeatureSelectionModel( QAbstractItemModel* model, QgsFeatureModel* featureModel, QgsVectorLayer* layer, QObject* parent );

/**
* Enables or disables synchronisation to the {@link QgsVectorLayer}
* When synchronisation is disabled, any selection change will be buffered inside this selection model.
* When enabled, any buffered changes are communicated to the layer and the buffer is emptied.
* Mainly to be used for performance reasons, because selection change on the layer can cost time as it
* repaints the layer.
*
* @param enable The synchronisation status to set.
*/
void enableSync( bool enable );

/**
* Returns the selection status of a given feature id.
*
* @param fid The featureid to determine the selection status of
*
* @return The selection status
*/

virtual bool isSelected( QgsFeatureId fid );
/**
* Returns the selection status of a given QModelIndex.
*
* @param index The index to determine the selection status of
*
* @return The selection status
*/
virtual bool isSelected( const QModelIndex& index );

signals:
/**
* Request a repaint of a list of model indexes.
* Views using this model should connect to and properly process this signal.
*
* @param indexes The model indexes which need to be repainted
*/
void requestRepaint( QModelIndexList indexes );

/**
* Request a repaint of the visible items of connected views.
* Views using this model should connect to and properly process this signal.
*/
void requestRepaint();

public slots:
/**
* Overwritten to do NOTHING (we handle selection ourselves)
*
* @see selectFeatures( const QItemSelection&, SelectionFlags )
*/
virtual void select ( const QModelIndex &index, SelectionFlags command ) { Q_UNUSED( index); Q_UNUSED( command ); }

/**
* Overwritten to do NOTHING (we handle selection ourselves)
*
* @see selectFeatures( const QItemSelection&, SelectionFlags )
*/
virtual void select ( const QItemSelection &selection, SelectionFlags command ) { Q_UNUSED( selection); Q_UNUSED( command ); }

/**
* Select features on this table. Is to be used in favor of the stock select methods.
*
* @param selection The QItemSelection which will be selected
* @param command The command to apply. Select, Deselect and ClearAndSelect are processed.
*/
virtual void selectFeatures( const QItemSelection &selection, SelectionFlags command );

private slots:
virtual void layerSelectionChanged( QgsFeatureIds selected, QgsFeatureIds deselected, bool clearAndSelect );

private:
QModelIndexList expandIndexToRow( const QModelIndex& index ) const;

private:
QgsFeatureModel* mFeatureModel;
QgsVectorLayer* mLayer;
bool mSyncEnabled;
QgsFeatureIds mSelectedBuffer;
QgsFeatureIds mDeselectedBuffer;
bool mClearAndSelectBuffer;
};

#endif // QGSFEATURESELECTIONMODEL_H
6 changes: 3 additions & 3 deletions src/plugins/evis/eventbrowser/evisgenericeventbrowsergui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ eVisGenericEventBrowserGui::~eVisGenericEventBrowserGui( )
//On close, clear selected feature
if ( 0 != mVectorLayer )
{
mVectorLayer->removeSelection( false );
mVectorLayer->removeSelection();
}
}

Expand Down Expand Up @@ -532,11 +532,11 @@ void eVisGenericEventBrowserGui::displayImage( )
}

//clear any selection that may be present
mVectorLayer->removeSelection( false );
mVectorLayer->removeSelection();
if ( mFeatureIds.size( ) > 0 )
{
//select the current feature in the layer
mVectorLayer->select( mFeatureIds.at( mCurrentFeatureIndex ), true );
mVectorLayer->select( mFeatureIds.at( mCurrentFeatureIndex ) );
//get a copy of the feature
QgsFeature* myFeature = featureAtId( mFeatureIds.at( mCurrentFeatureIndex ) );

Expand Down
2 changes: 0 additions & 2 deletions src/plugins/evis/idtool/eviseventidtool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ void eVisEventIdTool::select( QgsPoint thePoint )
//Transform rectange to map coordinates
myRectangle = toLayerCoordinates( myLayer, myRectangle );

//Rather than add to the current selection, clear all selected features
myLayer->removeSelection( false );
//select features
QgsFeatureIterator fit = myLayer->getFeatures( QgsFeatureRequest().setFilterRect( myRectangle ).setFlags( QgsFeatureRequest::ExactIntersect ).setSubsetOfAttributes( QgsAttributeList() ) );

Expand Down
55 changes: 10 additions & 45 deletions tests/src/gui/testqgsdualview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <qgsapplication.h>
#include <qgsvectorlayer.h>
#include <qgsmapcanvas.h>
#include <qgsfeature.h>

class TestQgsDualView: public QObject
{
Expand All @@ -31,7 +32,7 @@ class TestQgsDualView: public QObject
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void testSelection();
void testSelectAll();

private:
QgsMapCanvas* mCanvas;
Expand Down Expand Up @@ -78,54 +79,18 @@ void TestQgsDualView::cleanup()
delete mDualView;
}

void TestQgsDualView::testSelection()
void TestQgsDualView::testSelectAll()
{
// Select some features on the map canvas
QgsFeatureIds selectedIds;
selectedIds << 1 << 2 << 3;

mPointsLayer->setSelectedFeatures( selectedIds );

// Verify the same selection applies to the table
QVERIFY( mDualView->mFilterModel->masterSelection()->selectedIndexes().count() == 3 );
QVERIFY( mDualView->mTableView->selectionModel()->selectedRows().count() == 3 );

// Set the extent, so all features are visible
mCanvas->setExtent( QgsRectangle( -139, 22, -64, 48 ) );
// The table should also only show visible items (still all)
mDualView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
QVERIFY( mDualView->mFilterModel->masterSelection()->selectedIndexes().count() == 3 );
QVERIFY( mDualView->mTableView->selectionModel()->selectedRows().count() == 3 );

// Set the extent, so no features are visible
mCanvas->setExtent( QgsRectangle( 0, 1, 0, 1 ) );

// The master selection should still hold all the features, while the currently visible should not
QVERIFY( mDualView->mFilterModel->masterSelection()->selectedIndexes().count() == 3 );
QVERIFY( mDualView->mTableView->selectionModel()->selectedRows().count() == 0 );

// Only show parts of the canvas, so only one selected feature is visible
mCanvas->setExtent( QgsRectangle( -139, 22, -100, 48 ) );
QVERIFY( mDualView->mFilterModel->masterSelection()->selectedIndexes().count() == 3 );
QVERIFY( mDualView->mTableView->selectionModel()->selectedRows().count() == 1 );
QVERIFY( mDualView->mTableView->selectionModel()->selectedRows().first().row() == 1 );

// Now the other way round...
// TODO: Fixme
mCanvas->setExtent( QgsRectangle( -139, 23, -100, 48 ) );
mDualView->mTableView->selectAll();
QVERIFY( mPointsLayer->selectedFeatureCount() == 10 );

mPointsLayer->setSelectedFeatures( QgsFeatureIds() );
mCanvas->setExtent( QgsRectangle( -110, 40, -100, 48 ) );
mDualView->mTableView->selectAll();
QVERIFY( mPointsLayer->selectedFeaturesIds().count() == 12 );

// Deselect a previously selected row
// List index 2 => fid 2
QVERIFY( mPointsLayer->selectedFeaturesIds().contains( 2 ) );
mDualView->mTableView->selectRow( 2 );
QVERIFY( false == mPointsLayer->selectedFeaturesIds().contains( 2 ) );

// Select a previously not selected row
// List index 6 => fid 13
QVERIFY( false == mPointsLayer->selectedFeaturesIds().contains( 13 ) );
mDualView->mTableView->selectRow( 6 );
QVERIFY( mPointsLayer->selectedFeaturesIds().contains( 13 ) );
QVERIFY( mPointsLayer->selectedFeatureCount() == 1 );
}

QTEST_MAIN( TestQgsDualView )
Expand Down