/***************************************************************************
    qgsgrassselect.cpp  -  Select GRASS layer dialog
                             -------------------
    begin                : March, 2004
    copyright            : (C) 2004 by Radim Blazek
    email                : blazek@itc.it
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "qgsgrassedit.h"
#include "qgsgrassattributes.h"
#include "qgsgrassedittools.h"
#include "qgsgrassplugin.h"
#include "qgsgrassutils.h"
#include "qgsgrassprovider.h"
#include "qgsgrass.h"

#include "qgisinterface.h"
#include "qgsapplication.h"
#include "qgsfield.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmapcanvasitem.h"
#include "qgsmaplayer.h"
#include "qgsmaprenderer.h"
#include "qgsproject.h"
#include "qgsrubberband.h"
#include "qgsvectorlayer.h"
#include "qgsvertexmarker.h"

#include <QCloseEvent>
#include <QColorDialog>
#include <QDir>
#include <QHeaderView>
#include <QKeyEvent>
#include <QMessageBox>
#include <QSettings>
#include <QToolBar>

extern "C"
{
#include <grass/Vect.h>
}

#ifdef _MSC_VER
#define round(x)  ((x) >= 0 ? floor((x)+0.5) : floor((x)-0.5))
#endif


class QgsGrassEditLayer : public QgsMapCanvasItem
{
  public:

    QgsGrassEditLayer( QgsMapCanvas* mapCanvas ): QgsMapCanvasItem( mapCanvas )
    {
    }

    virtual void paint( QPainter* p )
    {
      p->drawPixmap( 0, 0, mPixmap );
    }

    virtual QRectF boundingRect() const
    {
      return QRectF( 0, 0, mMapCanvas->width(), mMapCanvas->height() );
    }

    virtual void updatePosition()
    {
      setPos( QPointF( mPanningOffset ) );
    }

    QPixmap& pixmap() { return mPixmap; }

  private:

    QPixmap mPixmap;
};


#include <QItemDelegate>
class QgsGrassEditAttributeTableItemDelegate : public QItemDelegate
{
  public:
    QgsGrassEditAttributeTableItemDelegate( QObject *parent = 0 );
    QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
    void setEditorData( QWidget *editor, const QModelIndex &index ) const;
    void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const;
};

QgsGrassEditAttributeTableItemDelegate::QgsGrassEditAttributeTableItemDelegate( QObject *parent )
    : QItemDelegate( parent )
{}

QWidget *QgsGrassEditAttributeTableItemDelegate::createEditor( QWidget *parent,
    const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
  QWidget *editor;
  if ( index.column() == 1 )
  {
    QComboBox *cb = new QComboBox( parent );
    cb->addItems( QStringList() << "integer" << "double precision" << "varchar" );
    editor = cb;
  }
  else
  {
    editor = QItemDelegate::createEditor( parent, option, index );
  }
  return editor;
}

#include<QDebug>
void QgsGrassEditAttributeTableItemDelegate::setEditorData( QWidget *editor,
    const QModelIndex &index ) const
{
  if ( index.column() == 1 )
  {
    QComboBox *cb = static_cast<QComboBox *>( editor );
    cb->setCurrentIndex( cb->findData( index.model()->data( index ), Qt::DisplayRole ) );
  }
  else
  {
    QItemDelegate::setEditorData( editor, index );
  }
}

void QgsGrassEditAttributeTableItemDelegate::setModelData( QWidget *editor,
    QAbstractItemModel *model, const QModelIndex &index ) const
{
  if ( index.column() == 1 )
  {
    QComboBox *cb = static_cast<QComboBox *>( editor );
    model->setData( index, cb->currentText(), Qt::EditRole );
  }
  else
  {
    QItemDelegate::setModelData( editor, model, index );
  }
}


bool QgsGrassEdit::mRunning = false;

QgsGrassEdit::QgsGrassEdit( QgisInterface *iface, QgsMapLayer* layer, bool newMap,
                            QWidget * parent, Qt::WFlags f )
    : QMainWindow( parent, f ), QgsGrassEditBase(), mInited( false ),
    mMapTool( 0 ), mCanvasEdit( 0 ), mRubberBandLine( 0 ), mRubberBandIcon( 0 )
{
  QgsDebugMsg( "QgsGrassEdit()" );

  setupUi( this );

  mRunning = true;
  mValid = false;
  mTool = QgsGrassEdit::NONE;
  mSuspend = false;
  mIface = iface;
  mNewMap = newMap;

  mProjectionEnabled = ( QgsProject::instance()->readNumEntry( "SpatialRefSys", "/ProjectionsEnabled", 0 ) != 0 );

  mCanvas = mIface->mapCanvas();

  if ( !isEditable( layer ) ) return;

  //TODO dynamic_cast ?
  mLayer = ( QgsVectorLayer* )layer;

  //TODO dynamic_cast ?
  mProvider = ( QgsGrassProvider * ) mLayer->dataProvider();

  init();

}

bool QgsGrassEdit::isEditable( QgsMapLayer *layer )
{
  if ( !layer ) return false;

  QgsDebugMsg( "layer name: " + layer->name() );

  if ( layer->type() != QgsMapLayer::VectorLayer )
  {
    QgsDebugMsg( "The selected layer is not vector." );
    return false;
  }

  //TODO dynamic_cast ?
  QgsVectorLayer *vector = ( QgsVectorLayer* )layer;

  QgsDebugMsg( "Vector layer type: " + vector->providerType() );

  if ( vector->providerType() != "grass" )
  {
    QgsDebugMsg( "The selected layer is not GRASS." );
    return false;
  }

  return true;
}

void QgsGrassEdit::keyPress( QKeyEvent *e )
{
  QgsDebugMsg( "entered." );
  // This does not work:
  //keyPressEvent(e);

  // TODO: this is not optimal
  switch ( e->key() )
  {
    case Qt::Key_F1: newPoint(); break;
    case Qt::Key_F2: newLine(); break;
    case Qt::Key_F3: newBoundary(); break;
    case Qt::Key_F4: newCentroid(); break;
    case Qt::Key_F5: moveVertex(); break;
    case Qt::Key_F6: addVertex(); break;
    case Qt::Key_F7: deleteVertex(); break;
    case Qt::Key_F9: moveLine(); break;
    case Qt::Key_F10: splitLine(); break;
    case Qt::Key_F11: deleteLine(); break;
    default: break;
  }
}


void QgsGrassEdit::init()
{
  if ( !( mProvider->isGrassEditable() ) )
  {
    QMessageBox::warning( 0, tr( "Warning" ),
                          tr( "You are not owner of the mapset, cannot open the vector for editing." ) );
    return;
  }

  if ( !( mProvider->startEdit() ) )
  {
    QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot open vector for update." ) );
    return;
  }

  mRubberBandLine = new QgsRubberBand( mCanvas );
  mRubberBandIcon = new QgsVertexMarker( mCanvas );
  mRubberBandLine->setZValue( 20 );
  mRubberBandIcon->setZValue( 20 );

  connect( mCanvas, SIGNAL( keyPressed( QKeyEvent * ) ), this, SLOT( keyPress( QKeyEvent * ) ) );


  mToolBar = addToolBar( tr( "Edit tools" ) );

  mNewPointAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_new_point.png" ), tr( "New point" ), this );
  mNewPointAction->setShortcut( QKeySequence( Qt::Key_F1 ) );
  mToolBar->addAction( mNewPointAction );
  connect( mNewPointAction, SIGNAL( triggered() ), this, SLOT( newPoint() ) );

  mNewLineAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_new_line.png" ), tr( "New line" ), this );
  mNewLineAction->setShortcut( QKeySequence( Qt::Key_F2 ) );
  mToolBar->addAction( mNewLineAction );
  connect( mNewLineAction, SIGNAL( triggered() ), this, SLOT( newLine() ) );

  mNewBoundaryAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_new_boundary.png" ), tr( "New boundary" ), this );
  mNewBoundaryAction->setShortcut( QKeySequence( Qt::Key_F3 ) );
  mToolBar->addAction( mNewBoundaryAction );
  connect( mNewBoundaryAction, SIGNAL( triggered() ), this, SLOT( newBoundary() ) );

  mNewCentroidAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_new_centroid.png" ), tr( "New centroid" ), this );
  mNewCentroidAction->setShortcut( QKeySequence( Qt::Key_F4 ) );
  mToolBar->addAction( mNewCentroidAction );
  connect( mNewCentroidAction, SIGNAL( triggered() ), this, SLOT( newCentroid() ) );

  mMoveVertexAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_move_vertex.png" ), tr( "Move vertex" ), this );
  mMoveVertexAction->setShortcut( QKeySequence( Qt::Key_F5 ) );
  mToolBar->addAction( mMoveVertexAction );
  connect( mMoveVertexAction, SIGNAL( triggered() ), this, SLOT( moveVertex() ) );

  mAddVertexAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_add_vertex.png" ), tr( "Add vertex" ), this );
  mAddVertexAction->setShortcut( QKeySequence( Qt::Key_F6 ) );
  mToolBar->addAction( mAddVertexAction );
  connect( mAddVertexAction, SIGNAL( triggered() ), this, SLOT( addVertex() ) );

  mDeleteVertexAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_delete_vertex.png" ), tr( "Delete vertex" ), this );
  mDeleteVertexAction->setShortcut( QKeySequence( Qt::Key_F7 ) );
  mToolBar->addAction( mDeleteVertexAction );
  connect( mDeleteVertexAction, SIGNAL( triggered() ), this, SLOT( deleteVertex() ) );

  mMoveLineAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_move_line.png" ), tr( "Move element" ), this );
  mMoveLineAction->setShortcut( QKeySequence( Qt::Key_F9 ) );
  mToolBar->addAction( mMoveLineAction );
  connect( mMoveLineAction, SIGNAL( triggered() ), this, SLOT( moveLine() ) );

  mSplitLineAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_split_line.png" ), tr( "Split line" ), this );
  mSplitLineAction->setShortcut( QKeySequence( Qt::Key_F10 ) );
  mToolBar->addAction( mSplitLineAction );
  connect( mSplitLineAction, SIGNAL( triggered() ), this, SLOT( splitLine() ) );

  mDeleteLineAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_delete_line.png" ), tr( "Delete element" ), this );
  mDeleteLineAction->setShortcut( QKeySequence( Qt::Key_F11 ) );
  mToolBar->addAction( mDeleteLineAction );
  connect( mDeleteLineAction, SIGNAL( triggered() ), this, SLOT( deleteLine() ) );

  mEditAttributesAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_edit_attributes.png" ), tr( "Edit attributes" ), this );
  mToolBar->addAction( mEditAttributesAction );
  connect( mEditAttributesAction, SIGNAL( triggered() ), this, SLOT( editAttributes() ) );

  mCloseEditAction = new QAction(
    QgsGrassPlugin::getThemeIcon( "grass_close_edit.png" ), tr( "Close" ), this );
  mToolBar->addAction( mCloseEditAction );
  connect( mCloseEditAction, SIGNAL( triggered() ), this, SLOT( closeEdit() ) );

  mNewPointAction->setCheckable( true );
  mNewLineAction->setCheckable( true );
  mNewBoundaryAction->setCheckable( true );
  mNewCentroidAction->setCheckable( true );
  mMoveVertexAction->setCheckable( true );
  mAddVertexAction->setCheckable( true );
  mDeleteVertexAction->setCheckable( true );
  mMoveLineAction->setCheckable( true );
  mSplitLineAction->setCheckable( true );
  mDeleteLineAction->setCheckable( true );
  mEditAttributesAction->setCheckable( true );

  QActionGroup *ag = new QActionGroup( this );
  ag->addAction( mNewPointAction );
  ag->addAction( mNewLineAction );
  ag->addAction( mNewBoundaryAction );
  ag->addAction( mNewCentroidAction );
  ag->addAction( mMoveVertexAction );
  ag->addAction( mAddVertexAction );
  ag->addAction( mDeleteVertexAction );
  ag->addAction( mMoveLineAction );
  ag->addAction( mSplitLineAction );
  ag->addAction( mDeleteLineAction );
  ag->addAction( mEditAttributesAction );

  mEditPoints = Vect_new_line_struct();
  mPoints = Vect_new_line_struct();
  mCats = Vect_new_cats_struct();

  // Set lines symbology from map
  int nlines = mProvider->numLines();
  mLineSymb.resize( nlines + 1000 );
  for ( int line = 1; line <= nlines; line++ )
  {
    mLineSymb[line] = lineSymbFromMap( line );
  }

  // Set nodes symbology from map
  int nnodes = mProvider->numNodes();
  mNodeSymb.resize( nnodes + 1000 );
  for ( int node = 1; node <= nnodes; node++ )
  {
    mNodeSymb[node] = nodeSymbFromMap( node );
  }

  // Set default colors
  mSymb.resize( SYMB_COUNT );
  mSymb[SYMB_BACKGROUND].setColor( QColor( 255, 255, 255 ) );       // white
  mSymb[SYMB_HIGHLIGHT].setColor( QColor( 255, 255,   0 ) );        // yellow
  mSymb[SYMB_DYNAMIC].setColor( QColor( 125, 125, 125 ) );          // grey
  mSymb[SYMB_POINT].setColor( QColor( 0,   0,   0 ) );              // black
  mSymb[SYMB_LINE].setColor( QColor( 0,   0,   0 ) );               // black
  mSymb[SYMB_BOUNDARY_0].setColor( QColor( 255,   0,   0 ) );       // red
  mSymb[SYMB_BOUNDARY_1].setColor( QColor( 255, 125,   0 ) );       // orange
  mSymb[SYMB_BOUNDARY_2].setColor( QColor( 0, 255,   0 ) );         // green
  mSymb[SYMB_CENTROID_IN].setColor( QColor( 0, 255,   0 ) );        // green
  mSymb[SYMB_CENTROID_OUT].setColor( QColor( 255,   0,   0 ) );     // red
  mSymb[SYMB_CENTROID_DUPL].setColor( QColor( 255,   0, 255 ) );    // magenta
  mSymb[SYMB_NODE_1].setColor( QColor( 255,   0,   0 ) );           // red
  mSymb[SYMB_NODE_2].setColor( QColor( 0, 255,   0 ) );             // green

  // Set mSymbDisplay
  mSymbDisplay.resize( SYMB_COUNT );
  mSymbDisplay[SYMB_BACKGROUND] = true;
  mSymbDisplay[SYMB_HIGHLIGHT] = true;
  mSymbDisplay[SYMB_DYNAMIC] = true;
  mSymbDisplay[SYMB_POINT] = true;
  mSymbDisplay[SYMB_LINE] = true;
  mSymbDisplay[SYMB_BOUNDARY_0] = true;
  mSymbDisplay[SYMB_BOUNDARY_1] = true;
  mSymbDisplay[SYMB_BOUNDARY_2] = true;
  mSymbDisplay[SYMB_CENTROID_IN] = true;
  mSymbDisplay[SYMB_CENTROID_OUT] = true;
  mSymbDisplay[SYMB_CENTROID_DUPL] = true;
  mSymbDisplay[SYMB_NODE_1] = true;
  mSymbDisplay[SYMB_NODE_2] = true;

  // Set symbology names
  mSymbName.resize( SYMB_COUNT );
  mSymbName[SYMB_BACKGROUND]    = tr( "Background" );
  mSymbName[SYMB_HIGHLIGHT]     = tr( "Highlight" );
  mSymbName[SYMB_DYNAMIC]       = tr( "Dynamic" );
  mSymbName[SYMB_POINT]         = tr( "Point" );
  mSymbName[SYMB_LINE]          = tr( "Line" );
  mSymbName[SYMB_BOUNDARY_0]    = tr( "Boundary (no area)" );
  mSymbName[SYMB_BOUNDARY_1]    = tr( "Boundary (1 area)" );
  mSymbName[SYMB_BOUNDARY_2]    = tr( "Boundary (2 areas)" );
  mSymbName[SYMB_CENTROID_IN]   = tr( "Centroid (in area)" );
  mSymbName[SYMB_CENTROID_OUT]  = tr( "Centroid (outside area)" );
  mSymbName[SYMB_CENTROID_DUPL] = tr( "Centroid (duplicate in area)" );
  mSymbName[SYMB_NODE_1]        = tr( "Node (1 line)" );
  mSymbName[SYMB_NODE_2]        = tr( "Node (2 lines)" );

  // Restore symbology
  QString spath = "/GRASS/edit/symb/";
  QSettings settings;

  mLineWidth = settings.value(
                 spath + "lineWidth", 1 ).toInt();
  mSize = settings.value(
            spath + "markerSize", 9 ).toInt();
  mLineWidthSpinBox->setValue( mLineWidth );
  mMarkerSizeSpinBox->setValue( mSize );

  for ( int i = 0; i < SYMB_COUNT; i++ )
  {
    bool ok = settings.contains(
                spath + "display/" + QString::number( i ) );
    bool displ = settings.value(
                   spath + "display/" + QString::number( i ),
                   true ).toBool();
    if ( ok )
    {
      mSymbDisplay[i] = displ;
    }

    ok = settings.contains(
           spath + "color/" + QString::number( i ) );
    QString colorName = settings.value(
                          spath + "color/" + QString::number( i ),
                          "" ).toString();
    if ( ok )
    {
      QColor color( colorName );
      mSymb[i].setColor( color );
      // Use the 'dynamic' color for mRubberBand
      if ( i == SYMB_DYNAMIC )
      {
        mRubberBandLine->setColor( QColor( colorName ) );
      }
    }
    mSymb[i].setWidth( mLineWidth );
  }

  // Set Symbology in dialog
  symbologyList->setColumnWidth( 0, 40 );
  symbologyList->setColumnWidth( 1, 50 );
  symbologyList->setColumnWidth( 2, 200 );

  for ( int i = 0; i < SYMB_COUNT; i++ )
  {
    if ( i == SYMB_NODE_0 ) continue;

    QPixmap pm( 40, 15 );
    pm.fill( mSymb[i].color() );
    QString index;
    index.sprintf( "%d", i );

    QTreeWidgetItem *item = new QTreeWidgetItem( symbologyList );
    if ( !( i == SYMB_BACKGROUND || i == SYMB_HIGHLIGHT || i == SYMB_DYNAMIC ) )
    {
      item->setCheckState( 0, mSymbDisplay[i] ? Qt::Checked : Qt::Unchecked );
    }
    item->setIcon( 1, pm );
    item->setText( 2, mSymbName[i] );
    item->setText( 3, index );
  }

  connect( symbologyList, SIGNAL( itemPressed( QTreeWidgetItem *, int ) ),
           this, SLOT( changeSymbology( QTreeWidgetItem *, int ) ) );

  // Init table tab
  mAttributeTable->setItemDelegate( new QgsGrassEditAttributeTableItemDelegate( this ) );
  mAttributeTable->verticalHeader()->hide();

  int ndblinks = mProvider->numDbLinks();

  if ( ndblinks > 0 )
  {
    for ( int i = 0; i < ndblinks; i++ )
    {
      int f = mProvider->dbLinkField( i );

      QString str;
      str.sprintf( "%d", f );
      mTableField->addItem( str );
      mFieldBox->addItem( str );
      if ( i == 0 )
      {
        setAttributeTable( f );
      }
    }
    mTableField->setCurrentIndex( 0 );
    mFieldBox->setCurrentIndex( 0 );
  }
  else
  {
    mTableField->addItem( "1" );
    setAttributeTable( 1 );

    mFieldBox->addItem( "1" );
  }

  connect( mAttributeTable, SIGNAL( cellChanged( int, int ) ), this, SLOT( columnTypeChanged( int, int ) ) );

  // Set variables
  mSelectedLine = 0;
  mAttributes = 0;

  // Read max cats
  for ( int i = 0; i < mProvider->cidxGetNumFields(); i++ )
  {
    int field = mProvider->cidxGetFieldNumber( i );
    if ( field > 0 )
    {
      int cat = mProvider->cidxGetMaxCat( i );
      MaxCat mc;
      mc.field = field;
      mc.maxCat = cat;
      mMaxCats.push_back( mc );
    }
  }

  connect( mCanvas, SIGNAL( renderComplete( QPainter * ) ), this, SLOT( postRender( QPainter * ) ) );

  mCanvasEdit = new QgsGrassEditLayer( mCanvas );

  mPixmap = &mCanvasEdit->pixmap();

  // Init GUI values
  mCatModeBox->addItem( tr( "Next not used" ), CAT_MODE_NEXT );
  mCatModeBox->addItem( tr( "Manual entry" ), CAT_MODE_MANUAL );
  mCatModeBox->addItem( tr( "No category" ), CAT_MODE_NOCAT );
  catModeChanged( );

  // TODO: how to get keyboard events from canvas (shortcuts)

  restorePosition();

  mValid = true;
  mInited = true;
}

void QgsGrassEdit::attributeTableFieldChanged( void )
{
  QgsDebugMsg( "entered." );
  int field = mTableField->currentText().toInt();

  setAttributeTable( field );
}

void QgsGrassEdit::setAttributeTable( int field )
{
  mAttributeTable->setRowCount( 0 );

  QString *key = mProvider->key( field );

  if ( !key->isEmpty() )   // Database link defined
  {
    std::vector<QgsField> *cols = mProvider->columns( field );

    mAttributeTable->setRowCount( cols->size() );


    for ( unsigned int c = 0; c < cols->size(); c++ )
    {
      QgsField col = ( *cols )[c];

      QTableWidgetItem *ti;

      ti = new QTableWidgetItem( col.name() );
      ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
      mAttributeTable->setItem( c, 0, ti );

      ti = new QTableWidgetItem( col.typeName() );
      ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
      mAttributeTable->setItem( c, 1, ti );

      QString str;
      str.sprintf( "%d", col.length() );
      ti = new QTableWidgetItem( str );
      ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
      mAttributeTable->setItem( c, 2, ti );
    }
  }
  else
  {
    mAttributeTable->setRowCount( 1 );

    QTableWidgetItem *ti;

    ti = new QTableWidgetItem( "cat" );
    mAttributeTable->setItem( 0, 0, ti );

    ti = new QTableWidgetItem( "integer" );
    ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
    mAttributeTable->setItem( 0, 1, ti );

    ti = new QTableWidgetItem( "" );
    ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
    mAttributeTable->setItem( 0, 2, ti );
  }
}

void QgsGrassEdit::addColumn( void )
{
  QgsDebugMsg( "entered." );
  int r = mAttributeTable->rowCount();
  mAttributeTable->setRowCount( r + 1 );

  QString cn;
  cn.sprintf( "column%d", r + 1 );

  QTableWidgetItem *ti;

  ti = new QTableWidgetItem( cn );
  mAttributeTable->setItem( r, 0, ti );

  ti = new QTableWidgetItem( "integer" );
  mAttributeTable->setItem( r, 1, ti );

  ti = new QTableWidgetItem( "20" );
  ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
  mAttributeTable->setItem( r, 2, ti );
}

void QgsGrassEdit::columnTypeChanged( int row, int col )
{
  QgsDebugMsg( QString( "row = %1 col = %2" ).arg( row ).arg( col ) );

  if ( col != 1 ) return;

  QTableWidgetItem *ti = mAttributeTable->item( row, 2 );
  if ( ti )
  {
    if ( mAttributeTable->item( row, 1 )->text().compare( "varchar" ) == 0 )
    {
      ti->setFlags( ti->flags() | Qt::ItemIsEnabled );
    }
    else
    {
      ti->setFlags( ti->flags() & ~Qt::ItemIsEnabled );
    }
  }
}

void QgsGrassEdit::alterTable( void )
{
  QgsDebugMsg( "entered." );

  // Create new table if first column name is editable otherwise alter table
  int field = mTableField->currentText().toInt();

  QString sql;
  QString type;

  if ( mAttributeTable->item( 0, 0 )->flags() & Qt::ItemIsEnabled )
  {
    QgsDebugMsg( "Create new table" );

    for ( int i = 0; i < mAttributeTable->rowCount(); i++ )
    {
      if ( i > 0 ) sql.append( ", " );

      type = mAttributeTable->item( i, 1 )->text();
      sql.append( mAttributeTable->item( i, 0 )->text() + " " + type );

      if ( type.compare( "varchar" ) == 0 )
      {
        sql.append( " (" + mAttributeTable->item( i, 2 )->text() + ")" );
      }
    }

    QString *error = mProvider->createTable( field, mAttributeTable->item( 0, 0 )->text(), sql );

    if ( !error->isEmpty() )
    {
      QMessageBox::warning( 0, tr( "Warning" ), *error );
    }
    else
    {
      QMessageBox::information( 0, tr( "Info" ), tr( "The table was created" ) );
      QString str;
      str.sprintf( "%d", field );
      mFieldBox->addItem( str );
    }
    delete error;
  }
  else
  {
    QgsDebugMsg( "Alter table" );

    for ( int i = 0; i < mAttributeTable->rowCount(); i++ )
    {
      if ( !( mAttributeTable->item( i, 0 )->flags() & Qt::ItemIsEnabled ) ) continue;

      type = mAttributeTable->item( i, 1 )->text();
      sql = mAttributeTable->item( i, 0 )->text() + " " + type;

      if ( type.compare( "varchar" ) == 0 )
      {
        sql.append( " (" + mAttributeTable->item( i, 2 )->text() + ")" );
      }

      QString *error = mProvider->addColumn( field, sql );

      if ( !error->isEmpty() )
      {
        QMessageBox::warning( 0, tr( "Warning" ), *error );
      }
      delete error;
    }
  }

  setAttributeTable( field );
}

void QgsGrassEdit::changeSymbology( QTreeWidgetItem * item, int col )
{
  QgsDebugMsg( QString( "col = %1" ).arg( col ) );

  QSettings settings;

  if ( !item ) return;

  int index = item->text( 3 ).toInt();

  if ( col == 0 )
  {
    if ( index == SYMB_BACKGROUND || index == SYMB_HIGHLIGHT || index == SYMB_DYNAMIC ) return;

    mSymbDisplay[index] = item->checkState( 0 ) == Qt::Checked;

    //int ww = settings.readNumEntry("/GRASS/windows/edit/w", 420);
    QString sn;
    // TODO use a name instead of index
    sn.sprintf( "/GRASS/edit/symb/display/%d", index );
    settings.setValue( sn, ( bool )mSymbDisplay[index] );
  }
  else if ( col == 1 )
  {
    QColor color = QColorDialog::getColor( mSymb[index].color(), this );
    mSymb[index].setColor( color );

    QPixmap pm( 40, 15 );
    pm.fill( mSymb[index].color() );
    item->setIcon( 1, pm );

    QString sn;
    // TODO use a name instead of index
    sn.sprintf( "/GRASS/edit/symb/color/%d", index );
    settings.setValue( sn, mSymb[index].color().name() );
    // Use the 'dynamic' color for mRubberBand
    if ( index == SYMB_DYNAMIC )
    {
      mRubberBandLine->setColor( color );
    }
  }
}

void QgsGrassEdit::lineWidthChanged()
{
  QgsDebugMsg( "entered." );
  QSettings settings;
  mLineWidth = mLineWidthSpinBox->value();

  for ( int i = 0; i < SYMB_COUNT; i++ )
  {
    mSymb[i].setWidth( mLineWidth );
  }

  QString spath = "/GRASS/edit/symb/";
  settings.setValue( spath + "lineWidth", mLineWidth );
}

void QgsGrassEdit::markerSizeChanged()
{
  QgsDebugMsg( "entered." );
  QSettings settings;
  mSize = mMarkerSizeSpinBox->value();
  QString spath = "/GRASS/edit/symb/";
  settings.setValue( spath + "markerSize", mSize );
}

void QgsGrassEdit::restorePosition()
{
  QSettings settings;
  restoreGeometry( settings.value( "/GRASS/windows/edit/geometry" ).toByteArray() );
}

void QgsGrassEdit::saveWindowLocation()
{
  QSettings settings;
  settings.setValue( "/GRASS/windows/edit/geometry", saveGeometry() );
}

void QgsGrassEdit::updateSymb( void )
{
  QgsDebugMsg( "entered." );

  // Set lines symbology from map
  unsigned int nlines = mProvider->numLines();
  if ( nlines + 1 >= mLineSymb.size() )
    mLineSymb.resize( nlines + 1000 );

  nlines = mProvider->numUpdatedLines();
  for ( unsigned int i = 0; i < nlines; i++ )
  {
    int line = mProvider->updatedLine( i );
    QgsDebugMsg( QString( "updated line = %1" ).arg( line ) );
    if ( !( mProvider->lineAlive( line ) ) ) continue;
    mLineSymb[line] = lineSymbFromMap( line );
  }

  // Set nodes symbology from map
  unsigned int nnodes = mProvider->numNodes();
  if ( nnodes + 1 >= mNodeSymb.size() )
    mNodeSymb.resize( nnodes + 1000 );

  nnodes = mProvider->numUpdatedNodes();
  for ( unsigned int i = 0; i < nnodes; i++ )
  {
    int node = mProvider->updatedNode( i );
    if ( !( mProvider->nodeAlive( node ) ) ) continue;
    mNodeSymb[node] = nodeSymbFromMap( node );
    QgsDebugMsg( QString( "node = %1 mNodeSymb = %2" ).arg( node ).arg( mNodeSymb[node] ) );
  }
}

int QgsGrassEdit::nodeSymbFromMap( int node )
{
  QgsDebugMsg( QString( "node = %1" ).arg( node ) );

  int nlines = mProvider->nodeNLines( node );

  int count = 0;

  for ( int i = 0; i < nlines; i++ )
  {
    int line = abs( mProvider->nodeLine( node, i ) );
    int type = mProvider->readLine( NULL, NULL, line );

    if ( type & GV_LINES )
      count++;
  }

  if ( count == 0 )
    return SYMB_NODE_0;
  else if ( count == 1 )
    return SYMB_NODE_1;

  return SYMB_NODE_2;
}

int QgsGrassEdit::lineSymbFromMap( int line )
{
  QgsDebugMsg( QString( "line = %1" ).arg( line ) );

  int type = mProvider->readLine( NULL, NULL, line );

  if ( type < 0 ) return 0;

  switch ( type )
  {
    case GV_POINT:
      return SYMB_POINT;
      break;

    case GV_LINE:
      return SYMB_LINE;
      break;

    case GV_BOUNDARY:
      int left, right, nareas;

      if ( !( mProvider->lineAreas( line, &left, &right ) ) ) return 0;

      /* Count areas/isles on both sides */
      nareas = 0;
      if ( left != 0 ) nareas++;
      if ( right != 0 ) nareas++;
      if ( nareas == 0 ) return SYMB_BOUNDARY_0;
      else if ( nareas == 1 ) return SYMB_BOUNDARY_1;
      else return SYMB_BOUNDARY_2;
      break;

    case GV_CENTROID:
      int area = mProvider->centroidArea( line );
      if ( area == 0 ) return SYMB_CENTROID_OUT;
      else if ( area > 0 ) return SYMB_CENTROID_IN;
      else return SYMB_CENTROID_DUPL; /* area < 0 */
      break;
  }

  return 0; // Should not happen
}

