Skip to content

Commit 534cb41

Browse files
committedOct 9, 2015
Use a model for node editor table (fixes #13541)
This commit switches the node editor to use a model backend rather then inserting and updating every node on every edit. Fixes the hang when editing a large feature. Also implements some extra functionality like scrolling to a selected vertex in the table.
1 parent 42d1c44 commit 534cb41

File tree

2 files changed

+316
-123
lines changed

2 files changed

+316
-123
lines changed
 

‎src/app/nodetool/qgsnodeeditor.cpp

Lines changed: 265 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -62,175 +62,323 @@ class CoordinateItemDelegate : public QStyledItemDelegate
6262
};
6363

6464

65-
QgsNodeEditor::QgsNodeEditor(
66-
QgsVectorLayer *layer,
67-
QgsSelectedFeature *selectedFeature,
68-
QgsMapCanvas *canvas )
65+
QgsNodeEditorModel::QgsNodeEditorModel( QgsVectorLayer* layer, QgsSelectedFeature* selectedFeature, QgsMapCanvas* canvas, QObject* parent )
66+
: QAbstractTableModel( parent )
67+
, mLayer( layer )
68+
, mSelectedFeature( selectedFeature )
69+
, mCanvas( canvas )
70+
, mHasZ( false )
71+
, mHasM( false )
72+
, mHasR( true ) //always show for now - avoids scanning whole feature for curves TODO - avoid this
73+
, mZCol( -1 )
74+
, mMCol( -1 )
75+
, mRCol( -1 )
6976
{
70-
setWindowTitle( tr( "Vertex editor" ) );
71-
setFeatures( features() ^ QDockWidget::DockWidgetClosable );
7277

73-
mLayer = layer;
74-
mSelectedFeature = selectedFeature;
75-
mCanvas = canvas;
78+
if ( !mSelectedFeature->vertexMap().isEmpty() )
79+
{
80+
mHasZ = mSelectedFeature->vertexMap().at( 0 )->point().is3D();
81+
mHasM = mSelectedFeature->vertexMap().at( 0 )->point().isMeasure();
7682

77-
mTableWidget = new QTableWidget( 0, 6, this );
78-
mTableWidget->setHorizontalHeaderLabels( QStringList() << "id" << "x" << "y" << "z" << "m" << "r" );
79-
mTableWidget->setSelectionMode( QTableWidget::ExtendedSelection );
80-
mTableWidget->setSelectionBehavior( QTableWidget::SelectRows );
81-
mTableWidget->verticalHeader()->hide();
82-
mTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::Stretch );
83-
mTableWidget->horizontalHeader()->setResizeMode( 2, QHeaderView::Stretch );
84-
mTableWidget->horizontalHeader()->setResizeMode( 3, QHeaderView::Stretch );
85-
mTableWidget->horizontalHeader()->setResizeMode( 4, QHeaderView::Stretch );
86-
mTableWidget->horizontalHeader()->setResizeMode( 5, QHeaderView::Stretch );
87-
mTableWidget->setItemDelegateForColumn( 1, new CoordinateItemDelegate() );
88-
mTableWidget->setItemDelegateForColumn( 2, new CoordinateItemDelegate() );
89-
mTableWidget->setItemDelegateForColumn( 3, new CoordinateItemDelegate() );
90-
mTableWidget->setItemDelegateForColumn( 4, new CoordinateItemDelegate() );
91-
mTableWidget->setItemDelegateForColumn( 5, new CoordinateItemDelegate() );
92-
93-
setWidget( mTableWidget );
83+
if ( mHasZ )
84+
mZCol = 2;
9485

95-
connect( mSelectedFeature, SIGNAL( selectionChanged() ), this, SLOT( updateTableSelection() ) );
96-
connect( mSelectedFeature, SIGNAL( vertexMapChanged() ), this, SLOT( rebuildTable() ) );
97-
connect( mTableWidget, SIGNAL( itemSelectionChanged() ), this, SLOT( updateNodeSelection() ) );
98-
connect( mTableWidget, SIGNAL( cellChanged( int, int ) ), this, SLOT( tableValueChanged( int, int ) ) );
86+
if ( mHasM )
87+
mMCol = 2 + ( mHasZ ? 1 : 0 );
88+
89+
if ( mHasR )
90+
mRCol = 2 + ( mHasZ ? 1 : 0 ) + ( mHasM ? 1 : 0 );
91+
}
92+
93+
QWidget* parentWidget = dynamic_cast< QWidget* >( parent );
94+
if ( parentWidget )
95+
{
96+
mWidgetFont = parentWidget->font();
97+
}
9998

