/****************************************************************************
**
** Copyright (C) 2001-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>,
**                         Jose Hernandez <joseh@tesco.net>,
**                         Tom Mishima <tmishima@mail.at-m.or.jp>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** This program is distributed in 
** the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
** even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
** PARTICULAR PURPOSE.
**
** See the GNU General Public License version 2 for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"

#include <stdlib.h>

#include <qtextcodec.h>
#include <qstring.h>
#include <qfont.h>
#include <qstringlist.h>
#include <qpushbutton.h>
#include <qmessagebox.h>
#include <qlayout.h>
#include <qcolordialog.h>
#include <qregexp.h>
#include <qapplication.h>
#include <qinputdialog.h>
#include <qmultilineedit.h>
#include <qwhatsthis.h>
#include <qcheckbox.h>
#include <qsizegrip.h>
#include <assert.h>

#ifndef Q_WS_WIN
#include <unistd.h>
#endif

#include "DiffDialogImpl.h"
#include "AnnotateDialogImpl.h"
#include "LogDialogImpl.h"
#include "globals.h"
#include "pixmapcache.h"
#include "colortab.h"
#include "VerifyDialog.h"

bool LogDialogImpl::s_keepSettings = false;
bool LogDialogImpl::s_showTags = false;
bool LogDialogImpl::s_showBranches = false;

LogDialogImpl::LogDialogImpl(CvsDirListView * workBench,
      GuardedDir workDir,
      const QIconSet &whatsThisIconSet,
      QWidget* parent,
      const char* name, WFlags fl )
   : LogDialog( LookAndFeel::g_b0AsParent ? 0 : parent, name, fl ),
     m_workBench(workBench),
     m_workDir(workDir),
     m_treeA(0), m_treeB(0), m_parent(parent)
{
   m_pWhatsThis->setIconSet(whatsThisIconSet);
#ifdef Q_WS_MAC
  m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height() * 2);
#else
  m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height());
#endif
   buttonLayout->addWidget(new QSizeGrip(this),0,Qt::AlignRight|Qt::AlignBottom);

   connect(this,SIGNAL(checkInProgress(bool)),parent,SLOT(checkInProgress(bool)));
   connect(this,SIGNAL(showWarning(const QString &,const QString &)),parent,SLOT(showWarning(const QString &,const QString &)));

   init();
}

void LogDialogImpl::init()
{
   installEventFilter(this);

   if (bUseSmallIcons) setIcon( findEmbeddedPixmap( "LogTree16x16" ) );
   else setIcon( findEmbeddedPixmap( "LogTree32x32" ) );

   assert(m_workDir);
   m_baseDir= "";
   m_topModule = m_workDir->topControlledDir()->relativeName();

   tags.setAutoDelete(true);

   connect(m_treeZoomInButton,SIGNAL(clicked()),tree,SLOT(zoomIn()));
   connect(m_treeZoomOutButton,SIGNAL(clicked()),tree,SLOT(zoomOut()));

   connect(tree,SIGNAL(revisionClicked(LogTreeView::LogTreeItem *,bool)),this,SLOT(revisionSelected(LogTreeView::LogTreeItem *,bool)));
   connect(tree,SIGNAL(revisionDblClicked(LogTreeView::LogTreeItem *)),this,SLOT(displayFileRevision(LogTreeView::LogTreeItem *)));

   connect(list,SIGNAL(revisionClicked(LogListView::LogListItem *,bool)),this,SLOT(revisionSelected(LogListView::LogListItem *,bool)));
   connect(list,SIGNAL(revisionDblClicked(LogListView::LogListItem *)),this,SLOT(displayFileRevision(LogListView::LogListItem *)));

   for (int i=0; i<2; i++) {
      tree->setSelectionColor (i, SELECTION_COLOR_OF_LOGDLG[i]);
      list->setSelectionColor (i, SELECTION_COLOR_OF_LOGDLG[i]);
   }

   m_showMergeInfo = true;
   CheckBoxKeepSettings->setChecked(s_keepSettings);
   if (s_keepSettings) {
      CheckBoxRegularTags->setChecked(s_showTags);
      CheckBoxBranchTags->setChecked(s_showBranches);
   }
}

/*  
 *  Destroys the object and frees any allocated resources
 */