QgsGrassEdit::~QgsGrassEdit()
{
  QgsDebugMsg( "entered." );

  // we can only call some methods if init was complete,
  // note that we cannot use mValid because it is disabled before
  // destructor is called
  if ( mInited )
  {
    // delete tool if exists
    delete mMapTool;

    eraseDynamic();
    mRubberBandLine->hide();
    mRubberBandIcon->hide();
    mRubberBandLine->reset();
    delete mRubberBandLine;
    delete mRubberBandIcon;

    delete mCanvasEdit;

    mCanvas->refresh();

    saveWindowLocation();
  }
  mRunning = false;
}

bool QgsGrassEdit::isRunning( void )
{
  return mRunning;
}

bool QgsGrassEdit::isValid( void )
{
  return mValid;
}

void QgsGrassEdit::closeEdit( void )
{
  QgsDebugMsg( "entered." );

  // Disconnect signals
  // Warning: it seems that slots (postRender) can be called even
  //          after disconnect (is it a queue?)
  disconnect( this, SLOT( postRender( QPainter * ) ) );

  mValid = false; // important for postRender

  if ( mAttributes )
  {
    delete mAttributes;
  }

  mProvider->closeEdit( mNewMap );

  hide();

  // Add new layers
  if ( mNewMap )
  {
    QString uri = QDir::cleanPath( mProvider->dataSourceUri() );
    QgsDebugMsg( QString( "uri = %1" ).arg( uri ) );
    // Note: QDir::cleanPath is using '/' also on Windows
    //QChar sep = QDir::separator();
    QChar sep = '/';

    QStringList split = uri.split( sep, QString::SkipEmptyParts );
    split.pop_back(); // layer
    QString map = split.last();
    split.pop_back(); // map
    QString mapset = split.last();

    QgsGrassUtils::addVectorLayers( mIface, QgsGrass::getDefaultGisdbase(),
                                    QgsGrass::getDefaultLocation(),
                                    mapset, map );
  }
  emit finished();
  delete this;
}