100-
rebuildTable();
99+
connect( mSelectedFeature, SIGNAL( vertexMapChanged() ), this, SLOT( featureChanged() ) );
101100
}
102101

103-
void QgsNodeEditor::rebuildTable()
102+
int QgsNodeEditorModel::rowCount( const QModelIndex& parent ) const
104103
{
105-
QFont curvePointFont = mTableWidget->font();
106-
curvePointFont.setItalic( true );
107-
108-
mTableWidget->blockSignals( true );
109-
mTableWidget->setRowCount( 0 );
110-
int row = 0;
111-
bool hasR = false;
112-
Q_FOREACH ( const QgsVertexEntry* entry, mSelectedFeature->vertexMap() )
113-
{
114-
mTableWidget->insertRow( row );
104+
if ( parent.isValid() )
105+
return 0;
115106

116-
QTableWidgetItem* idItem = new QTableWidgetItem();
117-
idItem->setData( Qt::DisplayRole, row );
118-
idItem->setFlags( idItem->flags() ^ Qt::ItemIsEditable );
119-
mTableWidget->setItem( row, 0, idItem );
107+
return mSelectedFeature->vertexMap().count();
108+
}
120109

121-
QTableWidgetItem* xItem = new QTableWidgetItem();
122-
xItem->setData( Qt::EditRole, entry->point().x() );
123-
mTableWidget->setItem( row, 1, xItem );
110+
int QgsNodeEditorModel::columnCount( const QModelIndex& parent ) const
111+
{
112+
Q_UNUSED( parent );
113+
return 2 + ( mHasZ ? 1 : 0 ) + ( mHasM ? 1 : 0 ) + ( mHasR ? 1 : 0 );
114+
}
124115

125-
QTableWidgetItem* yItem = new QTableWidgetItem();
126-
yItem->setData( Qt::EditRole, entry->point().y() );
127-
mTableWidget->setItem( row, 2, yItem );
116+
QVariant QgsNodeEditorModel::data( const QModelIndex& index, int role ) const
117+
{
118+
if ( !index.isValid() ||
119+
( role != Qt::DisplayRole && role != Qt::EditRole && role != MinRadiusRole && role != Qt::FontRole ) )
120+
return QVariant();
128121

129-
QTableWidgetItem* zItem = new QTableWidgetItem();
130-
zItem->setData( Qt::EditRole, entry->point().z() );
131-
mTableWidget->setItem( row, 3, zItem );
122+
if ( index.row() >= mSelectedFeature->vertexMap().count() )
123+
return QVariant();
132124

133-
QTableWidgetItem* mItem = new QTableWidgetItem();
134-
mItem->setData( Qt::EditRole, entry->point().m() );
135-
mTableWidget->setItem( row, 4, mItem );
125+
if ( index.column() >= columnCount() )
126+
return QVariant();
136127

137-
QTableWidgetItem* rItem = new QTableWidgetItem();
138-
mTableWidget->setItem( row, 5, rItem );
128+
//get QgsVertexEntry for row
129+
const QgsVertexEntry* vertex = mSelectedFeature->vertexMap().at( index.row() );
130+
if ( !vertex )
131+
{
132+
return QVariant();
133+
}
139134

140-
bool curvePoint = ( entry->vertexId().type == QgsVertexId::CurveVertex );
141-
if ( curvePoint )
135+
if ( role == Qt::FontRole )
136+
{
137+
double r = 0;
138+
double minRadius = 0;
139+
if ( calcR( index.row(), r, minRadius ) )
142140
{
143-
idItem->setFont( curvePointFont );
144-
xItem->setFont( curvePointFont );
145-
yItem->setFont( curvePointFont );
146-
zItem->setFont( curvePointFont );
147-
mItem->setFont( curvePointFont );
148-
rItem->setFont( curvePointFont );
141+
QFont curvePointFont = mWidgetFont;
142+
curvePointFont.setItalic( true );
143+
return curvePointFont;
144+
}
145+
else
146+
{
147+
return QVariant();
148+
}
149+
}
149150

150-
const QgsPointV2& p1 = mSelectedFeature->vertexMap()[row - 1]->point();
151-
const QgsPointV2& p2 = mSelectedFeature->vertexMap()[row]->point();
152-
const QgsPointV2& p3 = mSelectedFeature->vertexMap()[row + 1]->point();
151+
if ( role == MinRadiusRole )
152+
{
153+
if ( index.column() == mRCol )
154+
{
155+
double r = 0;
156+
double minRadius = 0;
157+
if ( calcR( index.row(), r, minRadius ) )
158+
{
159+
return minRadius;
160+
}
161+
}
162+
return QVariant();
163+
}
153164

154-
double r, cx, cy;
155-
QgsGeometryUtils::circleCenterRadius( p1, p2, p3, r, cx, cy );
156-
rItem->setData( Qt::EditRole, r );
165+
if ( index.column() == 0 )
166+
return vertex->point().x();
167+
else if ( index.column() == 1 )
168+
return vertex->point().y();
169+
else if ( index.column() == mZCol )
170+
return vertex->point().z();
171+
else if ( index.column() == mMCol )
172+
return vertex->point().m();
173+
else if ( index.column() == mRCol )
174+
{
175+
double r = 0;
176+
double minRadius = 0;
177+
if ( calcR( index.row(), r, minRadius ) )
178+
{
179+
return r;
180+
}
181+
return QVariant();
182+
}
183+
else
184+
{
185+
return QVariant();
186+
}
157187

158-
double x13 = p3.x() - p1.x(), y13 = p3.y() - p1.y();
159-
rItem->setData( MinRadiusRole, 0.5 * qSqrt( x13 * x13 + y13 * y13 ) );
188+
}
160189

161-
hasR = true;
190+
QVariant QgsNodeEditorModel::headerData( int section, Qt::Orientation orientation, int role ) const
191+
{
192+
if ( role == Qt::DisplayRole )
193+
{
194+
if ( orientation == Qt::Vertical ) //row
195+
{
196+
return QVariant( section );
162197
}
163198
else
164199
{
165-
rItem->setFlags( rItem->flags() & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled ) );
200+
if ( section == 0 )
201+
return QVariant( tr( "x" ) );
202+
else if ( section == 1 )
203+
return QVariant( tr( "y" ) );
204+
else if ( section == mZCol )
205+
return QVariant( tr( "z" ) );
206+
else if ( section == mMCol )
207+
return QVariant( tr( "m" ) );
208+
else if ( section == mRCol )
209+
return QVariant( tr( "r" ) );
210+
else
211+
return QVariant();
166212
}
167-
168-
++row;
169213
}
170-
mTableWidget->setColumnHidden( 3, !mSelectedFeature->vertexMap()[0]->point().is3D() );
171-
mTableWidget->setColumnHidden( 4, !mSelectedFeature->vertexMap()[0]->point().isMeasure() );
172-
mTableWidget->setColumnHidden( 5, !hasR );
173-
mTableWidget->resizeColumnToContents( 0 );
174-
mTableWidget->blockSignals( false );
214+
else
215+
{
216+
return QVariant();
217+
}
175218
}
176219