LogDialogImpl::~LogDialogImpl()
{
}

bool LogDialogImpl::eventFilter( QObject *o, QEvent *e ) {
   if ( e->type() == QEvent::ContextMenu ) {
      return TRUE; // eat event
   } else {
      // standard event processing
      return QWidget::eventFilter( o, e );
   }
}

void LogDialogImpl::parseCvsLog (QString BaseDir, QString name, QString revision, CvsBuffer *pCvsBuffer) {
   //     qDebug("startParsing: "+QDateTime::currentDateTime().toString("hh:mm:ss"));
   m_fileName = name;
   QStringList strlist;
   QStringList vTags;
   LogTreeView::ItemList itemList;
   QString file, tag, rev, authorLocked, author, state, lines, kopt, commitid, mergepoint, filename, comment;
   QDateTime date;
   enum { Begin, StartFile, Tags, Admin, Revision,
	  Author, Branches, Comment, Finished } ParserState;
   m_fileRevision = revision;
   m_pCvsBuffer = pCvsBuffer;
    
   setCaption ("CVS Log - " + name + " " + revision);
   m_baseDir = BaseDir;

   int files = 0;
   ParserState = Begin;
   QString line;

   unsigned int len = (*m_pCvsBuffer).numLines();
   for ( unsigned int i = outputLineOffset;i<len;i++) {
      line = (*m_pCvsBuffer).textLine(i);

      switch (ParserState) {
	 case Begin:
	    if (line.startsWith("Working file: ")) {
	       file = line.mid(14);
	       ++files;
	       ParserState = StartFile;
	    }
	    break;
	 case StartFile:
	    if (line == "symbolic names:")
	       ParserState = Tags;
	    
	    /* We'll drop through here because the log output
	     * doesn't always have a symbolic names section.
	     * This is particularly true if we have a .cvsrc file
	     * with the following line: "log -N"
	     */
	    
	 case Admin:
	    if (line == "----------------------------") {
	       ParserState = Revision;
	    }
	    break;
	    
	 case Tags:
	    if (line[0] == '\t') {
	       strlist = QStringList::split (':', line);
	       QString rev = strlist[1].simplifyWhiteSpace();
	       QString tag = strlist[0].simplifyWhiteSpace();
	       QString branchpoint = "";
	       
	       if (rev.contains (".0.")) {
		  /* For a revision number such as 2.10.0.6, we want:
		   * branchpoint = "2.10"
		   * rev = "2.10.6"
		   */
		  branchpoint = QString(rev).replace (QRegExp ("\\.0\\.[0-9]+$"), "");
		  rev.replace (QRegExp ("\\.0\\."), ".");
	       } else if (rev.contains('.') == 2) {
		  //vendor tag
		  vTags.append(rev);
	       }
	       
	       TagInfo *taginfo = new TagInfo;
	       taginfo->rev = rev;
	       taginfo->tag = tag;
	       taginfo->branch = rev;
	       taginfo->branchpoint = branchpoint;
	       tags.append(taginfo);
	       
	    }
	    else ParserState = Admin;
	    break;
	    
	 case Revision:
	    strlist = QStringList::split (' ', line);
	    rev = strlist[1];
	    int pos;
	    if ((pos=rev.findRev("\t"))>-1) {//lock info
	       rev.truncate(pos);
	    }
	    if ((pos=line.find("locked by: "))>-1) {
	       authorLocked = line.mid(pos+11,line.length()-pos-12);
	    } else {
	       authorLocked = "";
	    }
	    ParserState = Author;
	    break;
	    
	 case Author: {
	    /* Extract the date, author and state subcomponents of a
	     * line that looks something like this:
	     * date: 2001/05/16 22:04:07;  author: joseh;  state: Exp;  lines: +11 -11 
	     */
	    strlist = QStringList::split (';', line);
	    QString tmpVal = (QStringList::split (": ", strlist[0]))[1];
	    date = QDateTime::fromString(tmpVal.replace(10,1,(const char*)"T"),Qt::ISODate );
	    author = (QStringList::split (": ", strlist[1]))[1];
	    state = (QStringList::split (": ", strlist[2]))[1];
	    lines="";kopt="";commitid="";mergepoint="";filename="";
	    if (strlist.count() > 3) {
	       QStringList::Iterator it = strlist.at(3);
	       while (it != strlist.end()) {
		  QString tmpType = (QStringList::split (": ", *it))[0].stripWhiteSpace();
		  tmpVal = (QStringList::split (": ", *it))[1];
		  if (tmpType.find("lines") == 0) lines = tmpVal;
		  else if (tmpType.find("kopt") == 0) kopt = tmpVal;
		  else if (tmpType.find("commitid") == 0) commitid = tmpVal;
		  else if (tmpType.find("mergepoint") == 0) mergepoint = tmpVal;
		  else if (tmpType.find("filename") == 0) filename = tmpVal;
		  ++it;
	       }
	    }
	    comment = "";
	    ParserState = Branches;
	    break;
	 }
	 case Branches:
	    if (qstrncmp(line, "branches:", 9) != 0) {
	       comment = line;
	       ParserState = Comment;
	    }
	    break;
	    
	 case Comment:
	    
	    if (line == "----------------------------") {
	       ParserState = Revision;
	    } else if (line == "=============================================================================") {
	       ParserState = Begin;
	    }
	    if (ParserState == Comment) {// still in message
	       comment += QString("\n") + QString(line);
	    } else {
	       // Create tagcomment
	       QString tagcomment, BranchTagList, RegularTagList, branch, branchName, releaseTag;
	       
	       branch = QString(rev).replace (QRegExp ("\\.[0-9]+$"), "");
	       
	       //check for vendor branch
	       bool found = FALSE;
	       QPtrListIterator<TagInfo> rtagIt(tags);
	       --rtagIt;// set to invalid position
	       QStringList::Iterator it0;
	       for ( it0 = vTags.begin(); it0 != vTags.end(); ++it0 ) {
		  if (branch == *it0) {
		     rtagIt.toLast();
		     while (rtagIt.current()) {
			if (rtagIt.current()->rev == rev) {
			   found = TRUE;
			   break;
			}
			--rtagIt;
		     }
		  }
		  if (found) break;
	       }
	       
	       // Build tagcomment and taglist:
	       // tagcomment contains Tags, Branchpoints and 'On Branch's
	       // taglist contains tags (without prefix) and Branchpoints
	       QPtrListIterator<TagInfo> it(tags);
	       for (; it.current(); ++it) {
		  if (rev == it.current()->rev)	{
		     if (it != rtagIt) {// regular tag
			tagcomment += "\nTag: " + it.current()->tag;
			RegularTagList += "\nTag: " + it.current()->tag;
		     } else {// release tag
			tagcomment += "\nRelease-Tag: " + it.current()->tag;
			RegularTagList += "\nRelease-Tag: " + it.current()->tag;
		     }
		  }
		  if (branch == it.current()->branch) {
		     branchName = it.current()->tag;
		  }
		  if (rev == it.current()->branchpoint) {
		     // branch origin tag
		     tagcomment += "\nBranch-Point: " + it.current()->tag;
		     BranchTagList += "\nBranch-Point: " + it.current()->tag;
		  } else if (branch == it.current()->rev) {
		     if (rtagIt.current()) {
			// vendor tag
			// Note that cvs reports the vendor tag at the end of the tag list
			tagcomment += "\nVendor-Tag: " + it.current()->tag;
			BranchTagList += "\nVendor-Tag: " + it.current()->tag;
		     } else {
			// branch tag
			tagcomment += "\nBranch-Tip: " + it.current()->tag;
			BranchTagList += "\nBranch-Tip: " + it.current()->tag;
		     }
		     
		     // Since branch tags are floating tags, will associate
		     // the tag with the revision at the tip of the branch.
		     // Note that cvs always reports the latest revision first.
		     it.current()->rev = rev;
		  }
	       }
	       
	       // remove leading '\n'
	       if (!tagcomment.isEmpty())
		  tagcomment.remove(0, 1);
	       if (!BranchTagList.isEmpty())
		  BranchTagList.remove(0, 1);
	       if (!RegularTagList.isEmpty())
		  RegularTagList.remove(0, 1);
	       if (branchName.isEmpty()) branchName = "MAIN";
	       
	       list->addRevision(file, rev, branchName, author, comment, date);
	       itemList.append(new LogTreeView::LogTreeItem(file, rev, authorLocked,
				     date.toString(LookAndFeel::g_dateTimeFormat),
				     author, state, lines, kopt, commitid, mergepoint,
				     filename, comment, branchName, tagcomment,
				     BranchTagList, RegularTagList)
			       );
	    }
	    if (ParserState == Begin) {
	       tree->addRevisionList(itemList);
	       tree->nextFile();
	       list->nextFile();
	       tags.clear();
	       vTags.clear();
	       itemList.clear();
	    }
	    
	 default :
	    break;
      }
   }

   tree->recomputeCellSizes();
   layout()->activate();
   if (files > 1) tree->scrollDown();
   //     qDebug("finishParsing: "+QDateTime::currentDateTime().toString("hh:mm:ss"));
};