void QgsGrassEdit::closeEvent( QCloseEvent *e )
{
  QgsDebugMsg( "entered." );

  e->accept();

  closeEdit();
}

void QgsGrassEdit::catModeChanged( void )
{
  QgsDebugMsg( "entered." );
  int mode = mCatModeBox->currentIndex();

  int field = mFieldBox->currentText().toInt();

  if ( mode == CAT_MODE_NEXT )   // Find next not used
  {
    QString c = "1"; // Default for new field
    for ( unsigned int i = 0; i < mMaxCats.size(); i++ )
    {
      if ( mMaxCats[i].field == field )
      {
        c.sprintf( "%d", mMaxCats[i].maxCat + 1 );
        break;
      }
    }
    mCatEntry->setText( c );
    mCatEntry->setEnabled( false );
    mFieldBox->setDisabled( false );
  }
  else if ( mode == CAT_MODE_MANUAL )
  {
    mCatEntry->setEnabled( true );
    mFieldBox->setDisabled( false );
  }
  else   // CAT_MODE_NOCAT
  {
    mCatEntry->clear();
    mCatEntry->setEnabled( false );
    mFieldBox->setDisabled( true );
  }
}

void QgsGrassEdit::fieldChanged( void )
{
  QgsDebugMsg( "entered." );
  int mode = mCatModeBox->currentIndex();
  int field = mFieldBox->currentText().toInt();

  if ( mode == CAT_MODE_NEXT )   // Find next not used
  {
    QString c = "1"; // Default for new field
    for ( unsigned int i = 0; i < mMaxCats.size(); i++ )
    {
      if ( mMaxCats[i].field == field )
      {
        c.sprintf( "%d", mMaxCats[i].maxCat + 1 );
        break;
      }
    }
    mCatEntry->setText( c );
  }
}

