/* ====================================================================
 * Copyright (c) 2003-2006, Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

// sc
#include "config.h"
#include "WorkingCopyFilesWidget.h"
#include "WorkingCopyLvi.h"
#include "LockDialog.h"
#include "UnlockDialog.h"
#include "LogGraphDialog.h"
#include "BlameDialog.h"
#include "MoveDialog.h"
#include "PropertiesDialog.h"
#include "WcModel.h"
#include "ActionStorage.h"
#include "WcStatusLvi.h"
#include "CursorSupport.h"
#include "ErrorSupport.h"
#include "PostCmdResult.h"
#include "SyncCmdResult.h"
#include "DragDropMimeTypes.h"
#include "ScDragObject.h"
#include "ExternProviderImpl.h"
#include "ActionStorage.h"
#include "TextWindow.h"
#include "ScModel.h"
#include "DragDropMimeTypes.h"
#include "RemoveDialog.h"
#include "MkdirDialog.h"
#include "ExportDialog.h"
#include "UpdateDialog.h"
#include "ColorId.h"
#include "StatusId.h"
#include "DragInfo.h"
#include "dialogs/CommitDialogCmd.h"
#include "dialogs/CopyDialogCmd.h"
#include "events/LoggingEvent.h"
#include "events/UpdateWcEvent.h"
#include "events/ScParamEvent.h"
#include "commands/ScCmd.h"
#include "commands/AddParam.h"
#include "commands/CommitParam.h"
#include "commands/DeleteParam.h"
#include "commands/DiffParam.h"
#include "commands/EditConflictParam.h"
#include "commands/MoveParam.h"
#include "commands/RevertParam.h"
#include "commands/ResolvedParam.h"
#include "commands/PropGetParam.h"
#include "commands/PropSetParam.h"
#include "commands/LockParam.h"
#include "commands/UnlockParam.h"
#include "commands/StatusParam.h"
#include "commands/CleanupParam.h"
#include "commands/MkdirParam.h"
#include "commands/ExportParam.h"
#include "commands/UpdateParam.h"
#include "sublib/PathOps.h"
#include "sublib/ColorStorage.h"
#include "sublib/MessageBox.h"
#include "svn/WcEntry.h"
#include "svn/Revision.h"
#include "svn/PropGetItem.h"
#include "util/Id.h"

// qt
#include <QtGui/QApplication>
#include <qpopupmenu.h>
#include <QtGui/QAction>
#include <QtGui/QMessageBox>
#include <qheader.h>
#include <QtGui/QToolTip>
#include <QtGui/QWhatsThis>
#include <QtCore/QDateTime>
#include <QtCore/QTimer>


///////////////////////////////////////////////////////////////////////////////

typedef SimpleCmdData<WcStatusLvi*> WcStatusLviCmdData;

///////////////////////////////////////////////////////////////////////////////

class WorkingCopyFilesWidgetResultVisitor :
  public ParamVisitor<AddParam>,
  public ParamVisitor<RevertParam>,
  public ParamVisitor<DiffParam>,
  public ParamVisitor<CommitParam>,
  public ParamVisitor<EditConflictParam>,
  public ParamVisitor<ResolvedParam>,
  public ParamVisitor<MoveParam>,
  public ParamVisitor<LockParam>,
  public ParamVisitor<UnlockParam>
{
public:
  WorkingCopyFilesWidgetResultVisitor(WorkingCopyFilesWidget* w, WcModel* m)
    : _w(w), _m(m)
  {
  }

  void visit( AddParam* p )
  {
  }

  void visit( RevertParam* p )
  {
  }

  void visit( DiffParam* p )
  {
    if( p->getPatch() )
    {
      QString title = _q("diff %1").arg( (const char*)p->getPathOrUrl1() );

      TextWindow* tw = new TextWindow( title, _m->getModel()->getFontSettings(), _w );
      tw->loadText(QString::fromUtf8(p->getPatchFile()));
      tw->show();
    }
  }

  void visit( CommitParam* p )
  {
  }

  void visit( EditConflictParam* p )
  {
  }

  void visit( MoveParam* p )
  {
  }

  void visit( ResolvedParam* p )
  {
  }

  void visit( LockParam* p )
  {
  }

  void visit( UnlockParam* p )
  {
  }

private:
  WorkingCopyFilesWidget* _w;
  WcModel*                _m;
};

///////////////////////////////////////////////////////////////////////////////

static QString tooltips[] =
{
  _n("path/name"),
  _n("text status"),
  _n("property status"),
  _n("working copy lock"),
  _n("added with history"),
  _n("switched"),
  _n("repository lock"),
  _n("update available"),
  _n("working copy revision"),
  _n("last committed revision"),
  _n("last committed author"),
  _n("press <SHIFT><F1> for a more detailed column description")
};

class DynamicTip : public QToolTip
{
public:
  DynamicTip( QListView* parent ) : QToolTip( parent ), _w(parent)
  {
  }

  virtual ~DynamicTip()
  {
  }

protected:
  void maybeTip( const QPoint& p )
  {
    QHeader* h = _w->header();
    
    for( int cnt = 0; cnt < h->count(); cnt++ )
    {
      QRect r = h->sectionRect(cnt);
      if( r.contains(p) )
      {
        QString sTip = _q(tooltips[cnt]);

        if( cnt == 0 )
        {
          sTip += " - " + h->label(cnt);
        }
        
        tip( r, sTip );
        break;
      }
    }
  }
  
private:
  QListView* _w;
};

///////////////////////////////////////////////////////////////////////////////

enum Actions
{
  ActionAdd,
  ActionRevert,
  ActionRemove,
  ActionDiffBase,
  ActionCommit,
  ActionEditConflict,
  ActionResolved,
  ActionLog,
  ActionProperty,
  ActionIgnore,
  ActionBlame,
  ActionLock,
  ActionUnlock,
  ActionCleanup,
  ActionMkdir,
  ActionExport,
  ActionUpdateRev,
  ActionBranch,
  ActionLogGraph
};

static QString getStatusCodeInfo( StatusId id )
{
  QString s;
  s += "<td align=center>" + _q(StatusCodes[id]._code) + "</td>";
  s += "<td>" + _q(StatusCodes[id]._name) + "</td>";
  s += "<td bgcolor="
    + ColorStorage::getColor(StatusCodes[id]._cid).name()
    + "></td>";
  return s;
}

static QString getStatusCodeInfo2( StatusId id )
{
  QString s;
  s += "<td align=center>" + _q(StatusCodes[id]._code) + "</td>";
  s += "<td>" + _q(StatusCodes[id]._name) + "</td>";
  return s;
}

static QString buildWhatsThis()
{
  QString ws = 
    "<qt>";
  ws += "<b>" + _q("working copy view columns") + "</b>";
  ws += 
     "<hr>"
     "<table cellspacing=0>"
      "<tr>";
  ws += "<td><nobr>" + _q("path/name") + "</nobr></td>";
  ws += "<td>" + _q("the path and name of an item - the header shows the working copy path and url") + "<br></td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("text status") + "</nobr></td>";
  ws += "<td>" + _q("whether an item is modified") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo(StatusNormal)     +"<td></td>"+ getStatusCodeInfo(StatusAdded) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusConflicted) +"<td></td>"+ getStatusCodeInfo(StatusDeleted) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusMerged)     +"<td></td>"+ getStatusCodeInfo(StatusIgnored) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusModified)   +"<td></td>"+ getStatusCodeInfo(StatusReplaced) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusExternal)   +"<td></td>"+ getStatusCodeInfo(StatusUnversioned) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusMissing)    +"<td></td>"+ getStatusCodeInfo(StatusObstructed) +"</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("property status") + "</nobr></td>";
  ws += "<td>" + _q("whether an items properties are modified") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo(StatusNormal)     +"<td></td>"+ getStatusCodeInfo(StatusConflicted) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo(StatusModified)   +"<td colspan=4></td>"+ "</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("working copy lock") + "</nobr></td>";
  ws += "<td>" + _q("whether a working copy folder is locked by another subversion operation") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusWcLockNone) +"<td></td>"+ getStatusCodeInfo2(StatusWcLockLocked) +"</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("added with history") + "</nobr></td>";
  ws += "<td>" +_q("whether an item is added with history") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusHistoryNone) +"<td></td>"+ getStatusCodeInfo2(StatusHistory) +"</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("switched") + "</nobr></td>";
  ws += "<td>" + _q("whether an item is switched relativ to its parent") + "<br>" +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusSwitchedNo) +"<td></td>"+ getStatusCodeInfo2(StatusSwitched) +"</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("repository lock") + "</nobr></td>";
  ws += "<td>" + _q("whether an item is locked in the repository") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusLockNone)   +"<td></td>"+ getStatusCodeInfo2(StatusLockLocked) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusLockOther)  +"<td></td>"+ getStatusCodeInfo2(StatusLockStolen) +"</tr>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusLockBroken) +"<td colspan=4></td>" + "</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("update available") + "</nobr></td>";
  ws += "<td>" + _q("whether an item has a newer revision in the repository") +
        "<table cellspacing=1 width=100%>";
  ws += "<tr>"+ getStatusCodeInfo2(StatusUpToDate) +"<td></td>"+ getStatusCodeInfo2(StatusUpToDateNo) +"</tr>";
  ws += "</table>"
       "</td>"
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("working copy revision") + "</nobr></td>";
  ws += "<td>" + _q("the working copy revision") + "<br></td>" +
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("last committed revision") + "</nobr></td>";
  ws += "<td>" + _q("an items last committed revision") + "<br></td>" +
      "</tr>"
      "<tr>";
  ws += "<td><nobr>" + _q("last committed author") + "</nobr></td>";
  ws += "<td>" + _q("an items last committed revision author") + "</td>" +
      "</tr>"
     "</table>"
    "</qt>";

  return ws;
}

///////////////////////////////////////////////////////////////////////////////

WorkingCopyFilesWidget::WorkingCopyFilesWidget( WcModel* model,
  QWidget *eventTarget, QWidget *parent, const char *name )
: super(parent,name), TargetId(this), _wcModel(model), _eventTarget(eventTarget),
  _menu(NULL), _doubleClick(false), _clearItems(false)
{
  QTimer* timer = new QTimer(this);
  connect( timer, SIGNAL(timeout()), this, SLOT(dragTimeout()) );
  _dragInfo  = new DragInfo(timer);
    
  QWhatsThis::add( this, buildWhatsThis() );

  setAcceptDrops(true);

  setSelectionMode( QListView::Extended );
  setItemMargin( 2 );
  setShowToolTips( true );
  setAllColumnsShowFocus(true);
  addColumn( _q("file/folder") );
  addColumn( _q("ts") );                     // text
  addColumn( _q("ps") );                     // prop
  addColumn( _q("wl") );                     // working copy lock
  addColumn( _q("ah") );                     // copied, added with history
  addColumn( _q("sw") );                     // switched
  addColumn( _q("rl") );                     // repository lock
  addColumn( _q("upd") );                    // repository has updated version
  addColumn( _q("wc rev") );
  addColumn( _q("cmt rev") );
  addColumn( _q("cmt author") );
  addColumn( "" );                          // empty "background" column
  setColumnAlignment( 0, Qt::AlignLeft );
  setColumnAlignment( 1, Qt::AlignCenter );
  setColumnAlignment( 2, Qt::AlignCenter );
  setColumnAlignment( 3, Qt::AlignCenter );
  setColumnAlignment( 4, Qt::AlignCenter );
  setColumnAlignment( 5, Qt::AlignCenter );
  setColumnAlignment( 6, Qt::AlignCenter );
  setColumnAlignment( 7, Qt::AlignRight );
  setColumnAlignment( 8, Qt::AlignRight );
  setColumnAlignment( 9, Qt::AlignRight );
  setResizeMode( QListView::LastColumn );
  setSortColumn( 0 );

  connect( this, SIGNAL(selectionChanged()), SLOT(updateSelection()) );

  connect( this, SIGNAL(contextMenuRequested(QListViewItem*,const QPoint&,int)),
    SLOT(contextMenuRequest(QListViewItem*,const QPoint&,int)) );

  connect( this, SIGNAL(itemRenamed(QListViewItem*,int,const QString&)),
    SLOT(itemRenamed(QListViewItem*,int,const QString&)) );

  connect( this, SIGNAL(expanded(QListViewItem*)), SLOT(expanded(QListViewItem*)) );
  connect( this, SIGNAL(collapsed(QListViewItem*)), SLOT(collapsed(QListViewItem*)) );

  connect( this, SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)),
    SLOT(doubleClicked(QListViewItem*,const QPoint&,int)) );

  {
    _actions = new ActionStorage();
    QAction* action;

    action = new QAction( _q("&add item(s)"), _q("Ctrl+Shift+A"), this );
    action->setStatusTip( _q("add new file(s) or folder(s) to the working copy (R)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(add()) );
    _actions->addAction( ActionAdd, action );

    action = new QAction( _q("re&vert item(s)"), _q("Ctrl+Shift+V"), this );
    action->setStatusTip( _q("revert the changes on file(s) or folder(s) in the working copy (R)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(revert()) );
    _actions->addAction( ActionRevert, action );

    action = new QAction( _q("&remove item(s)"), _q("Ctrl+Shift+R"), this );
    action->setStatusTip( _q("remove file(s) or folder(s) from the working copy (R)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(remove()) );
    _actions->addAction( ActionRemove, action );

    action = new QAction( _q("&commit.."), _q("Ctrl+Shift+C"), this );
    action->setStatusTip( _q("commit your changes in the working copy to the repository (R)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(commit()) );
    _actions->addAction( ActionCommit, action );

    action = new QAction( _q("&diff"), _q("Ctrl+Shift+D"), this );
    action->setStatusTip( _q("show the differences between working copy and base") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(diff()) );
    _actions->addAction( ActionDiffBase, action );

    action = new QAction( _q("edit con&flict"), _q("Ctrl+Shift+F"), this );
    action->setStatusTip( _q("resolve the conflicts of the selected file") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(edit()) );
    _actions->addAction( ActionEditConflict, action );

    action = new QAction( _q("resolved conflicts"), _q("Ctrl+Shift+W"), this );
    action->setStatusTip( _q("mark the conflicts as resolved") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(resolved()) );
    _actions->addAction( ActionResolved, action );

    action = new QAction( _q("&log.."), _q("Ctrl+Shift+L"), this );
    action->setStatusTip( _q("view the log history of the selected file or folder") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(log()) );
    _actions->addAction( ActionLog, action );

    action = new QAction( _q("log &graph.."), _q("Ctrl+Shift+G"), this );
    action->setStatusTip( _q("view the graphical log history of the selected file or folder") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(loggraph()) );
    _actions->addAction( ActionLogGraph, action );

    action = new QAction( _q("properties.."), _q("Ctrl+Shift+P"), this );
    action->setStatusTip( _q("modify the properties of the selected file or folder") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(properties()) );
    _actions->addAction( ActionProperty, action );

    action = new QAction( _q("svn:ignore"), _q("Ctrl+Shift+I"), this );
    action->setStatusTip( _q("ignore the selected file(s) or folder(s)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(ignore()) );
    _actions->addAction( ActionIgnore, action );

    action = new QAction( _q("blam&e.."), _q("Ctrl+Shift+E"), this );
    action->setStatusTip( _q("view blame information") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(blame()) );
    _actions->addAction( ActionBlame, action );

    action = new QAction( _q("lock item(s).."), _q("Ctrl+Shift+Y"), this );
    action->setStatusTip( _q("lock selected working copy item(s)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(lock()) );
    _actions->addAction( ActionLock, action );

    action = new QAction( _q("unlock item(s).."), _q("Ctrl+Shift+Z"), this );
    action->setStatusTip( _q("unlock selected working copy item(s)") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(unlock()) );
    _actions->addAction( ActionUnlock, action );

    action = new QAction( _q("cleanup"), QString(""), this );
    action->setStatusTip( _q("cleanup a working copy path") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(cleanup()) );
    _actions->addAction( ActionCleanup, action );

    action = new QAction( _q("m&kdir.."), _q("Ctrl+Shift+K"), this );
    action->setStatusTip( _q("create and add a new folder to the working copy") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(mkdir()) );
    _actions->addAction( ActionMkdir, action );

    action = new QAction( _q("e&xport.."), _q("Ctrl+Shift+X"), this );
    action->setStatusTip( _q("export a working copy path") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(exportx()) );
    _actions->addAction( ActionExport, action );

    action = new QAction( _q("update.."), _q("Ctrl+Shift+J"), this );
    action->setStatusTip( _q("update the working copy path or file to a specific revision") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(updateRev()) );
    _actions->addAction( ActionUpdateRev, action );

    action = new QAction( _q("branch.."), _q("Ctrl+Shift+B"), this );
    action->setStatusTip( _q("create a branch/tag from the selected working copy path") );
    action->setEnabled(false);
    connect( action, SIGNAL(activated()), SLOT(branch()) );
    _actions->addAction( ActionBranch, action );
  }

  {
    _menu = new QPopupMenu(qApp->mainWidget());
    QAction* action;

    action = _actions->getAction( ActionDiffBase );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionCommit );
    action->addTo(_menu);
    action = _actions->getAction( ActionLog );
    action->addTo(_menu);
    action = _actions->getAction( ActionLogGraph );
    action->addTo(_menu);
    action = _actions->getAction( ActionBlame );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionAdd );
    action->addTo(_menu);
    action = _actions->getAction( ActionRevert );
    action->addTo(_menu);
    action = _actions->getAction( ActionRemove );
    action->addTo(_menu);
    action = _actions->getAction( ActionUpdateRev );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionLock );
    action->addTo(_menu);
    action = _actions->getAction( ActionUnlock );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionProperty );
    action->addTo(_menu);
    action = _actions->getAction( ActionIgnore );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionEditConflict );
    action->addTo(_menu);
    action = _actions->getAction( ActionResolved );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionMkdir );
    action->addTo(_menu);

    action = _actions->getAction( ActionBranch );
    action->addTo(_menu);

    action = _actions->getAction( ActionExport );
    action->addTo(_menu);

    action = _actions->getAction( ActionCleanup );
    action->addTo(_menu);
  }
  
  
  new DynamicTip(this);
}

WorkingCopyFilesWidget::~WorkingCopyFilesWidget()
{
  delete _actions;
  delete _dragInfo;
}

void WorkingCopyFilesWidget::focusInEvent( QFocusEvent* e )
{
  //printf("focusIn\n");

  if( e->reason() == QFocusEvent::Popup )
  {
    return;
  }

  updateSelection();
}

void WorkingCopyFilesWidget::focusOutEvent( QFocusEvent* e )
{
  //printf("focusOut\n");

  if( e->reason() == QFocusEvent::Popup )
  {
    return;
  }

  _actions->disableActions();
}

void WorkingCopyFilesWidget::clear()
{
  _entries.clear();
  super::clear();
}

void WorkingCopyFilesWidget::customEvent( QCustomEvent* ce )
{
  switch( ce->type() )
  {
  case ScParameterEvent:
    {
      WorkingCopyFilesWidgetResultVisitor v(this,_wcModel);
      ScParamEvent* pe = dynamic_cast<ScParamEvent*>(ce);
      pe->getParam()->accept(&v);
      break;
    }
  default:
    {
      printf( "WorkingCopyFilesWidget: unknown custom event type %d!\n", ce->type() );
    }
  }
}

QDragObject* WorkingCopyFilesWidget::dragObject()
{
  return new ScDragObject(ScMimeTypeWcFilesItem,_wcModel,this);
}

void WorkingCopyFilesWidget::dragTimeout()
{
  // drag scroll
  if( visibleHeight() < contentsHeight() )
  {
    int delta = _dragInfo->getScrollDelta(contentsHeight(),visibleHeight());
    if( delta != 0 )
    {
      scrollBy( 0, delta );
      return;
    }
  }
  
  // drag open/close
  _dragInfo->handleIdleItem();  
}

void WorkingCopyFilesWidget::dragMoveEvent( QDragMoveEvent* e )
{
  QPoint pos = viewport()->mapFromParent(e->pos());
  _dragInfo->setDragPos(pos);
  
  QListViewItem* lvi = itemAt( pos );
  _dragInfo->setCurrLvi(lvi);
  
  if( lvi )
  {
    e->accept(lvi->acceptDrop(e));
  }
  else
  {
    e->accept(false);
  }
  
  _dragInfo->startTimer();
}

void WorkingCopyFilesWidget::dragLeaveEvent( QDragLeaveEvent* e )
{
  _dragInfo->stopTimer();
}

void WorkingCopyFilesWidget::dropEvent( QDropEvent* e )
{
  _dragInfo->stopTimer();
  
  QPoint          pos = viewport()->mapFromParent(e->pos());
  QListViewItem* item = itemAt( pos );
  WcStatusLvi*   lvi  = dynamic_cast<WcStatusLvi*>(item);

  if( ! lvi )
    return;

  if( e->action() == QDropEvent::Copy )
  {
    e->accept();
    _wcModel->copy2( lvi->getStatus()->getName(), new PostCmdResult(this) );
  }
  else if( e->action() == QDropEvent::Move )
  {
    e->accept();
    _wcModel->move2( lvi->getStatus()->getName(), new PostCmdResult(this) );
  }
}

QPopupMenu* WorkingCopyFilesWidget::getMenu()
{
  return _menu;
}

void WorkingCopyFilesWidget::reload()
{
  _clearItems = true;

  status( _wcModel->getCurrentPath(), true );
}

void WorkingCopyFilesWidget::itemRenamed( QListViewItem* item, int col, const QString& text )
{
  WcStatusLvi* lvi = dynamic_cast<WcStatusLvi*>(item);

  QString newName = lvi->getParentPath() + "/" + text;

  svn::Paths paths;
  paths.push_back(lvi->getStatus()->getName());

  MoveParam* param = new MoveParam(
    paths , new svn::Revision(svn::Revision_Unspecified),
    sc::String(newName.utf8()), _wcModel->isCmdForce() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->move( param, pcres );
}

void WorkingCopyFilesWidget::contextMenuRequest( QListViewItem* item, const QPoint& p ,int col )
{
  _menu->exec(p);
}

void WorkingCopyFilesWidget::getSelection( svn::WcStatuss& statuss )
{
  QListViewItemIterator it( this, QListViewItemIterator::Selected );
  while( it.current() )
  {
    statuss.push_back( ((WcStatusLvi*)it.current())->getStatus() );
    ++it;
  }
}

void WorkingCopyFilesWidget::updateSelection()
{
  svn::WcStatuss statuss;
  getSelection( statuss );
  _wcModel->setSelection( statuss );
  bool hasSelection = statuss.size() > 0;

  // enable/disable actions based on current selection
  _actions->enableAction( ActionLog, _wcModel->isVersioned() );
  _actions->enableAction( ActionAdd, _wcModel->isAddable() );
  _actions->enableAction( ActionRevert, _wcModel->isRevertable() );
  _actions->enableAction( ActionRemove, _wcModel->isRemoveable() );
  _actions->enableAction( ActionDiffBase, _wcModel->isDiffable() );
  _actions->enableAction( ActionEditConflict, _wcModel->isConflicted() );
  _actions->enableAction( ActionResolved, _wcModel->isConflicted() || _wcModel->isPropConflicted() );
  _actions->enableAction( ActionCommit, _wcModel->isCommitable() );
  _actions->enableAction( ActionProperty, _wcModel->isVersioned() );
  _actions->enableAction( ActionIgnore, _wcModel->isUnversionedAll() );
  _actions->enableAction( ActionBlame, _wcModel->isVersionedFile() );
  _actions->enableAction( ActionLock, _wcModel->isVersionedAll() );
  _actions->enableAction( ActionUnlock, _wcModel->isVersionedAll() );
  _actions->enableAction( ActionCleanup, _wcModel->isVersionedDir() );
  _actions->enableAction( ActionExport, _wcModel->isVersionedDir() );
  _actions->enableAction( ActionMkdir, _wcModel->isVersionedDir() );
  _actions->enableAction( ActionUpdateRev, _wcModel->isVersioned() );
  _actions->enableAction( ActionBranch, hasSelection );
  _actions->enableAction( ActionLogGraph, _wcModel->isVersioned() );
}

void WorkingCopyFilesWidget::collapsed( QListViewItem* item )
{
  // ignore calls caused by a double click
  if( _doubleClick )
  {
    _doubleClick = false;
  }
}

void WorkingCopyFilesWidget::expanded( QListViewItem* item )
{
  // ignore calls caused by a double click
  if( _doubleClick )
  {
    _doubleClick = false;
    return;
  }

  WcStatusLvi* lvi = dynamic_cast<WcStatusLvi*>(item);
  status( lvi->getStatus()->getName(), false );
}

void WorkingCopyFilesWidget::doubleClicked( QListViewItem* item, const QPoint& p, int col )
{
  WcStatusLvi* lvi = dynamic_cast<WcStatusLvi*>(item);
  if( !lvi || !lvi->getStatus()->isDir() )
  {
    return;
  }

  sc::String target;

  if( lvi->text(0) == "." )
  {
    // parent
    target = _wcModel->getParentPath();
  }
  else
  {
    // child
    target = lvi->getStatus()->getName();
  }

  if( target.isEmpty() )
  {
    // no target, then there is no parent
    return;
  }

  _wcModel->setCurrentPath( target );
  _clearItems  = true;
  _doubleClick = true;

  status( target, false );
}

void WorkingCopyFilesWidget::status( const sc::String& path, bool reload )
{
  StatusParam* param = new StatusParam( path,
    new svn::Revision(svn::Revision_Unspecified), _wcModel->getStatusRecurse(),
    true /* that is correct! */, _wcModel->getStatusUpdates(),
    _wcModel->getStatusIgnored(), reload );

  param->setCmdData( new IdCmdData(_wcModel->getWcId()) );

  PostCmdResult* pcres = new PostCmdResult( _eventTarget );

  _wcModel->status( param, pcres );
}