/* 
 * protected slot
 */
void LogDialogImpl::annotateClicked() {
   QString cvsRoot = "annotate -r " + m_selectionA;
   QString fileA = m_fileA;
   QString file = masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
   callInteractive( m_topModule, m_baseDir, cvsRoot,
	 file, CVS_ANNOTATE_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 true);
}

void LogDialogImpl::keepSettingsToggled(bool state) {
   s_keepSettings = state;
}

void LogDialogImpl::showTagsToggled(bool state) {
   s_showTags = state;
}

void LogDialogImpl::showBranchesToggled(bool state) {
   s_showBranches = state;
}

void LogDialogImpl::displayFileRevision (LogTreeView::LogTreeItem * item) {
   runDisplayFileRevision(item->rev,item->file);
}

void LogDialogImpl::displayFileRevision (LogListView::LogListItem * item) {
   runDisplayFileRevision(item->rev,item->file);
}

void LogDialogImpl::runDisplayFileRevision( QString &rev, QString &fileName) {

   m_tmpDiffFileNameA = createTempFile("-" + rev + "-" + fileName);
   if (!m_tmpDiffFileNameA.isEmpty()) {
      QString tmpFile = fileName;
      QString cvsRoot = "-Q update -p -r " + rev + " " + masqWs(tmpFile.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS)) + " > " + m_tmpDiffFileNameA;
      QString file = "";
      callInteractive( m_topModule, m_baseDir, cvsRoot,
	    file, CVS_EDIT_REVISION_CMD,
	    ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	    true);
   }
}