int QgsGrassEdit::writeLine( int type, struct line_pnts *Points )
{
  int mode = mCatModeBox->currentIndex();
  int field = mFieldBox->currentText().toInt();
  int cat = mCatEntry->text().toInt();

  Vect_reset_cats( mCats );
  if ( mode == CAT_MODE_NEXT || mode == CAT_MODE_MANUAL )
  {
    Vect_cat_set( mCats, field, cat );

    // Insert new DB record if link is defined and the record for this cat does not exist
    QString *key = mProvider->key( field );

    if ( !key->isEmpty() )   // Database link defined
    {
      QgsAttributeMap *atts = mProvider->attributes( field, cat );

      if ( atts->count() == 0 )   // Nothing selected
      {
        QString *error = mProvider->insertAttributes( field, cat );

        if ( !error->isEmpty() )
        {
          QMessageBox::warning( 0, tr( "Warning" ), *error );
        }
        delete error;
      }

      delete atts;
    }
  }
  Vect_line_prune( Points );
  int line = mProvider->writeLine( type, Points, mCats );

  increaseMaxCat();
  return line;
}

void QgsGrassEdit::increaseMaxCat( void )
{
  int mode = mCatModeBox->currentIndex();
  int field = mFieldBox->currentText().toInt();
  int cat = mCatEntry->text().toInt();

  if ( mode == CAT_MODE_NEXT || mode == CAT_MODE_MANUAL )
  {
    int found = 0;
    for ( unsigned int i = 0; i < mMaxCats.size(); i++ )
    {
      if ( mMaxCats[i].field == field )
      {
        if ( cat > mMaxCats[i].maxCat )
        {
          mMaxCats[i].maxCat = cat;
        }
        found = 1;
        break;
      }
    }
    if ( !found )
    {
      MaxCat mc;
      mc.field = field;
      mc.maxCat = cat;
      mMaxCats.push_back( mc );
    }

    if ( mode == CAT_MODE_NEXT )
    {
      QString c;
      c.sprintf( "%d", cat + 1 );
      mCatEntry->setText( c );
    }
  }

}