void WorkingCopyFilesWidget::updateView()
{
  if( _clearItems )
  {
    clear();
    _clearItems = false;
  }

  QString wcCurrentPath;
  wcCurrentPath = QString::fromUtf8(_wcModel->getCurrentPath());
  wcCurrentPath += " (";
  wcCurrentPath += QString::fromUtf8(_wcModel->getCurrentUrl());
  wcCurrentPath += ")";
  setColumnText( 0, wcCurrentPath );
  //QToolTip::add( header(), wcCurrentPath );

  if( _wcModel->getStatusRecurse() )
  {
    updateViewFlat();
  }
  else
  {
    updateViewTree();
  }
}

void WorkingCopyFilesWidget::updateViewFlat()
{
  setRootIsDecorated(false);

  const WcModel::MapStatus& folders = _wcModel->getModStatus();
  const WcModel::MapStatus& statuss = _wcModel->getNewStatus();

  for( WcModel::MapStatus::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it).second;
    const svn::WcEntry*    entry  = status->getWcEntry();

    // not All and not Changed
    if( ! _wcModel->getStatusAll() && ! status->isChangedOrFlaged() )
    {
      if( entry && entry->isDir() )
      {
        // not a changed folder?
        if( folders.find(status->getName()) == folders.end() )
        {
          continue;
        }
      }
      else
      {
        continue;
      }
    }


    // ignore duplicate entries

    std::pair<Entries::iterator,bool> pair =
      _entries.insert( Entries::value_type(status->getName(),0) );

    if( ! pair.second )
    {
      continue;
    }

    WcStatusLvi* lvi = new WcStatusLvi( this, status );
    lvi->setParentPath( QString::fromUtf8(_wcModel->getCurrentPath()) );
    insertItem(lvi);

    pair.first->second = lvi;
  }

  if( horizontalScrollBar()->isVisible() )
  {
    adjustColumn(6);
  }
}