/* 
 * protected slot
 */
void LogDialogImpl::diffClicked() {
   m_captionA = m_selectionA;
   m_captionB = m_selectionB;
   diff(m_selectionA,m_selectionB,m_fileA,m_fileB);
}

void LogDialogImpl::diff(QString revA, QString revB, QString fileA, QString fileB) {

   m_sandbox = QString::null;
   m_fileName = fileA;
   if (bUSEEXTERNALDIFFFORSIDEBYSIDE) {
      QString cvsRoot = CvsOptions::cmprStr() + " -Q update ";

      m_tmpDiffFileNameB = createTempFile("-" + revB + "-" + fileB);
      if (!m_tmpDiffFileNameB.isEmpty()) {
	 if (!revA.isEmpty()) {
	    m_tmpDiffFileNameA = createTempFile("-" + revA + "-" + fileA);
	    if (!m_tmpDiffFileNameA.isEmpty()) {
	       if (bKeywordSuppression) cvsRoot += "-kk ";
	       cvsRoot += "-p -r " + revA + " " + masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS)) + " > " + m_tmpDiffFileNameA
		  + " && cvs " + CvsOptions::cmprStr() + " -Q update ";
	       if (bKeywordSuppression) cvsRoot += "-kk ";
	       m_sandbox = m_baseDir + "/" + fileA;
	    } else return;
	 } else m_tmpDiffFileNameA = m_baseDir + "/" + fileA;

	 // and now, the names of used files are stored in *NameA and *NameB unless we make a
	 // diff between two revisions or between sandbox and one revision
	 // but we use m_sandbox for 3way diff...
      
	 cvsRoot += "-p -r " + revB + " " + masqWs(fileB.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS)) + " > " + m_tmpDiffFileNameB;
	 if (revA.isEmpty()) {//diff against local rep, exchange diff file order
	    QString tmp = m_tmpDiffFileNameA;
	    m_tmpDiffFileNameA = m_tmpDiffFileNameB;
	    m_tmpDiffFileNameB = tmp;
	 }
	 QString es = "";
	 callInteractive( m_topModule, m_baseDir, cvsRoot,
	       es, CVS_DIFF_EXT_CMD,
	       ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	       true);

      }
   }
   else {
      QString hlp;
      QString cvsRoot = CvsOptions::cmprStr() + " -Q diff -N ";
      if (bDiffIgnoreWhiteSpace) cvsRoot += "-b -B ";
      if (!revA.isEmpty()) if (bKeywordSuppression) cvsRoot += "-kk ";
      cvsRoot += "--side-by-side -t -W "+hlp.setNum(SIDEWIDTH);
      if (!revA.isEmpty()) cvsRoot += " -r " + revA;
      cvsRoot += " -r " + revB;
      QString file =  masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
      callInteractive( m_topModule, m_baseDir, cvsRoot,
	    file, CVS_DIFF_SBS_CMD,
	    ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	    true);
   }
}