double QgsGrassEdit::threshold( void )
{
  int snapPixels = mSnapPixels->text().toInt();

  // Convert to map units (not nice)
  QgsPoint p1, p2;
  p1 = mTransform->toMapCoordinates( 0, 0 );
  p2 = mTransform->toMapCoordinates( snapPixels, 0 );

  if ( mProjectionEnabled )
  {
    try
    {
      p1 = mCanvas->mapRenderer()->mapToLayerCoordinates( mLayer, p1 );
      p2 = mCanvas->mapRenderer()->mapToLayerCoordinates( mLayer, p2 );
    }
    catch ( QgsCsException& cse )
    {
      Q_UNUSED( cse );
      //error
    }
  }

  double dx = p2.x() - p1.x();
  double dy = p2.y() - p1.y();
  double thresh = sqrt( dx * dx + dy * dy );
  return thresh;
}

void QgsGrassEdit::snap( double *x, double *y )
{
  double thresh = threshold();

  int node = mProvider->findNode( *x, *y, thresh );

  if ( node > 0 )
  {
    mProvider->nodeCoor( node, x, y );
  }
}

void QgsGrassEdit::snap( QgsPoint & point )
{
  double x = point.x();
  double y = point.y();

  snap( &x, &y );

  point.setX( x );
  point.setY( y );
}