177-
void QgsNodeEditor::tableValueChanged( int row, int col )
220+
bool QgsNodeEditorModel::setData( const QModelIndex& index, const QVariant& value, int role )
178221
{
179-
int nodeIdx = mTableWidget->item( row, 0 )->data( Qt::DisplayRole ).toInt();
180-
double x, y;
181-
if ( col == 5 ) // radius modified
182-
{
183-
double r = mTableWidget->item( row, 5 )->data( Qt::EditRole ).toDouble();
184-
double x1 = mTableWidget->item( row - 1, 1 )->data( Qt::EditRole ).toDouble();
185-
double y1 = mTableWidget->item( row - 1, 2 )->data( Qt::EditRole ).toDouble();
186-
double x2 = mTableWidget->item( row , 1 )->data( Qt::EditRole ).toDouble();
187-
double y2 = mTableWidget->item( row , 2 )->data( Qt::EditRole ).toDouble();
188-
double x3 = mTableWidget->item( row + 1, 1 )->data( Qt::EditRole ).toDouble();
189-
double y3 = mTableWidget->item( row + 1, 2 )->data( Qt::EditRole ).toDouble();
190-
191-
QgsPointV2 result;
192-
QgsGeometryUtils::segmentMidPoint( QgsPointV2( x1, y1 ), QgsPointV2( x3, y3 ), result, r, QgsPointV2( x2, y2 ) );
193-
x = result.x();
194-
y = result.y();
222+
if ( !index.isValid() || role != Qt::EditRole )
223+
{
224+
return false;
195225
}
196-
else
226+
if ( index.row() >= mSelectedFeature->vertexMap().count() )
197227
{
198-
x = mTableWidget->item( row, 1 )->data( Qt::EditRole ).toDouble();
199-
y = mTableWidget->item( row, 2 )->data( Qt::EditRole ).toDouble();
228+
return false;
200229
}
201-
double z = mTableWidget->item( row, 3 )->data( Qt::EditRole ).toDouble();
202-
double m = mTableWidget->item( row, 4 )->data( Qt::EditRole ).toDouble();
230+
231+
double x = ( index.column() == 0 ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().x() );
232+
double y = ( index.column() == 1 ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().y() );
233+
234+
if ( index.column() == mRCol ) // radius modified
235+
{
236+
if ( index.row() == 0 || index.row() >= mSelectedFeature->vertexMap().count() - 1 )
237+
return false;
238+
239+
double r = value.toDouble();
240+
double x1 = mSelectedFeature->vertexMap().at( index.row() - 1 )->point().x();
241+
double y1 = mSelectedFeature->vertexMap().at( index.row() - 1 )->point().y();
242+
double x2 = x;
243+
double y2 = y;
244+
double x3 = mSelectedFeature->vertexMap().at( index.row() + 1 )->point().x();
245+
double y3 = mSelectedFeature->vertexMap().at( index.row() + 1 )->point().y();
246+
247+
QgsPointV2 result;
248+
if ( QgsGeometryUtils::segmentMidPoint( QgsPointV2( x1, y1 ), QgsPointV2( x3, y3 ), result, r, QgsPointV2( x2, y2 ) ) )
249+
{
250+
x = result.x();
251+
y = result.y();
252+
}
253+
}
254+
double z = ( index.column() == mZCol ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().z() );
255+
double m = ( index.column() == mMCol ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().m() );
203256
QgsPointV2 p( QgsWKBTypes::PointZM, x, y, z, m );
204257

205258
mLayer->beginEditCommand( QObject::tr( "Moved vertices" ) );
206-
mLayer->moveVertex( p, mSelectedFeature->featureId(), nodeIdx );
259+
mLayer->moveVertex( p, mSelectedFeature->featureId(), index.row() );
207260
mLayer->endEditCommand();
208261
mCanvas->refresh();
262+
263+
return false;
264+
}
265+
266+
Qt::ItemFlags QgsNodeEditorModel::flags( const QModelIndex& index ) const
267+
{
268+
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
269+
270+
if ( index.isValid() )
271+
{
272+
return flags | Qt::ItemIsEditable;
273+
}
274+
else
275+
{
276+
return flags;
277+
}
278+
}
279+
280+
bool QgsNodeEditorModel::calcR( int row, double& r, double& minRadius ) const
281+
{
282+
if ( row <= 0 || row >= mSelectedFeature->vertexMap().count() - 1 )
283+
return false;
284+
285+
const QgsVertexEntry* entry = mSelectedFeature->vertexMap().at( row );
286+
287+
bool curvePoint = ( entry->vertexId().type == QgsVertexId::CurveVertex );
288+
if ( !curvePoint )
289+
return false;
290+
291+
const QgsPointV2& p1 = mSelectedFeature->vertexMap().at( row - 1 )->point();
292+
const QgsPointV2& p2 = mSelectedFeature->vertexMap().at( row )->point();
293+
const QgsPointV2& p3 = mSelectedFeature->vertexMap().at( row + 1 )->point();
294+
295+
double cx, cy;
296+
QgsGeometryUtils::circleCenterRadius( p1, p2, p3, r, cx, cy );
297+
298+
double x13 = p3.x() - p1.x(), y13 = p3.y() - p1.y();
299+
minRadius = 0.5 * qSqrt( x13 * x13 + y13 * y13 );
300+
301+
return true;
302+
}
303+
304+
void QgsNodeEditorModel::featureChanged()
305+
{
306+
//TODO - avoid reset
307+
reset();
308+
}
309+
310+
QgsNodeEditor::QgsNodeEditor(
311+
QgsVectorLayer *layer,
312+
QgsSelectedFeature *selectedFeature,
313+
QgsMapCanvas *canvas )
314+
: mUpdatingTableSelection( false )
315+
, mUpdatingNodeSelection( false )
316+
{
317+
setWindowTitle( tr( "Vertex Editor" ) );
318+
setFeatures( features() ^ QDockWidget::DockWidgetClosable );
319+
320+
mLayer = layer;
321+
mSelectedFeature = selectedFeature;
322+
mCanvas = canvas;
323+
324+
mTableView = new QTableView( this );
325+
mNodeModel = new QgsNodeEditorModel( mLayer, mSelectedFeature, mCanvas, this );
326+
mTableView->setModel( mNodeModel );
327+
328+
mTableView->setSelectionMode( QTableWidget::ExtendedSelection );
329+
mTableView->setSelectionBehavior( QTableWidget::SelectRows );
330+
mTableView->setItemDelegateForColumn( 0, new CoordinateItemDelegate() );
331+
mTableView->setItemDelegateForColumn( 1, new CoordinateItemDelegate() );
332+
mTableView->setItemDelegateForColumn( 2, new CoordinateItemDelegate() );
333+
mTableView->setItemDelegateForColumn( 3, new CoordinateItemDelegate() );
334+
mTableView->setItemDelegateForColumn( 4, new CoordinateItemDelegate() );
335+
336+
setWidget( mTableView );
337+
338+
connect( mSelectedFeature, SIGNAL( selectionChanged() ), this, SLOT( updateTableSelection() ) );
339+
connect( mTableView->selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), this, SLOT( updateNodeSelection( QItemSelection, QItemSelection ) ) );
340+
connect( mTableView, SIGNAL( cellChanged( int, int ) ), this, SLOT( tableValueChanged( int, int ) ) );
209341
}
210342