void LogDialogImpl::diffAClicked() {
   m_captionA = m_selectionA;
   if (!m_fileRevision.isEmpty()) m_captionB = m_fileRevision + " " + tr("(working copy)");
   else m_captionB = tr("of working copy");
   diff(QString::null,m_selectionA,m_fileA,m_fileA);
}

void LogDialogImpl::diffBClicked() {
   m_captionA = m_selectionB;
   if (!m_fileRevision.isEmpty()) m_captionB = m_fileRevision + " " + tr("(working copy)");
   else m_captionB = tr("of working copy");
   diff(QString::null,m_selectionB,m_fileB,m_fileB);
}

void LogDialogImpl::diffConsoleClicked() {

   QString cvsRoot = CvsOptions::cmprStr() + " -f -Q diff -N ";
   if (bDiffIgnoreWhiteSpace) cvsRoot += "-b -B ";
   if (bKeywordSuppression) cvsRoot += "-kk ";
   if (!m_selectionA.isEmpty()) cvsRoot += " -r " + m_selectionA;
   if (!m_selectionB.isEmpty()) cvsRoot += " -r " + m_selectionB;
   QString fileA = m_fileA;
   QString file =  masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
   callInteractive( m_topModule, m_baseDir, cvsRoot,
	 file, CVS_DIFF_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 true);

}

void LogDialogImpl::diffConsoleAClicked() {
  if (m_selectionA.isEmpty()) return;
  diffConsoleSandBox(m_selectionA, m_fileA);
}

void LogDialogImpl::diffConsoleBClicked() {
  if (m_selectionB.isEmpty()) return;
  diffConsoleSandBox(m_selectionB, m_fileB);
}