void QgsGrassEdit::snap( QgsPoint & point, double startX, double startY )
{
  double x = point.x();
  double y = point.y();

  double thresh = threshold();

  // Start
  double startDist = hypot( x - startX, y - startY );
  bool startIn = false;
  if ( startDist <= thresh ) startIn = true;

  // Nearest node
  double nodeX = 0;
  double  nodeY = 0;
  double nodeDist = 0;
  bool nodeIn = false;
  int node = mProvider->findNode( x, y, thresh );

  if ( node > 0 )
  {
    mProvider->nodeCoor( node, &nodeX, &nodeY );
    nodeDist = hypot( x - nodeX, y - nodeY );
    nodeIn = true;
  }

  // Choose
  if (( startIn && !nodeIn ) || ( startIn && nodeIn && startDist < nodeDist ) )
  {
    x = startX; y = startY;
  }
  else if (( !startIn && nodeIn ) || ( startIn && nodeIn && startDist > nodeDist ) )
  {
    x = nodeX; y = nodeY;
  }

  point.setX( x );
  point.setY( y );
}

void QgsGrassEdit::newPoint( void )
{
  startTool( QgsGrassEdit::NEW_POINT );
}

void QgsGrassEdit::newLine( void )
{
  QgsDebugMsg( "entered." );
  startTool( QgsGrassEdit::NEW_LINE );
}

void QgsGrassEdit::newBoundary( void )
{
  QgsDebugMsg( "entered." );
  startTool( QgsGrassEdit::NEW_BOUNDARY );
}

void QgsGrassEdit::newCentroid( void )
{
  startTool( QgsGrassEdit::NEW_CENTROID );
}

void QgsGrassEdit::moveVertex( void )
{
  startTool( QgsGrassEdit::MOVE_VERTEX );
}

void QgsGrassEdit::addVertex( void )
{
  startTool( QgsGrassEdit::ADD_VERTEX );
}

void QgsGrassEdit::deleteVertex( void )
{
  startTool( QgsGrassEdit::DELETE_VERTEX );
}

void QgsGrassEdit::splitLine( void )
{
  startTool( QgsGrassEdit::SPLIT_LINE );
}

void QgsGrassEdit::moveLine( void )
{
  startTool( QgsGrassEdit::MOVE_LINE );
}

void QgsGrassEdit::deleteLine( void )
{
  startTool( QgsGrassEdit::DELETE_LINE );
}

void QgsGrassEdit::editCats( void )
{
  startTool( QgsGrassEdit::EDIT_CATS );
}

void QgsGrassEdit::editAttributes( void )
{
  startTool( QgsGrassEdit::EDIT_ATTRIBUTES );
}

void QgsGrassEdit::startTool( int tool )
{
  QgsDebugMsg( QString( "tool = %1" ).arg( tool ) );

  // Delete last dynamic drawing from canvas
  eraseDynamic();
  if ( mSelectedLine > 0 )
    displayElement( mSelectedLine, mSymb[mLineSymb[mSelectedLine]], mSize );

  // close old tool
  if ( mMapTool )
  {
    delete mMapTool;
    mMapTool = NULL;
  }

  // All necessary data were written -> reset mEditPoints etc.
  Vect_reset_line( mEditPoints );
  mSelectedLine = 0;

  // TODO: mTool != NEW_LINE is a hack for lines until more buttons can be recieved
  if ( mAttributes && mTool != QgsGrassEdit::NEW_LINE && mTool != QgsGrassEdit::NEW_BOUNDARY )
  {
    delete mAttributes;
    mAttributes = 0;
  }

  // Start new tool
  mTool = tool;

  switch ( mTool )
  {
    case NEW_POINT:
      mMapTool = new QgsGrassEditNewPoint( this, false );
      mMapTool->setAction( mNewPointAction );
      break;

    case NEW_CENTROID:
      mMapTool = new QgsGrassEditNewPoint( this, true );
      mMapTool->setAction( mNewCentroidAction );
      break;

    case NEW_LINE:
      mMapTool = new QgsGrassEditNewLine( this, false );
      mMapTool->setAction( mNewLineAction );
      break;

    case NEW_BOUNDARY:
      mMapTool = new QgsGrassEditNewLine( this, true );
      mMapTool->setAction( mNewBoundaryAction );
      break;

    case MOVE_VERTEX:
      mMapTool = new QgsGrassEditMoveVertex( this );
      mMapTool->setAction( mMoveVertexAction );
      break;

    case ADD_VERTEX:
      mMapTool = new QgsGrassEditAddVertex( this );
      mMapTool->setAction( mAddVertexAction );
      break;

    case DELETE_VERTEX:
      mMapTool = new QgsGrassEditDeleteVertex( this );
      mMapTool->setAction( mDeleteVertexAction );
      break;

    case MOVE_LINE:
      mMapTool = new QgsGrassEditMoveLine( this );
      mMapTool->setAction( mMoveLineAction );
      break;

    case DELETE_LINE:
      mMapTool = new QgsGrassEditDeleteLine( this );
      mMapTool->setAction( mDeleteLineAction );
      break;

    case SPLIT_LINE:
      mMapTool = new QgsGrassEditSplitLine( this );
      mMapTool->setAction( mSplitLineAction );
      break;

    case EDIT_ATTRIBUTES:
      mMapTool = new QgsGrassEditAttributes( this );
      mMapTool->setAction( mEditAttributesAction );
      break;

    case EDIT_CATS:
      mTool = NONE;
      QMessageBox::warning( 0, tr( "Warning" ), tr( "Tool not yet implemented." ) );
      break;

    default:
      QgsDebugMsg( "Unknown tool" );
      break;
  }

  // assign newly created tool to map canvas
  mCanvas->setMapTool( mMapTool );
}