211343
void QgsNodeEditor::updateTableSelection()
212344
{
213-
mTableWidget->blockSignals( true );
214-
mTableWidget->clearSelection();
345+
if ( mUpdatingNodeSelection )
346+
return;
347+
348+
mUpdatingTableSelection = true;
349+
mTableView->selectionModel()->clearSelection();
215350
const QList<QgsVertexEntry*>& vertexMap = mSelectedFeature->vertexMap();
351+
int firstSelectedRow = -1;
216352
for ( int i = 0, n = vertexMap.size(); i < n; ++i )
217353
{
218354
if ( vertexMap[i]->isSelected() )
219355
{
220-
mTableWidget->selectRow( i );
356+
if ( firstSelectedRow < 0 )
357+
firstSelectedRow = i;
358+
mTableView->selectionModel()->select( mNodeModel->index( i, 0 ), QItemSelectionModel::Rows | QItemSelectionModel::Select );
221359
}
222360
}
223-
mTableWidget->blockSignals( false );
361+
362+
if ( firstSelectedRow >= 0 )
363+
mTableView->scrollTo( mNodeModel->index( firstSelectedRow, 0 ), QAbstractItemView::PositionAtTop );
364+
365+
mUpdatingTableSelection = false;
224366
}
225367

226-
void QgsNodeEditor::updateNodeSelection()
368+
void QgsNodeEditor::updateNodeSelection( const QItemSelection&, const QItemSelection& )
227369
{
228-
mSelectedFeature->blockSignals( true );
370+
if ( mUpdatingTableSelection )
371+
return;
372+
373+
mUpdatingNodeSelection = true;
374+
229375
mSelectedFeature->deselectAllVertexes();
230-
Q_FOREACH ( const QModelIndex& index, mTableWidget->selectionModel()->selectedRows() )
376+
Q_FOREACH ( const QModelIndex& index, mTableView->selectionModel()->selectedRows() )
231377
{
232-
int nodeIdx = mTableWidget->item( index.row(), 0 )->data( Qt::DisplayRole ).toInt();
378+
int nodeIdx = index.row();
233379
mSelectedFeature->selectVertex( nodeIdx );
234380
}
235-
mSelectedFeature->blockSignals( false );
381+
382+
mUpdatingNodeSelection = false;
236383
}
384+