void LogDialogImpl::diffConsoleSandBox(const QString &rev, const QString &f) {

   QString cvsRoot = CvsOptions::cmprStr() + " -f -Q diff -N ";
   if (bDiffIgnoreWhiteSpace) cvsRoot += "-b -B ";
   if (bKeywordSuppression) cvsRoot += "-kk ";
   cvsRoot += " -r " + rev;
   QString tmpFile = f;
   QString file =  masqWs(tmpFile.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
   callInteractive( m_topModule, m_baseDir, cvsRoot,
	 file, CVS_DIFF_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 true);
}

void LogDialogImpl::createPatchClicked() {

   bool ok;
   QString dir = m_baseDir + "/" + m_fileA + "-diff-" + m_selectionA + "-" + m_selectionB;
   QString patchFile = QInputDialog::getText(tr("Enter file name (default:") + " " + dir + ")",
	 "Enter a file name for the patch file", QLineEdit::Normal,
	 dir, &ok, this, "Patch file dialog");

   if (!ok) return;

   QFile f(patchFile);
   if (f.exists()) {
      if (QMessageBox::warning (this, tr("Warning"), 
		tr("The filename: ") + patchFile + tr(" already exists.\nOverwrite it?"), 
		QMessageBox::Yes , QMessageBox::No) != QMessageBox::Yes) return;
    
      f.remove();
   }

   QString hlp;
   QString cvsRoot = CvsOptions::cmprStr() + " -Q diff -N ";
   if (bDiffIgnoreWhiteSpace) cvsRoot += "-b -B ";
   if (bKeywordSuppression) cvsRoot += "-kk ";
   if (CvsOptions::g_bUnifiedDiffOutput) cvsRoot += "-u ";
   if (!m_selectionA.isEmpty()) cvsRoot += " -r " + m_selectionA;
   if (!m_selectionB.isEmpty()) cvsRoot += " -r " + m_selectionB;
   QString fileA = m_fileA;
   cvsRoot +=  " " + masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS)) + " > " + patchFile;
   QString file = "";
   callInteractive( m_topModule, m_baseDir, cvsRoot,
	 file, CVS_NOT_INTERACTIVE_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 true);

}