void QgsGrassEdit::checkOrphan( int field, int cat )
{
  QgsDebugMsg( QString( "field = %1 cat = %2" ).arg( field ).arg( cat ) );

  int orphan;
  QString *error = mProvider->isOrphan( field, cat, &orphan );

  if ( !error->isEmpty() )
  {
    QMessageBox::warning( 0, tr( "Warning" ),
                          tr( "Cannot check orphan record: %1" ).arg( *error ) );
    return;
  }
  if ( !orphan ) return;

  QMessageBox::StandardButton ret = QMessageBox::question( 0, tr( "Warning" ),
                                    tr( "Orphan record was left in attribute table. "
                                        "<br>Delete the record?" ),
                                    QMessageBox::Ok | QMessageBox::Cancel );

  if ( ret == QMessageBox::Cancel ) return;

  // Delete record
  error = mProvider->deleteAttributes( field, cat );
  if ( !error->isEmpty() )
  {
    QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot delete orphan record: " )
                          + *error );
    return;
  }
}

void QgsGrassEdit::addAttributes( int field, int cat )
{
  QString *key = mProvider->key( field );

  QString lab;
  lab.sprintf( "%d:%d", field, cat );
  int tab = mAttributes->addTab( lab );
  mAttributes->setField( tab, field );

  QString catLabel;
  if ( key->isEmpty() )
  {
    catLabel = "Category";
  }
  else
  {
    catLabel = *key;
  }
  mAttributes->setCat( tab, catLabel, cat );

  if ( !key->isEmpty() )   // Database link defined
  {
    std::vector<QgsField> *cols = mProvider->columns( field );

    if ( cols->size() == 0 )
    {
      QString str;
      str.setNum( field );
      QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot describe table for field %1" ).arg( str ) );
    }
    else
    {
      QgsAttributeMap *atts = mProvider->attributes( field, cat );

      if ( atts->size() == 0 )   // cannot select attributes
      {
        mAttributes->addTextRow( tab, "WARNING: ATTRIBUTES MISSING" );
      }
      else
      {
        int size;
        if ( atts->size() < ( int ) cols->size() )
          size = atts->size();
        else
          size = cols->size();

        for ( unsigned int j = 0; j < cols->size(); j++ )
        {
          QgsField col = ( *cols )[j];
          QVariant att = ( *atts )[j];
          QgsDebugMsg( QString( " name = %1" ).arg( col.name() ) );

          if ( col.name() != *key )
          {
            QgsDebugMsg( QString( " value = %1" ).arg( att.toString() ) );
            mAttributes->addAttribute( tab, col.name(), att.toString(), col.typeName() );
          }
        }
      }
      delete atts;
    }
    delete cols;
  }
}

void QgsGrassEdit::addCat( int line )
{
  int mode = mCatModeBox->currentIndex();
  int field = mFieldBox->currentText().toInt();
  int cat = mCatEntry->text().toInt();

  int type = mProvider->readLine( mPoints, mCats, line );
  if ( mode == CAT_MODE_NEXT || mode == CAT_MODE_MANUAL )
  {
    Vect_cat_set( mCats, field, cat );
  }

  line = mProvider->rewriteLine( line, type, mPoints, mCats );
  mSelectedLine = line;
  if ( mAttributes ) mAttributes->setLine( line );
  updateSymb();
  increaseMaxCat();

  // Insert new DB record if link is defined and the record for this cat does not exist
  QString *key = mProvider->key( field );

  if ( !key->isEmpty() )   // Database link defined
  {
    QgsAttributeMap *atts = mProvider->attributes( field, cat );

    if ( atts->size() == 0 )   // Nothing selected
    {
      QString *error = mProvider->insertAttributes( field, cat );

      if ( !error->isEmpty() )
      {
        QMessageBox::warning( 0, tr( "Warning" ), *error );
      }
      delete error;
    }

    delete atts;
  }

  addAttributes( field, cat );
}

void QgsGrassEdit::deleteCat( int line, int field, int cat )
{
  QgsDebugMsg( "entered." );

  int type = mProvider->readLine( mPoints, mCats, line );
  Vect_field_cat_del( mCats, field, cat );

  line = mProvider->rewriteLine( line, type, mPoints, mCats );
  mSelectedLine = line;
  if ( mAttributes ) mAttributes->setLine( line );

  // Check orphan record
  checkOrphan( field, cat );

  updateSymb();
}


void QgsGrassEdit::postRender( QPainter * )
{
  QgsDebugMsg( "entered." );

  // Warning: it seems that this slot can be called even
  //          after disconnect (is it a queue?)
  //          -> check mValid

  if ( !mValid ) return;

  displayMap();

  // Redisplay highlighted
  if ( mSelectedLine )
  {
    displayElement( mSelectedLine, mSymb[SYMB_HIGHLIGHT], mSize );
  }
}

void QgsGrassEdit::displayMap()
{
  QgsDebugMsg( "entered." );

  mTransform = mCanvas->getCoordinateTransform();

  // re-create pixmap - it's transparent by default
  *mPixmap = QPixmap( mCanvas->size() );
  mPixmap->fill( QColor( 0, 0, 0, 0 ) );

  QPainter *painter = new QPainter();
  painter->begin( mPixmap );

  // Display lines
  int nlines = mProvider->numLines();

  QPen pen;

  // TODO?: 2 loops, first lines, then points
  for ( int line = 1; line <= nlines; line++ )
  {
    displayElement( line, mSymb[mLineSymb[line]], mSize, painter );
  }

  // Display nodes
  int nnodes = mProvider->numNodes();

  pen.setColor( QColor( 255, 0, 0 ) );

  if ( mSymbDisplay[SYMB_NODE_1] || mSymbDisplay[SYMB_NODE_2] )
  {
    for ( int node = 1; node <= nnodes; node++ )
    {
      if ( mNodeSymb[node] == SYMB_NODE_0 ) continue; // do not display nodes with points only
      displayNode( node, mSymb[mNodeSymb[node]], mSize, painter );
    }
  }

  painter->end();
  delete painter;

  // porting mCanvas->update();
  mCanvasEdit->update();
  mRubberBandIcon->update();
  mRubberBandLine->update();
}

void QgsGrassEdit::displayUpdated( void )
{
  QgsDebugMsg( "entered." );

  mTransform = mCanvas->getCoordinateTransform();
  mProjectionEnabled = ( QgsProject::instance()->readNumEntry( "SpatialRefSys", "/ProjectionsEnabled", 0 ) != 0 );

  QPainter *painter = new QPainter();
  painter->begin( mPixmap );

  // Display lines
  int nlines = mProvider->numUpdatedLines();

  for ( int i = 0; i < nlines; i++ )
  {
    int line = mProvider->updatedLine( i );
    if ( !( mProvider->lineAlive( line ) ) ) continue;

    displayElement( line, mSymb[mLineSymb[line]], mSize, painter );
  }

  // Display nodes
  int nnodes = mProvider->numUpdatedNodes();
  for ( int i = 0; i < nnodes; i++ )
  {
    int node = mProvider->updatedNode( i );
    if ( !( mProvider->nodeAlive( node ) ) ) continue;
    if ( mNodeSymb[node] == SYMB_NODE_0 ) continue; // do not display nodes with points only
    displayNode( node, mSymb[mNodeSymb[node]], mSize, painter );
  }

  painter->end();
  delete painter;

  // porting mCanvas->update();
  mCanvasEdit->update();
  mRubberBandIcon->update();
  mRubberBandLine->update();
}