‎src/app/nodetool/qgsnodeeditor.h

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,53 @@
2020
#define QGSNODEEDITOR_H
2121

2222
#include <QDockWidget>
23+
#include <QAbstractTableModel>
24+
#include <QItemSelection>
2325

2426
class QgsMapCanvas;
2527
class QgsRubberBand;
2628
class QgsSelectedFeature;
2729
class QgsVectorLayer;
28-
class QTableWidget;
30+
class QTableView;
31+
32+
class QgsNodeEditorModel : public QAbstractTableModel
33+
{
34+
Q_OBJECT
35+
public:
36+
37+
QgsNodeEditorModel( QgsVectorLayer* layer,
38+
QgsSelectedFeature* selectedFeature,
39+
QgsMapCanvas* canvas, QObject* parent = 0 );
40+
41+
virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
42+
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
43+
virtual QVariant data( const QModelIndex &index, int role ) const override;
44+
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
45+
virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
46+
Qt::ItemFlags flags( const QModelIndex &index ) const override;
47+
48+
private:
49+
50+
QgsVectorLayer* mLayer;
51+
QgsSelectedFeature* mSelectedFeature;
52+
QgsMapCanvas* mCanvas;
53+
54+
bool mHasZ;
55+
bool mHasM;
56+
bool mHasR;
57+
58+
int mZCol;
59+
int mMCol;
60+
int mRCol;
61+
62+
QFont mWidgetFont;
63+
64+
bool calcR( int row, double& r, double &minRadius ) const;
65+
66+
private slots:
67+
68+
void featureChanged();
69+
};
2970

3071
class QgsNodeEditor : public QDockWidget
3172
{
@@ -39,13 +80,17 @@ class QgsNodeEditor : public QDockWidget
3980
QgsVectorLayer* mLayer;
4081
QgsSelectedFeature* mSelectedFeature;
4182
QgsMapCanvas* mCanvas;
42-
QTableWidget* mTableWidget;
83+
QTableView* mTableView;
84+
QgsNodeEditorModel* mNodeModel;
4385

4486
private slots:
45-
void rebuildTable();
46-
void tableValueChanged( int row, int col );
47-
void updateTableSelection();
48-
void updateNodeSelection();
87+
void updateTableSelection( );
88+
void updateNodeSelection( const QItemSelection& selected, const QItemSelection& deselected );
89+
90+
private:
91+
92+
bool mUpdatingTableSelection;
93+
bool mUpdatingNodeSelection;
4994
};
5095

5196
#endif // QGSNODEEDITOR_H

0 commit comments

Comments
 (0)
Please sign in to comment.