void WorkingCopyFilesWidget::updateViewTree()
{
  setRootIsDecorated(true);

  const WcModel::MapStatus& folders = _wcModel->getModStatus();
  const WcModel::MapStatus& statuss = _wcModel->getNewStatus();

  for( WcModel::MapStatus::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it).second;
    const svn::WcEntry*    entry  = status->getWcEntry();

    // not All and not Changed
    if( ! _wcModel->getStatusAll() && ! status->isChangedOrFlaged() )
    {
      if( entry && entry->isDir() )
      {
        // not a changed folder?
        if( folders.find(status->getName()) == folders.end() )
        {
          //continue;
        }
      }
      else
      {
        continue;
      }
    }


    // ignore duplicate entries
    std::pair<Entries::iterator,bool> pair =
      _entries.insert( Entries::value_type(status->getName(),0) );

    if( ! pair.second )
    {
      continue;
    }


    // get parent lvi

    QString path   = QString::fromUtf8(status->getName());
    QString last   = path.section( '/', -1 );
    QString parent = path.left( path.length() -last.length() -1 );
    
    Entries::iterator parIt = _entries.find( sc::String(parent.utf8()) );
    if( parIt != _entries.end() )
    {
      // child items
      WcStatusLvi* lvi = new WcStatusLvi( (*parIt).second, status );
      lvi->setParentPath( parent );

      if( status->isDir() )
      {
        lvi->setExpandable(true);
      }

      pair.first->second = lvi;
    }
    else
    {
      // root item
      WcStatusLvi* lvi = new WcStatusLvi( this, status );
      lvi->setParentPath( path );

      if( status->isDir() )
      {
        lvi->setExpandable(true);
      }
      _doubleClick = true;
      lvi->setOpen(true);

      pair.first->second = lvi;
    }
  }

  if( horizontalScrollBar()->isVisible() )
  {
    adjustColumn(6);
  }
}