void LogDialogImpl::mergeClicked() {

   QString tmp;
 
   if( (! m_selectionA.isEmpty()) && (! m_selectionB.isEmpty()) ) {
      if( m_showMergeInfo) {
	 //create dialog  
	 VerifyDialog *dlg = new VerifyDialog(this);
	 dlg->setCaption(tr("Information"));
	 dlg->TextLabel->setText(tr("You are about to merge differences between revisions:\r\n  "+
				       m_selectionA + " and "+m_selectionB+
				       "\ninto:\r\n  "+
				       m_fileA + " " + m_fileRevision));
	 dlg->adjustSize();
	 if(!dlg->exec()) {
	    delete dlg;
	    return;
	 }
	 m_showMergeInfo = dlg->VerifyCheckBox->isChecked();
	 delete dlg;
      }

      QString command = "";
      if (!bRWPermission) command += "-r ";
      command += CvsOptions::cmprStr() + " update ";
      if (bKeywordSuppression) command += "-kk ";
      command += "-j " + m_selectionA;
      command += " -j " + m_selectionB;
      QString fileA = m_fileA;
      QString file = masqWs(fileA.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
      callInteractive( m_topModule, m_baseDir, command,
	    file, CVS_MERGE_REV_INTO_CMD,
	    ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	    true);
   }
}

/* 
 * protected slot
 */
void LogDialogImpl::revisionSelected(LogTreeView::LogTreeItem * item,bool rmb) {

   if (rmb) {
      m_treeB = item->tree;
      m_fileB = item->file;
      FileBBox->setText(item->file);
      RevBBox->setText (m_selectionB = item->rev);
      BranchBBox->setText(item->branch);
      AuthorDateBBox->setText(item->author+"  "+item->date);
      CommentBBox->setText(item->comment);
   } else {
      m_treeA = item->tree;
      m_fileA = item->file;
      FileABox->setText(item->file);
      RevABox->setText (m_selectionA = item->rev);
      BranchABox->setText(item->branch);
      AuthorDateABox->setText(item->author+"  "+item->date);
      CommentABox->setText(item->comment);
   }
   setButtonsAndSelectedPair( m_treeA, m_treeB );
}  

/* 
 * protected slot
 */
void LogDialogImpl::revisionSelected(LogListView::LogListItem * item,bool rmb) {

   if (rmb) {
      m_treeB = item->tree;
      m_fileB = item->file;
      FileBBox->setText(item->file);
      RevBBox->setText (m_selectionB = item->rev);
      BranchBBox->setText(item->branch);
      AuthorDateBBox->setText(item->author+"  "+item->date.toString(LookAndFeel::g_dateTimeFormat));
      CommentBBox->setText(item->comment);
   } else {
      m_treeA = item->tree;
      m_fileA = item->file;
      FileABox->setText(item->file);
      RevABox->setText (m_selectionA = item->rev);
      BranchABox->setText(item->branch);
      AuthorDateABox->setText(item->author+"  "+item->date.toString(LookAndFeel::g_dateTimeFormat));
      CommentABox->setText(item->comment);
   }
   setButtonsAndSelectedPair( m_treeA, m_treeB );
}

void LogDialogImpl::setButtonsAndSelectedPair(int treeA, int treeB) {

   /* Enable or disable the view diffs button depending
    * on whether there are two different revisions
    * currently selected.
    */

   bool sameTree = (treeA == treeB);
   bool sameRev = (m_selectionA == m_selectionB);
   bool bothRevsSet = (!m_selectionA.isNull() && !m_selectionB.isNull());

   buttonDiffs->setEnabled( bool ( bothRevsSet &&
				  ((sameTree && !sameRev) ||
					(!sameTree && bUSEEXTERNALDIFFFORSIDEBYSIDE)) ));

   m_ButtonDiffConsole->setEnabled( bool (sameTree &&
					  !m_selectionA.isNull() &&
					  !m_selectionB.isNull() &&
					  m_selectionA != m_selectionB));
  
   m_ButtonPatch->setEnabled( bool (sameTree &&
				    !m_selectionA.isNull() &&
				    !m_selectionB.isNull() &&
				    m_selectionA != m_selectionB));
  
   /* Enable or disable the annotation button depending
    * on whether there is a revision currently selected.
    */
   buttonAnnotate->setEnabled (bool (!m_selectionA.isNull()));
  
   /* Enable or disable the merge button depending
    * on whether there are two revisions currently selected.
    */
   buttonMerge->setEnabled (bool (sameTree &&
				  !m_selectionA.isNull() &&
				  !m_selectionB.isNull() &&
				  m_selectionA != m_selectionB));
  
   /* Enable or disable the rev->sandbox buttons depending
    * on whether there is a revision currently selected.
    */
   m_buttonDiffA->setEnabled(bool (!m_selectionA.isNull()) );
   m_buttonDiffB->setEnabled(bool (!m_selectionB.isNull()) );
   m_pDiffConsoleA->setEnabled(bool (!m_selectionA.isNull()) );
   m_pDiffConsoleB->setEnabled(bool (!m_selectionB.isNull()) );
   CommentABox->setEnabled(bool (!m_selectionA.isNull()) );
   CommentBBox->setEnabled(bool (!m_selectionB.isNull()) );
  
   tree->setSelectedPair(treeA, treeB, m_selectionA, m_selectionB);
   list->setSelectedPair(treeA, treeB, m_selectionA, m_selectionB);
}

void LogDialogImpl::setEnabled(bool state) {
   static bool diff = true;
   static bool diffConsole = true;
   static bool annotate = true;
   static bool merge = true;
   static bool patch = true;

   LogTabWidget->setEnabled(state);
   RevFrame->setEnabled(state);
   m_pWhatsThis->setEnabled(state);
   buttonDone->setEnabled(state);

   if (!state) {//preserve state
      diff = buttonDiffs->isEnabled();
      diffConsole = m_ButtonDiffConsole->isEnabled();
      annotate = buttonAnnotate->isEnabled();
      merge = buttonMerge->isEnabled();
      patch = m_ButtonPatch->isEnabled();
   } else {
      buttonDiffs->setEnabled(diff);
      m_ButtonDiffConsole->setEnabled(diffConsole);
      buttonAnnotate->setEnabled(annotate);
      buttonMerge->setEnabled(merge);
      m_ButtonPatch->setEnabled(patch);
   }
}

void LogDialogImpl::cvsCallStarted() {
   setEnabled(FALSE);
   QApplication::setOverrideCursor(Qt::waitCursor);
}

void LogDialogImpl::cvsCallFinished() {
   QApplication::restoreOverrideCursor();
   setEnabled(TRUE);
}

void LogDialogImpl::afterCall( int cmd, CvsBuffer * buf, bool failed) {
   cvsCallFinished();

   if (failed) return;
   switch( cmd) {
      case CVS_DIFF_CMD: {
	 break;
      }
      case CVS_DIFF_SBS_CMD: {
	 DiffDialogImpl *l = new DiffDialogImpl( *(m_pWhatsThis->iconSet()),
	       m_parent, "DiffDlg", LookAndFeel::g_nonModalF | WDestructiveClose);

	 l->parseCvsDiff(m_pCvsBuffer, m_fileName, m_captionA, m_captionB);
	 l->show();
	 break;
      }
      case CVS_DIFF_EXT_CMD: {
	 QString diffPrg = "\"" + ExtApps::g_diffProg.path + "\" ";
	 QStringList nameList;
	 QString tmp = m_tmpDiffFileNameA;
	 nameList.append(tmp.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
	 tmp = m_tmpDiffFileNameB;
	 nameList.append(tmp.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
	 if (m_sandbox.isEmpty()) nameList.append("");
	 else {
	    tmp = m_sandbox;
	    nameList.append(tmp.replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS));
	 }
	 QString options = ExtApps::g_diffProg.options;
	 if (masqedFilenamesForPlaceholders(options, nameList)) diffPrg += options;
	 else {// no options, we add filenames assuming the user want it so ;-)
	    diffPrg += masqWs(m_tmpDiffFileNameA.latin1()) + " ";
	    diffPrg += masqWs(m_tmpDiffFileNameB.latin1()) + " ";
	    if (!m_sandbox.isEmpty()) {
	       diffPrg += masqWs(m_sandbox.latin1());   
	    }
	 }
	 runExternal(diffPrg);
	 break;
      }
      case CVS_ANNOTATE_CMD: {
	 AnnotateDialogImpl *pAnnotateDialogImpl = new AnnotateDialogImpl( *(m_pWhatsThis->iconSet()),
	       m_parent, "AnnotateDlg",
	       LookAndFeel::g_nonModalF | WDestructiveClose);

	 pAnnotateDialogImpl->parseCvsAnnotate (m_fileA, m_pCvsBuffer, m_selectionA);
	 pAnnotateDialogImpl->show();
	 break;
      }
      case CVS_EDIT_REVISION_CMD: {
	 QFile tmpFile(m_tmpDiffFileNameA);
	 setPermission(tmpFile, READABLE);//don't think you could change the file
	 emit editFile(m_tmpDiffFileNameA);
	 break;
      }
      case CVS_MERGE_REV_INTO_CMD: {
	 if (m_workBench->validate(m_workDir)) {
	    m_workDir->parseCallResult( buf, cmd);
	    emit checkInProgress(true);
	    m_workDir->activateItem(false);
	    emit checkInProgress(false);
	 }
	 break;
      }
   }
}

void LogDialogImpl::enterWhatsThisMode()
{
   QWhatsThis::enterWhatsThisMode();
}



syntax highlighted by Code2HTML, v. 0.9.1