void QgsGrassEdit::displayElement( int line, const QPen & pen, int size, QPainter *painter )
{
  QgsDebugMsg( QString( "line = %1" ).arg( line ) );

  // is it a valid line?
  if ( line == 0 )
    return;

  if ( !mSymbDisplay[mLineSymb[line]] ) return;

  int type = mProvider->readLine( mPoints, NULL, line );
  if ( type < 0 ) return;

  QPainter *myPainter;
  if ( !painter )
  {
    myPainter = new QPainter();
    myPainter->begin( mPixmap );
  }
  else
  {
    myPainter = painter;
  }

  if ( type & GV_POINTS )
  {
    displayIcon( mPoints->x[0], mPoints->y[0], pen, QgsVertexMarker::ICON_CROSS, size, myPainter );
  }
  else   // line
  {
    QgsPoint point;
    QPolygon pointArray( mPoints->n_points );

    for ( int i = 0; i < mPoints->n_points; i++ )
    {
      point.setX( mPoints->x[i] );
      point.setY( mPoints->y[i] );
      point = transformLayerToCanvas( point );
      pointArray.setPoint( i, static_cast<int>( round( point.x() ) ),
                           static_cast<int>( round( point.y() ) ) );
    }

    myPainter->setPen( pen );
    myPainter->drawPolyline( pointArray );
  }

  if ( !painter )
  {
    myPainter->end();
    // porting mCanvas->update();
    mCanvasEdit->update();
    delete myPainter;
  }
}

void QgsGrassEdit::eraseElement( int line )
{
  QgsDebugMsg( QString( "line = %1" ).arg( line ) );

  int type = mProvider->readLine( NULL, NULL, line );
  if ( type < 0 ) return;

  // Erase line
  displayElement( line, mSymb[SYMB_BACKGROUND], mSize );

  // Erase nodes
  if ( type & GV_LINES )
  {
    int node1, node2;
    mProvider->lineNodes( line, &node1, &node2 );

    double x, y;
    mProvider->nodeCoor( node1, &x, &y );
    displayIcon( x, y, mSymb[SYMB_BACKGROUND], QgsVertexMarker::ICON_X, mSize );

    mProvider->nodeCoor( node2, &x, &y );
    displayIcon( x, y, mSymb[SYMB_BACKGROUND], QgsVertexMarker::ICON_X, mSize );
  }
}

void QgsGrassEdit::eraseDynamic( void )
{
  displayDynamic( 0, 0.0, 0.0, QgsVertexMarker::ICON_NONE, 0 );
}

void QgsGrassEdit::displayDynamic( struct line_pnts *Points )
{
  displayDynamic( Points, 0.0, 0.0, QgsVertexMarker::ICON_NONE, 0 );
}

void QgsGrassEdit::displayDynamic( double x, double y, int type, int size )
{
  QgsDebugMsg( "entered." );

  displayDynamic( 0, x, y, type, size );
}

void QgsGrassEdit::displayDynamic( struct line_pnts *Points, double x, double y, int type, int size )
{
  QgsDebugMsg( QString( "Points = %1 type = %2" ).arg( QString::number(( qulonglong )Points, 16 ).toLocal8Bit().constData() ).arg( type ) );
  QgsPoint point;

  //mTransform = mCanvas->getCoordinateTransform();

  // Dynamic points are in layer coordinate system, we have to
  // reproject them to current coordinate system if necessary

  mRubberBandLine->reset();

  if ( Points )
  {
    for ( int i = 0; i < Points->n_points; i++ )
    {
      point.setX( Points->x[i] );
      point.setY( Points->y[i] );
      point = transformLayerToMap( point );
      mRubberBandLine->addPoint( point, false ); // false = don't update now
    }
    // Now add the last point again and force update of rubberband.
    // This should improve the performance as canvas is updated only once
    // and not with every added point to rubberband.
    mRubberBandLine->addPoint( point, true );
  }

  mRubberBandIcon->setIconType( type );
  mRubberBandIcon->setIconSize( size );
  point = transformLayerToMap( QgsPoint( x, y ) );
  mRubberBandIcon->setCenter( point );
}

void QgsGrassEdit::displayNode( int node, const QPen & pen, int size, QPainter *painter )
{
  QgsDebugMsg( QString( "node = %1" ).arg( node ) );

  if ( !mSymbDisplay[mNodeSymb[node]] ) return;

  double x, y;

  if ( !( mProvider->nodeCoor( node, &x, &y ) ) ) return;

  displayIcon( x, y, pen, QgsVertexMarker::ICON_X, size, painter );
}

QgsPoint QgsGrassEdit::transformLayerToCanvas( QgsPoint point )
{
  point = mCanvas->mapRenderer()->layerToMapCoordinates( mLayer, point );
  return mTransform->transform( point );
}

QgsPoint QgsGrassEdit::transformLayerToMap( QgsPoint point )
{
  return mCanvas->mapRenderer()->layerToMapCoordinates( mLayer, point );
}

void QgsGrassEdit::displayIcon( double x, double y, const QPen & pen,
                                int type, int size, QPainter *painter )
{
  QgsDebugMsg( "entered." );

  QgsPoint point;
  QPolygon pointArray( 2 );

  point.setX( x );
  point.setY( y );

  point = transformLayerToCanvas( point );

  int px = static_cast<int>( round( point.x() ) );
  int py = static_cast<int>( round( point.y() ) );
  int m = ( size - 1 ) / 2;

  QPainter *myPainter;
  if ( !painter )
  {
    myPainter = new QPainter();
    myPainter->begin( mPixmap );
  }
  else
  {
    myPainter = painter;
  }

  myPainter->setPen( pen );

  switch ( type )
  {
    case QgsVertexMarker::ICON_CROSS :
      pointArray.setPoint( 0, px - m, py );
      pointArray.setPoint( 1, px + m, py );
      myPainter->drawPolyline( pointArray );

      pointArray.setPoint( 0, px, py + m );
      pointArray.setPoint( 1, px, py - m );
      myPainter->drawPolyline( pointArray );
      break;
    case QgsVertexMarker::ICON_X :
      pointArray.setPoint( 0, px - m, py + m );
      pointArray.setPoint( 1, px + m, py - m );
      myPainter->drawPolyline( pointArray );

      pointArray.setPoint( 0, px - m, py - m );
      pointArray.setPoint( 1, px + m, py + m );
      myPainter->drawPolyline( pointArray );
      break;
    case QgsVertexMarker::ICON_BOX :
      pointArray.resize( 5 );
      pointArray.setPoint( 0, px - m, py - m );
      pointArray.setPoint( 1, px + m, py - m );
      pointArray.setPoint( 2, px + m, py + m );
      pointArray.setPoint( 3, px - m, py + m );
      pointArray.setPoint( 4, px - m, py - m );
      myPainter->drawPolyline( pointArray );
      break;
  }

  if ( !painter )
  {
    myPainter->end();
    //mCanvas->update();
    mCanvasEdit->update();
    delete myPainter;
  }
}

void QgsGrassEdit::setCanvasPrompt( QString left, QString mid, QString right )
{
  QgsDebugMsg( "entered." );
  mCanvasPrompt = "";
  if ( left.length() > 0 ) mCanvasPrompt.append( tr( "Left: %1   " ).arg( left ) );
  if ( mid.length() > 0 ) mCanvasPrompt.append( tr( "Middle: %1" ).arg( mid ) );
  if ( right.length() > 0 ) mCanvasPrompt.append( tr( "Right: %1" ).arg( right ) );
}

void QgsGrassEdit::attributesClosed()
{
  mAttributes = 0;
}