void WorkingCopyFilesWidget::add()
{
  svn::Paths paths;
  _wcModel->getSelection(paths);

  AddParam* param =
    new AddParam( paths, _wcModel->isCmdRecursive(), false /*TODO*/ );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->add( param, pcres );
}

void WorkingCopyFilesWidget::revert()
{
  // \todo add recurse checkmark

  int answer = msgInformation( _q("subcommander:revert"),
    _q("Reverting modified files or folders will permanently remove your changes.\nRevert anyway?"),
    _q("&Ok"), _q("&Cancel")
  );

  if( answer != QMessageBox::Ok )
  {
    return;
  }

  svn::Paths paths;
  _wcModel->getSelection(paths);

  RevertParam*   param = new RevertParam( paths, _wcModel->isCmdRecursive() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->revert( param, pcres );
}

void WorkingCopyFilesWidget::remove()
{
  svn::WcStatuss items;
  _wcModel->getSelection(items);

  svn::Paths versioned;
  svn::Paths unversioned;

  for( svn::WcStatuss::iterator it = items.begin(); it != items.end(); it++ )
  {
    if( (*it)->isVersioned() )
    {
      versioned.push_back( (*it)->getName() );
    }
    else
    {
      unversioned.push_back( (*it)->getName() );
    }
  }


  RemoveDialog* dlg =
    new RemoveDialog( this, _wcModel->getModel()->getOptionCommandForce(),
    unversioned.size() != 0 );

  int answer = dlg->exec();
  
  if( answer != QDialog::Accepted )
  {
    return;
  }

  DeleteParam*   param = new DeleteParam( versioned, false );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->remove( param, pcres );

  _wcModel->remove( unversioned );
}

void WorkingCopyFilesWidget::diff()
{
  PostCmdResult* pcres = new PostCmdResult(this);
  _wcModel->diffBaseWorking(pcres);
}

void WorkingCopyFilesWidget::commit()
{
  svn::Paths paths;
  _wcModel->getSelection(paths);
  
  CommitDialogCmd cmd( this, getTid(), _wcModel );
  
  cmd.run(paths);
}

void WorkingCopyFilesWidget::edit()
{
  svn::WcStatuss statuss;
  _wcModel->getSelection(statuss);

  EditConflictParam* param = new EditConflictParam( statuss.front() );
  PostCmdResult*     pcres = new PostCmdResult( this );

  _wcModel->edit( param, pcres );
}

void WorkingCopyFilesWidget::resolved()
{
  int answer = msgInformation( _q("subcommander:resolved"),
    _q("Do you have resolved all conflicts?"),
    _q("&Ok"), _q("&Cancel") );
  
  if( answer != QMessageBox::Ok )
  {
    return;
  }

  svn::Paths paths;
  _wcModel->getSelection(paths);

  ResolvedParam* param = new ResolvedParam( paths.front(), _wcModel->isCmdRecursive() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->resolved( param, pcres );
}

void WorkingCopyFilesWidget::log()
{
#if 0
  svn::WcStatuss statuss;
  _wcModel->getSelection(statuss);
  svn::WcStatusPtr status = statuss.front();

  LogDialog* ldlg = new LogDialog( _wcModel, _wcModel->getProject(),
    status->getName(), status->isDir() );

  ldlg->show();
#endif
}

void WorkingCopyFilesWidget::move()
{
  svn::Paths paths;
  _wcModel->getSelection(paths);

  ExternProviderImpl externProvider( _wcModel );
  MoveDialog* mdlg = new MoveDialog( &externProvider, this );

  QString path = QString::fromUtf8(paths.front());
  mdlg->setWcTargetPath( path );

  int result = mdlg->exec();

  if( result != QDialog::Accepted )
  {
    return;
  }

  MoveParam* param = new MoveParam( paths, mdlg->getRevision(),
   sc::String(mdlg->getWcTargetPath().utf8()), mdlg->isForce() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->move( param, pcres );
}

void WorkingCopyFilesWidget::properties()
{
  PropertiesDialog* dlg = new PropertiesDialog(_wcModel,this);

  svn::WcStatuss statuss;
  _wcModel->getSelection(statuss);

  dlg->setSource( statuss.front() );

  dlg->show();
}

void WorkingCopyFilesWidget::ignore()
{
  svn::WcStatuss statuss;
  _wcModel->getSelection(statuss);

  for( svn::WcStatuss::iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const sc::String& ignoreFull = (*it)->getName();

    QString tmp;
    tmp = QString::fromUtf8(ignoreFull).section( '/', -1 );
    sc::String ignore = sc::String(tmp.utf8());

    tmp = QString::fromUtf8(ignoreFull).section( '/', 0, -2 );
    sc::String parent = sc::String(tmp.utf8());

    //if( ! _wcModel->getModel()->isWorkingCopy(parent) )
    //{
      // TODO error...
      //return;
    //}

    PropGetParam* paramGet = new PropGetParam( sc::String("svn:ignore"), parent,
      new svn::Revision(svn::Revision_Unspecified), false );

    SyncCmdResult resGet;
    _wcModel->propget( paramGet, &resGet, false );

    if( resGet.getError() )
    {
      handleError(resGet.getError(),this);
      return;
    }

    sc::String propValue;

    svn::PropGetItems& items = paramGet->getItems();
    if( items.size() == 1 )
    {
      svn::PropGetItemPtr item = items.front();
      propValue = item->getValue();
    }

    propValue += ignore;
    propValue += "\n";


    PropSetParam* paramSet = new PropSetParam( sc::String("svn:ignore"), propValue, parent, false );

    SyncCmdResult resSet;
    _wcModel->propset( paramSet, &resSet, false );

    if( resSet.getError() )
    {
      handleError(resSet.getError(),this);
      return;
    }
  }
}

void WorkingCopyFilesWidget::blame()
{
#if 0
  BlameDialog* dlg = new BlameDialog( _wcModel );

  svn::Paths paths;
  _wcModel->getSelection(paths);

  dlg->setSource( paths.front() );

  dlg->show();
#endif
}

void WorkingCopyFilesWidget::lock()
{
  LockDialog* dlg = new LockDialog( _wcModel->getModel(), this );

  int result = dlg->exec();
  if( result != QDialog::Accepted )
  {
    return;
  }

  svn::Paths paths;
  _wcModel->getSelection(paths);

  LockParam* param = new LockParam( paths, sc::String(dlg->getLogMessage().utf8()),
    dlg->isStealLock() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->lock( param, pcres );
}

void WorkingCopyFilesWidget::unlock()
{
  UnlockDialog* dlg = new UnlockDialog( _wcModel->getModel(), this );

  int result = dlg->exec();
  if( result != QDialog::Accepted )
  {
    return;
  }

  svn::Paths paths;
  _wcModel->getSelection(paths);

  UnlockParam*   param = new UnlockParam( paths, dlg->isBreakLock() );
  PostCmdResult* pcres = new PostCmdResult( this );

  _wcModel->unlock( param, pcres );
}

void WorkingCopyFilesWidget::cleanup()
{
  svn::Paths paths;
  _wcModel->getSelection(paths);

  CleanupParam*  param = new CleanupParam(paths.front());
  PostCmdResult* pcres = new PostCmdResult(this);

  _wcModel->cleanup( param, pcres );
}

void WorkingCopyFilesWidget::mkdir()
{
  MkdirDialog* mdlg = new MkdirDialog(this);
  int result = mdlg->exec();
  if( result != QDialog::Accepted )
  {
    return;
  }

  svn::Paths sel;
  _wcModel->getSelection(sel);

  svn::Paths paths;

  for( svn::Paths::iterator it = sel.begin(); it != sel.end(); it++ )
  {
    sc::String path = (*it);
    path += "/" + mdlg->getFolder().utf8();

    paths.push_back( path );
  }

  MkdirParam*    param = new MkdirParam(paths);
  PostCmdResult* pcres = new PostCmdResult(this);

  _wcModel->mkdir( param, pcres );
}

void WorkingCopyFilesWidget::exportx()
{
  ExternProviderImpl externProvider(_wcModel);

  svn::Paths sel;
  _wcModel->getSelection(sel);

  ExportDialog* dlg = new ExportDialog( &externProvider, _wcModel->isCmdForce(), this );
  dlg->initSrcPath( QString::fromUtf8(sel.front()) );

  int result = dlg->exec();

  if( result != QDialog::Accepted )
  {
    return;
  }

  ExportParam*   param = dlg->getParameters();
  PostCmdResult* pcres = new PostCmdResult(this);

  _wcModel->exportx( param, pcres );
}

void WorkingCopyFilesWidget::updateRev()
{
  ExternProviderImpl externProvider(_wcModel);
  
  UpdateDialog* dlg =
    new UpdateDialog( &externProvider, _wcModel->isCmdRecursive(), this);
  
  svn::Paths sel;
  _wcModel->getSelection(sel);

  QString qsel = QString::fromUtf8(sel.front());
  dlg->setWorkingCopyPath( qsel );
  
  int result = dlg->exec();
  if( result != QDialog::Accepted )
  {
    return;
  }
  
  UpdateParam* param = new UpdateParam( 
    sc::String(dlg->getWorkingCopyPath().utf8()),
    svn::RevisionPtr(dlg->getRevision()), dlg->isRecursive() );
  PostCmdResult* pcres = new PostCmdResult(this);
 
  _wcModel->update( param, pcres );
}

void WorkingCopyFilesWidget::branch()
{
  CopyDialogCmd copy( this, getTid(), _wcModel );

  svn::Paths paths;
  _wcModel->getSelection(paths);

  QString srcPath = QString::fromUtf8(paths.front());
  QString dstPath = QString::fromUtf8(_wcModel->getProject()->getBranchesUrl());

  copy.run( srcPath, dstPath );
}

void WorkingCopyFilesWidget::loggraph()
{
#if 0
  svn::WcStatuss statuss;
  _wcModel->getSelection(statuss);
  svn::WcStatusPtr status = statuss.front();

  LogGraphDialog* dlg = new LogGraphDialog( _wcModel, _wcModel->getProject(),
    status->getName(), status->isDir() );

  dlg->show();
  dlg->run();
#endif
}
