/****************************************************************************
**
** Copyright (C) 2003-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** 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 <qapplication.h>
#include <qtextcodec.h>
#include <qinputdialog.h>
#include <qfile.h>
#include <qcheckbox.h>
#include <qtextstream.h>
#include <qfiledialog.h>
#include <qlabel.h>
#include <qstringlist.h>
#include <qregexp.h> 
#include <qprocess.h>
#include <assert.h>

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

#include "directory.h"
#include "cvscontrol.h"
#include "PixmapTimer.h"
#include "colortab.h"
#include "cvslistview.h"
#include "noncvslistview.h"
#include "cvsignorelistview.h"
#include "cvsconfig.h"
#include "cvsconfig_old.h"
#include "login.h"
#include "LinCVSLog.h"
#include "TextEncoder.h"

//----------------------------------------------------------------------------

const bool CvsControl::OVERRIDE = TRUE;

//----------------------------------------------------------------------------

CvsControl::CvsControl( QString startUpDir)
   : CMainWindow(0, "CrossVC main window", WType_TopLevel | WDestructiveClose)
{
   conf = 0;
   KILLED = false;
   createTmpDir();
   
   sshAgentIsRunning = false;
   sshAgentStarted = false;
   
   m_pInteractiveCmdThread = NULL;
   m_pCvsBuffer = new CvsBuffer();
   
   readSettings();
   Debug::g_pLog = new CLinCVSLog();
   
   if (Debug::g_pLog) {
      QString msg = "\n";
	  msg += LC_APPNAME;
	  msg += " version: " + getVersion() + "\n";
#ifdef Q_WS_WIN
      msg += "for Windows\n";
#else
#ifdef Q_WS_MAC
      msg += "for Mac OS X\n";
#else
      msg += "for Unix\n";
#endif
#endif
      msg += "Compile Time: "+getCompileTime()+"\n";
      msg += "LogLevel: "+QString::number(Debug::g_logLevel)+"\n";
      Debug::g_pLog->log(Debug::LL_INFO, msg);
   }
   
   //we have set the application icon --> now we try to load icons from disk 
   loadPixmapsFromDisk(iconDir);
   
   globalListViewsEnabled = true;
   
   m_pFetchTagDir = NULL;
   m_pLastCvsCallDir = NULL;
   
   globalStopAction = false;
   m_chainedCommand = false;
   m_interactiveCmdThreadIsRunning = false;
   
   /* initialize CommandInterface */
   initCommandInterface(this);

   if (bStartedInDir) {
      m_tmpStartUpDir = startUpDir;
   }
   
   /* initialize Monitoring */
   initMonitoring();
   if (DirWatch::b_isActive) {
      //No better way here since globals can't handle qt messages
      connect (getDirConnector(), SIGNAL(eventReceived()),this,SLOT(dirConnectorDataNotify()));
   }
}

//----------------------------------------------------------------------------

CvsControl::~CvsControl()
{
   releaseMonitoring();

   //remove all tmp files from external diff
   QDir d(tmpDir);
   d.setFilter( QDir::Files | QDir::NoSymLinks );
    
   const QStringList list = d.entryList();
   unsigned int i;  
   for (i = 0; i < list.count(); i++) {
      if ( (WINVERSION && (list[i].find("fn") >= 0) ) || ( (!WINVERSION) && (list[i].find("tmp.") == 0)) ) {
	 QFile tmpFile(tmpDir + "/" + list[i]);
	 setPermission(tmpFile,READABLE | WRITEABLE);
	 tmpFile.remove();
      }
   }
   killSshAgent();

   delete Debug::g_pLog;
   Debug::g_pLog = 0;  //prevents logging after deleting log object
}

//----------------------------------------------------------------------------

void CvsControl::readSettings() {

   delete conf;
   conf = 0;   //paranoia

   QString tmpCvsRsh = getenv("CVS_RSH");
   QString tmpCvsPassPath = getenv("CVS_PASSFILE");
   QString tmpCvsWrappers = getenv("CVSWRAPPERS");
   if (tmpCvsPassPath.isEmpty()) tmpCvsPassPath = QDir::homeDirPath() + "/.cvspass";

   conf = new CvsConfig;

   // check for old lincvs config
   if (Misc::g_bVeryFirstStart) {
      // try to load old config --> creation of object reads old config
      CvsConfigOld oldConf;
   }

   Misc::g_bVeryFirstStart = false;

   expurgateProjectSettings();

   if (ExtApps::g_cvsRsh.path.isEmpty() && (!tmpCvsRsh.isEmpty())) {
      ExtApps::g_cvsRsh.path = tmpCvsRsh;   //use CVS_RSH
   }
   if (CVSPASSPATH.isEmpty() && (!tmpCvsPassPath.isEmpty())) {
      CVSPASSPATH = tmpCvsPassPath;
   }
   if (BINARYFILESPECLIST.isEmpty() && (!tmpCvsWrappers.isEmpty())) {
      //probably read file pointed to by CVSWRAPPERS, no docu available
      // 	BINARYFILESPECLIST = tmpCvsWrappers;
   }
}

//----------------------------------------------------------------------------

void CvsControl::writeSettings() {

   expurgateProjectSettings();

   writeSetup();
   writeCfg();
   delete conf;
   conf = 0;
}

//----------------------------------------------------------------------------

void CvsControl::expurgateProjectSettings() {
   unsigned int i;
   unsigned int j;
   QString workdir;
   QStringList projectNameList;
   QStringList topLevelProjectNameList;

   //remove unreferenced project settings
   projectSettings->getTopLevelProjects(topLevelProjectNameList);
   projectSettings->getProjects(projectNameList);
   QStringList tmpProjectNameList = projectNameList;
   QString joined;

   for (i=0; i<topLevelProjectNameList.count(); ++i) {//remove unreferenced entries from subProject list
      QStringList subProjects;
      if (projectSettings->getSubProjects(topLevelProjectNameList[i],subProjects)) {
	 for (j=0; j<subProjects.count(); ++j) {
	    QStringList::Iterator it = projectNameList.find(subProjects[j]);
	    if (it == projectNameList.end()) {
	       projectSettings->removeSubProject(topLevelProjectNameList[i],subProjects[j]);
	       if (Debug::g_pLog) {
		  Debug::g_pLog->log(Debug::LL_INFO,"removing unreferenced sub-project from sub-project list: "+subProjects[j]);
	       }
	    } else {
	       joined += "^e"+subProjects[j].replace(QRegExp("\\^"),"^0");
	    }
	 }
      }
      tmpProjectNameList.remove(tmpProjectNameList.find(topLevelProjectNameList[i]));
   }

   QStringList allSubProjectsList = QStringList::split("^e",joined);
   QStringList::Iterator it;
   for (it = allSubProjectsList.begin(); it != allSubProjectsList.end(); ++it) {
      (*it).replace(QRegExp("\\^0"),"^");
   }

   for (i=0; i<tmpProjectNameList.count(); ++i) {//remove unreferenced subProjects

      QStringList::Iterator it = allSubProjectsList.find(tmpProjectNameList[i]);
      if (it == allSubProjectsList.end()) {
	 projectSettings->removeProject(tmpProjectNameList[i]);
	 if (Debug::g_pLog) Debug::g_pLog->log(Debug::LL_INFO,"removing unreferenced sub-project: "+tmpProjectNameList[i]);
      }
   }
}

//----------------------------------------------------------------------------

void CvsControl::setSettings() {
   if (!DirWatch::b_useDirWatch) releaseMonitoring();
   Debug::g_pLog->log(Debug::LL_THE_OLD_MAN_AND_THE_SEA, "LogLevel set to: "+QString::number(Debug::g_logLevel));
}

//----------------------------------------------------------------------------

void CvsControl::startInSingleDir() {//startup option, only called by lincvs.cpp
   addProject(m_tmpStartUpDir,true);
}

//----------------------------------------------------------------------------

void CvsControl::initialDirScanning()//startup option, only called by lincvs.cpp
{
   QStringList projectNameList;
   projectSettings->getTopLevelProjects(projectNameList);

   QStringList removeList;
   unsigned int i;

   for(i = 0; i < projectNameList.count(); i++) {
      QString fullName;
      projectSettings->get(projectNameList[i],WORKDIR,fullName);
      if (addProject(fullName)) {

	 applyProjectSettings(projectNameList[i]);

      } else {//globalStopAction!
	 
	 setStatusText( tr("Scanning aborted") );
	 if (showYesNoQuestion( tr("Interrupted"),
		     tr("Remove project from workbench:")+"\n"+
		     fullName+"?" ) ) {

	    removeList.append(projectNameList[i]);
	 }
      }
   }

   if (!removeList.isEmpty()) {
      for(i = 0; i < removeList.count(); ++i) {
	 QString fullName;
	 projectSettings->get(removeList[i],WORKDIR,fullName);
	 DirBase * dir = m_pWorkBenchTree->find(fullName);
	 if (dir) delete dir;
	 projectSettings->removeProject(removeList[i]);
      }
   }

   globalStopAction = false;

   initDone();
}

//----------------------------------------------------------------------------

bool CvsControl::addNewProject(const QString fileName, bool expand) {

   QStringList projectNameList;
   projectSettings->getProjects(projectNameList);
   unsigned int i;
   for(i=0; i < projectNameList.count(); i++) {
      QString fullName;
      projectSettings->get(projectNameList[i],WORKDIR,fullName);

      if ( fileName.mid(fileName.findRev("/")+1) == projectNameList[i]) {
	 showWarning(tr("Warning")+", "+tr("adding aborted"),
	       tr("This project is already in the workbench:\n")
	       +projectNameList[i]
	       +"\nat: "+fullName);
	 setStatusText( tr("Adding aborted"), 4000 );
	 return FALSE;
      }
   }

   setStatusText("");

   Directory * item = Directory::createInstance (fileName);

   projectSettings->set(item->shortName(),WORKDIR,item->fullName());

   if(expand) {
      item->setOpen(true);
   }
  
   if (globalStopAction) {
      setStatusText(tr("Adding aborted"));
      globalStopAction = false;
      return FALSE;
   } else {
      m_pWorkBenchTree->setSelected(item,TRUE);
      slot_checkStatusOfTree(item);
      return TRUE;
   }
}

//----------------------------------------------------------------------------

bool CvsControl::addProject(const char * fileName, bool expand)
{
   bool addNewProject = false;

   QString fn = fileName;
   if(fn.isEmpty()){//popup dialog
      addNewProject = true;
      expand = true;

      /* Using the Qt standard file dialog preserves the look and feel
       * of the application across platforms.
       */
      QString defaultDir = QString::null;
      DirBase * dir;
      if ( (dir = getSelectedDir(false)) ) {
	 defaultDir = dir->fullName();
	 defaultDir = defaultDir.left(defaultDir.findRev("/"));
      }

      blockGUI(false);
      fn = QFileDialog::getExistingDirectory (defaultDir, this, NULL,
					      tr("Add directory to workbench"), true);
      blockGUI(true);

      // Remove trailing "/" from the path.
      fn.replace (QRegExp ("/+$"), "");
#ifdef Q_WS_WIN
      fn.replace (QRegExp ("\\\\"), "/");
#endif
   }
   
   if ( !fn.isEmpty() ) {

      if (addNewProject) {
	 QStringList projectNameList;
	 projectSettings->getProjects(projectNameList);
	 unsigned int i;
	 for(i=0; i < projectNameList.count(); i++) {
	    QString fullName;
	    projectSettings->get(projectNameList[i],WORKDIR,fullName);

	    if ( fn.mid(fn.findRev("/")+1) == projectNameList[i]) {
	       showWarning(tr("Warning")+", "+tr("adding aborted"),
		     tr("This project is already in workbench:\n")
		     +projectNameList[i]
		     +"\nat: "+fullName);
	       setStatusText( tr("Adding aborted"), 4000 );
	       return FALSE;
	    }
	 }
      }

      setStatusText("");

      Directory * item = Directory::createInstance (fn);

      if(addNewProject){
	 projectSettings->set(item->shortName(),WORKDIR,item->fullName());
      }

      if(expand) {
	 item->setOpen(true);
      }

      if (globalStopAction) {
	 setStatusText(tr("Adding aborted"));
	 globalStopAction = false;
	 return FALSE;
      } else {
	 if (!item->isDisabled()) {
	    m_pWorkBenchTree->setSelected(item,TRUE);
	    slot_checkStatusOfTree(item);
	 }
	 return TRUE;
      }
   }
   else setStatusText(tr("Adding aborted"));
   return FALSE;
}

//----------------------------------------------------------------------------

void CvsControl::slot_addCheckedOutProject(QString path,QString name,int sshAccess) {

   Directory * myChild = (Directory*)(m_pWorkBenchTree->firstChild());
   while( myChild) {
      if (myChild->shortName() == name) {
	 showWarning( tr("Warning")+", "+tr("can't add to workbench"),
	       tr("A project with equal name is already in workbench:")+"\n"+
	       myChild->shortName());
	 return;
      }
      myChild = (Directory*)(myChild->nextSibling());
   }
   
   bool res = showYesNoQuestion( tr("Checkout Info"),
	 tr("Add this project to workbench:")+"\n"+
	 name+"?" );
   if (res) {
      
      Directory * item = Directory::createInstance (path+"/"+name);
      assert(item);
      item->setAllToUpToDate();
      
      //write project settings
      QString topProjectDir = item->shortName();
      projectSettings->set(item->shortName(),WORKDIR,item->fullName());
      projectSettings->set(topProjectDir,SSHACCESS,sshAccess);
      
      //select the dir
      m_pWorkBenchTree->setSelected(item,TRUE);
      
      //open the dir
      item->setOpen(true);
      
      if (globalStopAction) globalStopAction = FALSE;
   }
}

//----------------------------------------------------------------------------

void CvsControl::slot_requestDirectorySuicide(const QVariant &name) {
   QString dirName = name.toString();
   m_DirsToKill.append(dirName);
   CExtTimer::singleShot(0,this,SLOT(slot_execDirectorySuicide()) );
}

//----------------------------------------------------------------------------

void CvsControl::slot_execDirectorySuicide() {
   if (!m_DirsToKill.isEmpty()) {
      QString name = m_DirsToKill.first();
      m_DirsToKill.pop_front();
      Directory * dir = static_cast<Directory *>(m_pWorkBenchTree->find(name));
      if (dir) {
	 Directory * parentDir = dir->parent();
	 if (parentDir) parentDir->removeChild(dir);
      }
   }
}

//----------------------------------------------------------------------------

void CvsControl::slot_removeProjectSlot(DirBase * dir)
{
   if (!dir) {
      dir = m_pWorkBenchTree->selectedItem();
   }
   if (!dir) {
      showWarning(tr("Warning"), tr("There is no directory selected") );
      return;
   }

   if (dir != dir->topDir()) {
      showInfo( tr("Information"), 
	    tr("This Directory is not a top level dir.") );
      return;
   }

   QString topModule = dir->shortName();
   projectSettings->removeProject(topModule);
   delete dir;
   Directory * nextDir = static_cast <Directory *> (m_pWorkBenchTree->selectCurrentItem ());
   if (nextDir) {
      nextDir->activateItem(FALSE);
   }
}

/*---------------------------------------------------------------------------*/
/*!
  \fn			void CvsControl::disableProjectSlot(void)
  \brief		Disable a dir at top level form workbench.
  Dir is still listed but not further updated and scanned until enabled
  again. Entry is inserted in file list in cfg file section DISABLED.


  <BR><HR>*/
/*---------------------------------------------------------------------------*/
void CvsControl::disableProjectSlot(void) //only called from lincvs.cpp
{
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);
   QString topCvsModule = dir->topControlledDir()->relativeName();
   QString dirname = dir->fullName();
   QStringList disabled;
   projectSettings->get(topCvsModule,DISABLED,disabled);
   disabled.append(dirname);
   projectSettings->set(topCvsModule,DISABLED,disabled);
   static_cast<Directory *>(dir->topControlledDir())->updateDisabledList(disabled);

   Directory *hlp = dir->parent();
   if (hlp) {
      hlp->reloadChild(dirname);
      dir = static_cast<Directory *>(m_pWorkBenchTree->find( dirname));
      m_pWorkBenchTree->setSelected( dir, TRUE );
      slot_checkStatusOfTree(dir);
   }
   else {
      delete dir;
      addProject(dirname.latin1());
      applyProjectSettings(topCvsModule);
      dir = static_cast<Directory *>(m_pWorkBenchTree->find( dirname));
      m_pWorkBenchTree->setSelected( dir, TRUE );
   }
   if (!blocked) blockGUI(false);
}


/*---------------------------------------------------------------------------*/
/*!
  \fn			void CvsControl::enableProjectSlot()
  \brief		Reread project after reenable.

  <BR><HR>*/
/*---------------------------------------------------------------------------*/
void CvsControl::enableProjectSlot() //only called from lincvs.cpp
{
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true); // takes some time on huge dirs
   QString topCvsModule = dir->topControlledDir()->relativeName();
   QString dirname = dir->fullName();
   QStringList disabled;
   projectSettings->get(topCvsModule,DISABLED,disabled);
   disabled.remove(dirname);
   projectSettings->set(topCvsModule,DISABLED,disabled);
   static_cast<Directory *>(dir->topControlledDir())->updateDisabledList(disabled);

   Directory * hlp = dir->parent();
   if (hlp) {
      dir = static_cast<Directory *>(hlp->reloadChild(dirname));
      if (dir == dir->topControlledDir()) applyProjectSettings(topCvsModule);
      if (!dir->isDisabled()) {
	 slot_checkStatusOfTree(dir);
      }
   }
   else {
      delete dir;
      addProject(dirname.latin1());// calls slot_checkStatusOfTree()
      dir = static_cast<Directory *>(m_pWorkBenchTree->find( dirname));
      applyProjectSettings(topCvsModule);
   }
   m_pWorkBenchTree->setSelected(dir,TRUE);
   dir->activateItem(FALSE);
   if (!blocked) blockGUI(false);
}

/*---------------------------------------------------------------------------*/

void CvsControl::disableOthers()
{
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
  
   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);

   QStringList disabled;
   QStringList projectList;
   Directory * tmpDir = dir;
   Directory * parent = NULL;
   while ( (parent = tmpDir->parent() ) ) {
      Directory * child = parent->firstChild();
      while (child) {
	 if (child != tmpDir) {
	    disabled.append(child->fullName());
	 }
	 child = child->nextSibling();
      }
      if (parent == parent->topControlledDir()) {
	 QString topCvsModule = parent->relativeName();
	 projectList.append(topCvsModule);
	 projectSettings->set(topCvsModule,DISABLED,disabled);
	 disabled.clear();
      }
      tmpDir = parent;
   }

   QString openDir = dir->fullName();
   QString moduleName = dir->topDir()->shortName();
   QString topDirName = dir->topDir()->fullName();
   delete dir->topDir();
   performRereadProject(moduleName,topDirName);
   QStringList::Iterator iter;
   for (iter = projectList.begin(); iter != projectList.end(); iter++) {
      applyProjectSettings(*iter);
   }
   openTreeAndActivate(topDirName,openDir);

   if (!blocked) blockGUI(false);
}

/*---------------------------------------------------------------------------*/

void CvsControl::enableTree() {

   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
  
   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);

   QString dirName = dir->fullName();
   QString projectName = dir->topDir()->shortName();

   //remove from toplevel project
   QStringList disabledList;
   projectSettings->get(projectName,DISABLED,disabledList);
   QStringList::Iterator it = disabledList.begin();
   while (it != disabledList.end()) {
      if ((*it).startsWith(dirName)) {
	 it = disabledList.remove(it);
      } else {
	 ++it;
      }
   }
   projectSettings->set(projectName,DISABLED,disabledList);

   //remove from subprojects
   QStringList emptyList;
   QStringList subProjectNameList;
   projectSettings->getSubProjects(projectName,subProjectNameList);
   unsigned int i;
   for(i=0; i < subProjectNameList.count(); i++) {
      QString fullName;
      projectSettings->get(subProjectNameList[i],WORKDIR,fullName);
      if ( fullName.startsWith(dirName)) {
	 projectSettings->set(subProjectNameList[i],DISABLED,emptyList);
      } else if (dirName.startsWith(fullName)) {
	 projectSettings->get(subProjectNameList[i],DISABLED,disabledList);
	 QStringList::Iterator it = disabledList.begin();
	 while (it != disabledList.end()) {
	    if ((*it).startsWith(dirName)) {
	       it = disabledList.remove(it);
	    } else {
	       ++it;
	    }
	 }
	 projectSettings->set(subProjectNameList[i],DISABLED,disabledList);
      }
   }

   //make sure disabled list is set
   QString topCvsModule = dir->topControlledDir()->relativeName();
   projectSettings->get(topCvsModule,DISABLED,disabledList);
   static_cast<Directory *>(dir->topControlledDir())->updateDisabledList(disabledList);

   //reread tree
   rereadProjectOfDir(dir);

   if (!blocked) blockGUI(false);
}

/*---------------------------------------------------------------------------*/

void CvsControl::performRereadProject( QString moduleName, QString fullName) {
   setStatusText( "");
   addProject(fullName);
   projectSettings->set(moduleName,WORKDIR,fullName);
   setStatusText( tr("Ready"));
}

//----------------------------------------------------------------------------

void CvsControl::slot_rereadProject(DirBase * dir) {

   if (!dir) {
      if (!(dir = getSelectedDir())) {
	 return;
      }
   }
   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);
   Directory * tDir = static_cast<Directory *>(dir);
   assert(tDir);
   rereadProjectOfDir(tDir);
   if (!blocked) blockGUI(false);
}

//----------------------------------------------------------------------------

// reopen tree, returns deepest opened dir in hierarchy or null, if not found in workbench
// it returns also null if dirToOpen is removed behind the back
Directory * CvsControl::openTreeAt(const QString &topDirName,const QString &dirToOpen) {

   DirBase * dir = NULL;
   if (ONTHEFLYSCANNING && (topDirName != dirToOpen)) {//can't find dir since it's not scanned jet!
      int from = topDirName.length();
      while (from >= 0) {
	 dir = m_pWorkBenchTree->find(dirToOpen.left( (unsigned int)from));
	 if (dir) {
	    dir->setOpen(TRUE);//This will scan!
	 }
	 from = dirToOpen.find('/',from+1);
      }
   }
   dir = m_pWorkBenchTree->find( dirToOpen );
   if (dir) {
      QString curDir = dir->fullName();
      QString removed;
      if (ONTHEFLYSCANNING) dir->setOpen(TRUE, removed);//This will scan!
      else dir->setAllOpen();
      if (removed == curDir) dir = 0;
   }
   slot_checkStatusOfTree(static_cast<Directory *>(m_pWorkBenchTree->find(topDirName)));
   return static_cast<Directory *>(dir);
}

//----------------------------------------------------------------------------

void CvsControl::slot_openTreeAndActivate(DirBase * dirToOpen,const QString name) {
   DirBase * dir;
   if ( !(dir = openTreeAndActivate(dirToOpen->topDir()->fullName(),name)) ) {
      dir = m_pWorkBenchTree->findBestMatch(name);
      if (dir) {
	 m_pWorkBenchTree->setSelected(dir,true);
	 dir->activateItem(false);
      }
   }
}

//----------------------------------------------------------------------------

Directory * CvsControl::openTreeAndActivate( const QString &topDirName, QString dirToOpenAfterRescan) {
   // reopen tree
   int pos = dirToOpenAfterRescan.findRev("/");
   if (pos > (int)topDirName.length()) {
      openTreeAt(topDirName,dirToOpenAfterRescan.left(pos) );
   }
   //activate dir
   Directory * dir = NULL;
   dir = static_cast<Directory *>(m_pWorkBenchTree->find( dirToOpenAfterRescan ));
   if( dir )  {
      m_pWorkBenchTree->setSelected( dir, TRUE );
      dir->activateItem(FALSE);
      return dir;
   }
   return NULL;
}

//----------------------------------------------------------------------------

//warning: param 'dir' will be deleted/invalidated by this method!!!
void CvsControl::slot_selectSubDir(DirBase ** pdir, QString subDir) {
   *pdir = rereadProjectOfDir( static_cast<Directory *>(*pdir));//selects the given dir and opens from top to parent if available
   if(*pdir) {
      // make directory active and open so that content of directory is shown
      (*pdir)->setOpen(TRUE);
      DirBase * prevDir = *pdir;
      *pdir = (*pdir)->searchDirOfPath( subDir );
      if (*pdir) {
	 m_pWorkBenchTree->setSelected( *pdir, TRUE );
	 (*pdir)->activateItem(FALSE);
      } else {//adding of dir failed
	 *pdir = prevDir;
	 (*pdir)->activateItem(FALSE);
	 QString caption = tr("CrossVC - Error");
	 QString msg = tr("Adding of directory %1 failed!\nMaybe permission denied.");
	 showWarning( caption, msg.arg(subDir) );
      }
   }
}

//----------------------------------------------------------------------------

void CvsControl::applyProjectSettings(QString projectName) {
   if (projectName.isEmpty()) {//apply settings of all existing projects
      QStringList projectNameList;
      projectSettings->getTopLevelProjects(projectNameList);
      unsigned int i;
      for(i = 0; i < projectNameList.count(); ++i) {
	 projectName = projectNameList[i];
	 QStringList subProjectNameList;
	 projectSettings->getSubProjects(projectName,subProjectNameList);

	 Directory * dir;
	 QString workdir;
	 int autoupdate;
	 bool autoUpdateInSubProject = FALSE;

	 unsigned int j;
	 for (j = 0; j < subProjectNameList.count(); ++j) {
	    autoupdate = AutoUpdate::OFF;
	    projectSettings->get(subProjectNameList[j],WORKDIR,workdir);
	    projectSettings->get(subProjectNameList[j],PAUTOUPDATE,autoupdate);
	    dir = (Directory *)m_pWorkBenchTree->find(workdir);
	    if (dir) {
	       bool state = AUTOUPDATE && (autoupdate != AutoUpdate::OFF);
	       autoUpdateInSubProject |= state;
	       dir->setAutoUpdate(state);
	    }
	 }
	 autoupdate = AutoUpdate::OFF;
	 projectSettings->get(projectName,PAUTOUPDATE,autoupdate);
	 projectSettings->get(projectName,WORKDIR,workdir);
	 dir = (Directory *)m_pWorkBenchTree->find(workdir);
	 if (dir) {
	    dir->setAutoUpdate(AUTOUPDATE && ( (autoUpdateInSubProject) || (autoupdate != AutoUpdate::OFF) ) );
	 }
      }
   } else {//apply only settings of 'projectName'
      QStringList subProjectNameList;
      projectSettings->getSubProjects(projectName,subProjectNameList);

      Directory * dir;
      QString workdir;
      int autoupdate;
      bool autoUpdateInSubProject = FALSE;

      unsigned int j;
      for (j = 0; j < subProjectNameList.count(); ++j) {
	 autoupdate = AutoUpdate::OFF;
	 projectSettings->get(subProjectNameList[j],WORKDIR,workdir);
	 projectSettings->get(subProjectNameList[j],PAUTOUPDATE,autoupdate);
	 dir = (Directory *)m_pWorkBenchTree->find(workdir);
	 if (dir) {
	    bool state = AUTOUPDATE && (autoupdate != AutoUpdate::OFF);
	    autoUpdateInSubProject |= state;
	    dir->setAutoUpdate(state);
	 }
      }
      autoupdate = AutoUpdate::OFF;
      projectSettings->get(projectName,PAUTOUPDATE,autoupdate);
      projectSettings->get(projectName,WORKDIR,workdir);
      dir = (Directory *)m_pWorkBenchTree->find(workdir);
      if (dir) {
	 dir->setAutoUpdate(AUTOUPDATE && ( (autoUpdateInSubProject) || (autoupdate != AutoUpdate::OFF) ) );
      }
   }
}

//----------------------------------------------------------------------------

void CvsControl::stopTimers(int type /* = TALL */) {
   if (type == TALL || type == TSTATUS) {
      m_timer.stop();
   }
   if (type == TALL || type == TUPDATE) {
      m_updateTimer.stop();
   }
}

//----------------------------------------------------------------------------

void CvsControl::startTimers(int type /* = TALL */) {
   if (type == TALL || type == TSTATUS) {
      if (Polling::checkStatusLevel>Polling::NONE && (!DirWatch::b_isActive) ) {
	 if (!m_timer.isActive()) {
	    m_timer.start(CHECKSTATUSINTERVALL);
	 }
      }
   }
   if (type == TALL || type == TUPDATE) {
      if (!m_updateTimer.isActive() && AUTOUPDATE) {
	 m_updateTimer.start(AUTOUPDATEINTERVALL*1000*60);
      }
   }
}

//----------------------------------------------------------------------------

/**
 * Use this Method to check project state and to start the state timer
 * It will stop and restart the timer 
 * appropriate according to the Settings
 */
void CvsControl::slot_checkStatus(bool forceRefresh /*=false*/) {

   checkInProgress(TRUE);

   stopTimers(TSTATUS);
   bool doBlock = !isGuiBlocked();
   if (doBlock) blockGUI(true,BLOCKNOCONTROLS | BLOCKNOCURSOR);
   DirBase * dir = m_pWorkBenchTree->selectedItem();
   if ( dir && dir->isSelected() ) {

      if ( dir->isModified() || forceRefresh) {
	 dir->activateItem(TRUE);
      } else {
	 
	 CFileListView * lv = static_cast<CvsListView*>(m_pFileViews->page(0));
	 QListViewItem  *myChild = lv->firstChild();
	 while(myChild) {
	    if(lv->itemRect(myChild).isValid()) {
	       dir->checkAndShowStatus(static_cast<FileListViewItem*>(myChild));   
	    }
	    myChild = myChild->nextSibling();
	 }
      }
      if ( (Polling::checkStatusLevel >= Polling::ALLDIRS) || forceRefresh ) {
	 dir =  dir->topDir();
	 dir->recCheckForModifications( (Polling::checkStatusLevel >= Polling::ALLFILES) || forceRefresh );
      }
   }

   if (Polling::checkStatusLevel >= Polling::ALLPROJECTS) {
      QListViewItem * myChild = m_pWorkBenchTree->firstChild();
      while (myChild) {
	 if (myChild != dir) {
	    static_cast<Directory *>(myChild)->recCheckForModifications(Polling::checkStatusLevel >= Polling::ALLFILES);
	    if (globalStopAction) break;
	 }
	 myChild = myChild->nextSibling();
      }
   }
   globalStopAction = false;

   checkInProgress(FALSE);

   startTimers(TSTATUS);
   m_timer.pause();
   if (doBlock) blockGUI(false,BLOCKNOCONTROLS | BLOCKNOCURSOR);
  
}

//----------------------------------------------------------------------------

void CvsControl::slot_checkStatusOfTree(DirBase * dir) {

   checkInProgress(TRUE);

   if ( dir && dir->isSelected() ) {

      if ( dir->isModified() ) {
	 dir->activateItem(TRUE);
      } else {

	 CFileListView *lv = static_cast<CvsListView*>(m_pFileViews->page(0));
	 QListViewItem *myChild = lv->firstChild();
	 while(myChild) {
	    if(lv->itemRect(myChild).isValid()) {
	       dir->checkAndShowStatus(static_cast<FileListViewItem*>(myChild));   
	    }
	    myChild = myChild->nextSibling();
	 }
      }
   }

   dir->recCheckForModifications(TRUE);

   checkInProgress(FALSE);
}

//----------------------------------------------------------------------------

void CvsControl::dirConnectorDataNotify() {
   DirBase * dir = m_pWorkBenchTree->selectedItem();
   //if(!dir) return; <-- not needed because we want to check dirs even if we have no dirs selected

   DirBase * tmpDir = NULL;
   QString fileName;
   bool deleted;
   blockGUI(true,BLOCKNOCONTROLS | BLOCKNOCURSOR);
   checkInProgress(TRUE);
   while ( (tmpDir = checkMonitoredEvent(fileName,deleted))) {
      //     qDebug("Notify Dir: "+tmpDir->fullName()+", file: "+fileName+", deleted: "+QString(deleted ? "true" : "false"));
      //tmpDir != NULL --> call only dir->activateItem if dir != NULL too
      if (dir == tmpDir) {
	 dir->activateItem(true);
      } else {
	 tmpDir->checkAndShowStatus(NULL,true,DirBase::Controled|DirBase::NonControled|DirBase::Ignored,true);
      }
   }
   checkInProgress(FALSE);
   blockGUI(false,BLOCKNOCONTROLS | BLOCKNOCURSOR);
}

//----------------------------------------------------------------------------

void CvsControl::test() { /* -->please keep test() method for testing,
			     slot is enabled if .lincvsrc has <DEBUG = true> entrie<-- */

   recValidateCvsFilesStatus(false,true);
}

void CvsControl::slot_onlineHelp() {
   openUrlWithRemoteBrowser(tr("http://cvsbook.red-bean.com/cvsbook.html"));
}

void CvsControl::slot_debug() {

   QString debugLogFile;
   QFileDialog* fd = new QFileDialog(QDir::currentDirPath(), 
				     tr("Debug Log (*.log)"), 
				     this, "file dialog", TRUE );
   fd->setCaption(LC_APPNAME);
   fd->setMode( QFileDialog::AnyFile );
   fd->setSelection("debug.log");
   if ( fd->exec() == QDialog::Accepted) {
      debugLogFile = fd->selectedFile();
      if ( debugLogFile.isEmpty()) return;
   } else {
      return;
   }

   QFile file(debugLogFile);
   if (file.exists()) {
      if (!showYesNoQuestion( tr("Warning"), 
		  tr("The filename:") + " "
		  + debugLogFile + " "
		  + tr("already exists.")
		  + "\n" 
		  + tr("Overwrite it?")) )
	 return;
    
      file.remove();
   }

   if (file.open(IO_WriteOnly)) {
      QTextStream s(&file);                        // serialize using f
      s << "----------start debug log------------\n";
      s << "Date: "+QDateTime::currentDateTime().toString(Qt::ISODate)+"\n";
      s << "CrossVC version: "+getVersion()+"\n";
#ifdef Q_WS_WIN
      s << "for Windows\n";
#else
#ifdef Q_WS_MAC
      s << "for Mac OS X\n";
#else
      s << "for Unix\n";
#endif
#endif
      s << "Compile Time: "+getCompileTime()+"\n";
      fillCmdDebugStream(s);
      s << "----------end debug log------------";
      file.close();
   } else {
      showWarning( tr("Error:"),
	    tr("No write access to: ") + debugLogFile);
   }
}

//----------------------------------------------------------------------------

void CvsControl::fillCmdDebugStream(QTextStream &s) {

   s << m_debugCvsCallString;
   s << "outputLineOffset: "+QString::number(outputLineOffset)+"\n";
   s << "CvsBuffer: ->"+m_pCvsBuffer->readAll()+"<-\n";
   s << "==================================================================\n";
   s << "CvsBuffer single line mode: \n";
   unsigned int len = (*m_pCvsBuffer).numLines();
   for ( unsigned int i = outputLineOffset;i<len;i++) {
      s << "->"+(*m_pCvsBuffer).textLine(i)+"<-\n";
   }
}

//----------------------------------------------------------------------------

// use this if visible cvs output doesn't match CVS/Repository i.e. merge with -j xxx -j xxx
void CvsControl::recValidateCvsFilesStatus(bool forceCacheReset /*= FALSE */, bool forceEntriesReset /*= FALSE */) {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
   // iter down all dirs, check state of folders and set entries
   qApp->setOverrideCursor(waitCursor); // takes some time on huge dirs
   dir->validateControlledFilesStatus(TRUE,forceCacheReset,forceEntriesReset);
   qApp->restoreOverrideCursor();
}

//----------------------------------------------------------------------------

void CvsControl::updateProjectTagList() {

   if (!(m_pFetchTagDir = getSelectedDir())) {
      return;
   }
   DirBase * topDir = m_pFetchTagDir->topControlledDir();
   QString dirName = topDir->fullName();
   QString cvsRoot = "status -v";
   QString files = "";
   QString topModule = topDir->relativeName();
   callInteractiveCmd( topModule, dirName, cvsRoot,
	 files, CVS_GET_TAG_INFO_PROJECT_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 NOROOT, this);

}

//----------------------------------------------------------------------------

void CvsControl::readCurrentTagList() {

   if (!(m_pFetchTagDir = getSelectedDir())) {
      return;
   }
   m_currentTagList.clear();
   QString cvsRoot = "status -v";
   QString files;
   QStringList fileList = m_pFetchTagDir->getSelectedFiles();
   masqQuoteMarks(&fileList);
   if (!fileList.isEmpty()) files = masqWs(fileList.join("\" \""));
   else cvsRoot += " -l";
   
   QString dirName = m_pFetchTagDir->fullName();
   QString topModule = m_pFetchTagDir->topControlledDir()->relativeName();
   callInteractiveCmd( topModule, dirName, cvsRoot,
	 files, CVS_GET_TAG_INFO_FILES_CMD,
	 ExtApps::g_cvsRsh.path,  //additional options of cvsRsh not supported yet
	 NOROOT, this);
}

//----------------------------------------------------------------------------

void CvsControl::loginDir()
{
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   m_pMessages->setText("");
   m_pMessages->setCursorPosition(0, 0);
   dir->loginOk(m_pMessages, true);
}

//----------------------------------------------------------------------------

void CvsControl::logoutDir()
{
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   m_pMessages->setText("");
   m_pMessages->setCursorPosition(0, 0);
   dir->removeLogin(m_pMessages);
}

//----------------------------------------------------------------------------

void CvsControl::editFile( QString FN) {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QStringList nameList;
   nameList.append(FN);

   Mapping::FileAppItem item = getMatchForWildcard(nameList,Mapping::openFileAppList);
   if (!item.params.isEmpty()) {
    
      QStringList::Iterator it;
      for (it=nameList.begin(); it != nameList.end(); it++) {
         (*it).replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS);
	 (*it) = dir->fullName()+"/"+(*it);
      }
      QString cmd = masqWs(item.app);
      QString params = " " + item.params;
      if (masqedFilenamesForPlaceholders(params,nameList)) {
         cmd += params;
         runExternal(cmd);
      } else {
	 showWarning(tr("Warning"), tr("There are no matching placeholders specified,")
	       +"\n"
	       +tr("Check and adjust 'Options/Open file mapping ...' to proceed."));
      }
   } else {
      showWarning(tr("Warning"), tr("There is no application specified to open the selected file with,")
	    +"\n"
	    +tr("Check and adjust 'Options/Open file mapping ...' to proceed."));
   }
}

//----------------------------------------------------------------------------

void CvsControl::viewFileWithPath( QString FN) {

   int pos = FN.findRev("/");
   QString path = FN.mid(0,pos);
   FN = FN.mid(pos+1);

   QStringList nameList;
   nameList.append(FN);

   Mapping::FileAppItem item = getMatchForWildcard(nameList,Mapping::viewFileAppList);
   if (!item.params.isEmpty()) {
    
      QStringList::Iterator it;
      for (it=nameList.begin(); it != nameList.end(); it++) {
         (*it).replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS);
	 (*it) = path+"/"+(*it);
      }
      QString cmd = masqWs(item.app);
      QString params = " " + item.params;
      if (masqedFilenamesForPlaceholders(params,nameList)) {
	 cmd += params;
	 runExternal(cmd);
      } else {
	 showWarning(tr("Warning"), tr("There are no matching placeholders specified,")
	       +"\n"
	       +tr("Check and adjust 'Options/View file mapping ...' to proceed."));
      }
   } else {
      showWarning(tr("Warning"), tr("There is no application specified to view the selected file with,")
	    +"\n"
	    +tr("Check and adjust 'Options/View file mapping ...' to proceed."));
   }
}

//----------------------------------------------------------------------------

void CvsControl::browseDir()
{
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QString cmd = "\"" + ExtApps::g_localBrowser.path + "\" ";
   QStringList nameList;
   nameList.append(dir->fullName());//the startup dir is not sufficiant -- some browsers need the startup dir specified
   QString options = ExtApps::g_localBrowser.options;
   options = validateOptions(options);
   masqedFilenamesForPlaceholders(options, nameList);
   cmd += options;
   QDir * directory = new QDir(dir->fullName());
   runExternal(cmd, directory);
}

//----------------------------------------------------------------------------

void CvsControl::openShellDir()
{
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QString cmd = "\"" + ExtApps::g_localShell.path + "\" ";
   QStringList nameList;
   nameList.append(dir->fullName());//the startup dir is not sufficiant -- some consoles might need the startup dir specified
   QString options = ExtApps::g_localShell.options;
   //options = validateOptions(options); we cannot append an '%n' because the most shells don't like it!
   masqedFilenamesForPlaceholders(options, nameList);
   cmd += options;
   QDir * directory = new QDir(dir->fullName());
   runExternal(cmd, directory);
}

//----------------------------------------------------------------------------

void CvsControl::openFile()
{
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QStringList nameList = dir->getSelectedFiles();
   if (!nameList.empty()) {//dir is selected, no further check needed

      Mapping::FileAppItem item = getMatchForWildcard(nameList,Mapping::openFileAppList);
      if (!item.params.isEmpty()) {
      
	 QStringList::Iterator it;
	 for (it=nameList.begin(); it != nameList.end(); it++) {
            (*it).replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS);
            (*it) = dir->fullName()+"/"+(*it);
	 }
	 QString cmd = masqWs(item.app);
	 QString params = " " + item.params;
	 if (masqedFilenamesForPlaceholders(params,nameList)) {
	    cmd += params;
	    runExternal(cmd);
	 } else {
	    showWarning(tr("Warning"), tr("There are no matching placeholders specified,")
		  +"\n"
		  +tr("Check and adjust 'Options/Open file mapping ...' to proceed."));
	 }
      } else {
	 showWarning(tr("Warning"), tr("There is no application specified to open the selected files with,")
	       +"\n"
	       +tr("Check and adjust 'Options/Open file mapping ...' to proceed."));
      }
   }
}



//----------------------------------------------------------------------------

void CvsControl::viewFile()
{
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QStringList nameList = dir->getSelectedFiles();
   if (!nameList.empty()) {//dir is selected, no further check needed

      Mapping::FileAppItem item = getMatchForWildcard(nameList,Mapping::viewFileAppList);
      if (!item.params.isEmpty()) {
      
	 QStringList::Iterator it;
	 for (it=nameList.begin(); it != nameList.end(); it++) {
            (*it).replace("\"", PLACEHOLDER_FOR_QUOTATION_MARKS);
            (*it) = dir->fullName()+"/"+(*it);
	 }
	 QString cmd = masqWs(item.app);
	 QString params = " " + item.params;
	 if (masqedFilenamesForPlaceholders(params,nameList)) {
	    cmd += params;
	    runExternal(cmd);
	 } else {
	    showWarning(tr("Warning"), tr("There are no matching placeholders specified,")
		  +"\n"
		  +tr("Check and adjust 'Options/View file mapping ...' to proceed."));
	 }
      } else {
	 showWarning(tr("Warning"), tr("There is no application specified to view the selected files with,")
	       +"\n"
	       +tr("Check and adjust 'Options/View file mapping ...' to proceed."));
      }
   }
}

//----------------------------------------------------------------------------

void CvsControl::createNewFile() {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QString newFile = QInputDialog::getText("Create File", "Name: ",
	 QLineEdit::Normal ).stripWhiteSpace();
   if (!newFile.isEmpty() && !newFile.contains("/")) {
      QFile file(dir->fullName()+"/"+newFile);
      if (file.exists()) {
	 showWarning( tr("Warning"), tr("This file already exists") );
      } else if (file.open(IO_WriteOnly)) {
	 file.close();
	 dir->activateItem(false,( (dir->getEntryState(newFile)>0) ? DirBase::Controled : 0)|DirBase::NonControled|DirBase::Ignored);
      } else {
	 showWarning( tr("Warning"), tr("The file could not be created") );
      }
   } else {
      showWarning( tr("Warning"), tr("The filename is invalid") );
   }
}

//----------------------------------------------------------------------------

void CvsControl::createNewDir() {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QString newDir = QInputDialog::getText("Create Dir", "Name: ",
	 QLineEdit::Normal ).stripWhiteSpace();
   if (!newDir.isEmpty() && !newDir.contains("/")) {
      QDir d(dir->fullName()+"/"+newDir);
      if (d.exists()) {
	 showWarning( tr("Warning"), tr("This directory already exists") );
      } else if (d.mkdir(dir->fullName()+"/"+newDir,true)) {
	 dir->activateItem(false,DirBase::NonControled|DirBase::Ignored);
      } else {
	 showWarning( tr("Warning"), tr("The Dir could not be created") );
      }
   } else {
      showWarning( tr("Warning"), tr("The dirname is invalid") );
   }
}

//----------------------------------------------------------------------------

//returns the dir if successfully reopened, null otherwise
Directory * CvsControl::rereadProjectOfDir( Directory * dir ) {

   // have added directory
   QString dirToOpenAfterRescan = dir->fullName();

   DirBase * topDir = dir->topDir();

   // keep project identifiers
   QString moduleName = topDir->shortName();
   QString topDirName = topDir->fullName();

   //keep open dirs
   QStringList openDirs;
   dir->recGetOpenDirsStringList(openDirs);

   if (topDirName == dirToOpenAfterRescan) {
      delete dir;
      dir = NULL;
      performRereadProject( moduleName,topDirName);//calls addProject()
   } else {
      Directory * parentDir = dir->parent();
      parentDir->reloadChild(dirToOpenAfterRescan);
   }

   dir = (Directory *)m_pWorkBenchTree->find( dirToOpenAfterRescan );
   if( dir )  {
      QString curDir = dir->fullName();
      QStringList::Iterator it;
      for (it = openDirs.begin(); it != openDirs.end(); ++it) {
        Directory *openedDir = openTreeAt( topDirName, (*it));
        if (!openedDir && (curDir == (*it))) dir = 0;
      }
      if (dir) {
        m_pWorkBenchTree->setSelected( dir, TRUE );
        dir->activateItem(FALSE);
      }
   }
   applyProjectSettings(moduleName);
   return dir;
}

//----------------------------------------------------------------------------

void CvsControl::expandDir() {//only called from lincvs.cpp
   Directory * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);
   dir->expandAllDir();
   slot_checkStatusOfTree(dir);
   if (!blocked) blockGUI(false);
}

//----------------------------------------------------------------------------

void CvsControl::collapsDir() {//only called from lincvs.cpp
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
   blockGUI(true,BLOCKNOCONTROLS);
   dir->collapsAllDir();
   QApplication::restoreOverrideCursor();
   blockGUI(false,BLOCKNOCONTROLS);
}

//----------------------------------------------------------------------------

//works only on toplevel dirs!!!!!!!!!!!!!!!!!!!!
void CvsControl::renameDir(Directory * dir,QString newName) {

   QStringList projectNameList;
   projectSettings->getProjects(projectNameList);
   unsigned int i;
   for(i=0; i < projectNameList.count(); i++) {
      QString fullName;
      projectSettings->get(projectNameList[i],WORKDIR,fullName);
      if ( newName.startsWith(fullName)) {
	 int pos = fullName.length()-projectNameList[i].length();
	 QString newModule = newName.mid(pos,newName.find('/',pos)-pos);
	 if (projectNameList[i] == newModule) {
	    showWarning(tr("Warning: rename aborted")
		  ,tr("This project is already in workbench:")+" "
		  +projectNameList[i]
		  +"\nat: "+fullName);
	    setStatusText( tr("Rename aborted"), 4000 );
	    return;
	 }
      }
   }

   QString oldTopName = dir->topDir()->fullName();
   QString topModule = dir->topDir()->shortName();
   QString newTopModule = newName.mid( newName.findRev("/")+1);

   if (renameProject(topModule,newTopModule)) {
      if (!rename(dir->fullName(),newName)) {
	 renameProject(newTopModule,topModule);
	 showWarning(tr("Warning: rename aborted")
	       ,tr("Can't rename:\n")
	       +oldTopName
	       +",\n"+tr("check permissions"));
	 setStatusText( tr("Rename aborted"), 4000 );
	 return;
      }
   } else {
      showWarning(tr("Warning: rename aborted")
	    ,tr("Can't rename:\n")
	    +oldTopName
	    +",\n"+tr("the new name: ")+newTopModule+"\n"+tr("is already in use"));
      setStatusText( tr("Rename aborted"), 4000 );
      return;
   }

   QStringList openDirs;
   QStringList::Iterator it;
   dir->recGetOpenDirsStringList(openDirs);
   for (it = openDirs.begin(); it != openDirs.end(); ++it) {
      if ( (*it).startsWith(oldTopName)) {
	 (*it) = newName + (*it).mid(oldTopName.length());
      }
   }

   delete dir;
   dir = NULL;
   addProject(newName,TRUE);

   dir = static_cast<Directory *>(m_pWorkBenchTree->find(newName));
   if (dir) {
      QString curDir = dir->fullName();
      for (it = openDirs.begin(); it != openDirs.end(); ++it) {
        Directory *openedDir = openTreeAt( newName, (*it));
        if (!openedDir && (curDir == (*it))) dir = 0;
      }
      if (dir) {
        m_pWorkBenchTree->setSelected( dir, TRUE );
        dir->activateItem(FALSE);
      }
   }
}

//----------------------------------------------------------------------------

bool CvsControl::moveProject(const QString& project, const QString& oldPath, const QString& newPath) {//FIXME unused ...
   projectSettings->set(project,WORKDIR,newPath);
   QStringList::Iterator it;
   QStringList list;
   projectSettings->get(project,DISABLED,list);
   for (it = list.begin(); it != list.end(); ++it) {
      if ( (*it).startsWith(oldPath)) {
	 (*it) = newPath + (*it).mid(oldPath.length());
      }
   }
   projectSettings->set(project,DISABLED,list);
   projectSettings->getSubProjects(project,list);
   for (it = list.begin(); it != list.end(); ++it) {
      QString path;
      projectSettings->get(*it,WORKDIR,path);
      if ( path.startsWith(oldPath)) {
	 path = newPath + path.mid(oldPath.length());
      }
      projectSettings->set(*it,WORKDIR,path);
   }
   return true;
}

//----------------------------------------------------------------------------

bool CvsControl::renameProject(const QString& oldProject, const QString& newProject) {

   if (!projectSettings->renameProject(oldProject,newProject)) return false;
   QString oldWorkdir;
   projectSettings->get(newProject,WORKDIR,oldWorkdir);
   QString newWorkdir = oldWorkdir.left(oldWorkdir.length()-oldProject.length()) + newProject;
   projectSettings->set(newProject,WORKDIR,newWorkdir);

   QStringList::Iterator it;
   QStringList list;
   projectSettings->get(newProject,DISABLED,list);
   QStringList oldDisabledList = list;
   for (it = list.begin(); it != list.end(); ++it) {
      if ( (*it).startsWith(oldWorkdir)) {
	 (*it) = newWorkdir + (*it).mid(oldWorkdir.length());
      }
   }
   projectSettings->set(newProject,DISABLED,list);

   list.clear();
   projectSettings->getSubProjects(newProject,list);
   for (it = list.begin(); it != list.end(); ++it) {

      QString newName = newProject + (*it).mid(oldProject.length());
      if (renameProject(*it,newName)) {
	 *it = newName;
      } else {//rollback
	 QStringList::Iterator rit = list.begin();
	 while( rit != it) {
	    QString oldName = oldProject + (*it).mid(newProject.length());
	    renameProject(*it,oldName);
	    ++rit;
	 }
	 projectSettings->renameProject(newProject,oldProject);
	 projectSettings->set(oldProject,WORKDIR,oldWorkdir);
	 projectSettings->set(oldProject,DISABLED,oldDisabledList);
	 return false;
      }

   }
   projectSettings->setSubProjects(newProject,list);
   return true;
}

//----------------------------------------------------------------------------

void CvsControl::slot_removeFileFromDisk() {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QStringList files = dir->getSelectedFiles();
   if (files.isEmpty()) return;

   if (!showVerifyDialog(tr("Warning"),tr("This command will remove file(s) from disk!"))) {
      return;
   }

   QString path = dir->fullName()+"/";

   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);

   QStringList::Iterator it;
   for( it = files.begin(); it != files.end(); ++ it ) {
      QFile f(path+*it);
      if (!QFileInfo(f.name()).isSymLink()) setPermission(f,READABLE | WRITEABLE);
      if (f.remove()) {
	 dir->setEntryState(*it,ES_missing);
      } else qDebug("couldn't remove: "+path+*it);
   }
   
   dir->postCallCheck(DirBase::Controled|DirBase::Force);
   if (!blocked) blockGUI(false);
}

//----------------------------------------------------------------------------

void CvsControl::removeNonCvsFileFromDisk() {
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }
   if (!showVerifyDialog(tr("Warning"),tr("This command will remove file(s) from disk!"))) {
      return;
   }

   QString CurDir = dir->fullName() + "/";
   QListViewItem *myChild = static_cast<QListView*>(m_pFileViews->page(1))->firstChild();
   while(myChild) {
      if(myChild->isSelected()) {
	 QString fileName = myChild->text(0).stripWhiteSpace();
	 QFileInfo info( CurDir + fileName);
	    
	 if (!info.isSymLink() && info.isDir()) {
	    if (removeDirTreeFromDisk(CurDir + fileName, FALSE)) {
	       if (!removeDirTreeFromDisk(CurDir + fileName, TRUE)) {
		  myChild = myChild->nextSibling();
		  continue;
	       }
	    }
	 } else {
	    QFile f( CurDir + fileName);
	    if (!QFileInfo(f.name()).isSymLink()) setPermission(f,READABLE | WRITEABLE);
	    f.remove();
	 }
	 QListViewItem *childToDelete;
	 childToDelete = myChild;
	 myChild = myChild->nextSibling();
	 delete childToDelete;
	 continue;
      }
      myChild = myChild->nextSibling();
   }
   dir->activateItem(TRUE);
}

//----------------------------------------------------------------------------

void CvsControl::removeCvsIgnoreFileFromDisk() {
   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   if (!showVerifyDialog(tr("Warning"),tr("This command will remove file(s) from disk!"))) {
      return;
   }

   QString CurDir = dir->fullName() + "/";
   QListViewItem *myChild = static_cast<QListView*>(m_pFileViews->page(2))->firstChild();
   while(myChild) {
      if(myChild->isSelected()) {
	 QString fileName = myChild->text(0).stripWhiteSpace();
	 QFileInfo info( CurDir + fileName);
	    
	 if (!info.isSymLink() && info.isDir()) {
	    if (removeDirTreeFromDisk(CurDir + fileName, FALSE)) {
	       if (!removeDirTreeFromDisk(CurDir + fileName, TRUE)) {
		  myChild = myChild->nextSibling();
		  continue;
	       }
	    }
	 } else {
	    QFile f( CurDir + fileName);
	    if (!info.isSymLink()) setPermission(f,READABLE | WRITEABLE);
	    f.remove();
	 }
	 QListViewItem *childToDelete;
	 childToDelete = myChild;
	 myChild = myChild->nextSibling();
	 delete childToDelete;
	 continue;
      }
      myChild = myChild->nextSibling();
   }
   dir->activateItem(TRUE);
}

//----------------------------------------------------------------------------

bool CvsControl::removeDirTreeFromDisk(QString dir,bool remove) {

   QDir D( dir);
   D.setFilter( QDir::Dirs); //should list all dirs
   QStringList AllDirEntries = D.entryList();
   AllDirEntries.remove( "." );
   AllDirEntries.remove( ".." );

   if (!remove && (AllDirEntries.find( "CVS") != AllDirEntries.end())) return FALSE;

   QStringList::Iterator it;
   for ( it = AllDirEntries.begin(); it != AllDirEntries.end(); it++ ) {
      if (!removeDirTreeFromDisk( dir+"/"+(*it),remove)) return FALSE;
   }

   dir += "/";
   D.setFilter( QDir::Files | QDir::Hidden); //should list everything
   QStringList AllEntriesInDir = D.entryList();

   for ( it = AllEntriesInDir.begin(); it != AllEntriesInDir.end(); it++ ) {
      QString file = dir + (*it);
      if (remove) {
	 QFile f( file);
	 if (!QFileInfo(file).isSymLink()) setPermission(f,READABLE | WRITEABLE);
	 if (!f.remove()) {
	    showWarning( tr("Warning"), tr("Cannot remove:\n")+file);
	    return FALSE;
	 }
      }
   }
   QFileInfo info( D.path());
   if (info.permission( QFileInfo::WriteUser)) {
      if (remove) {
	 if (D.rmdir(D.path())) return TRUE;
	 else {
	    showWarning( tr("Warning"), tr("Cannot remove:") + "\n" + D.path() + "\n" + tr("no permission") );
	    return FALSE;
	 }
      } else return TRUE;
   } else {
      showWarning( tr("Warning"), tr("Cannot remove:") + "\n" + D.path() + "\n" + tr("no permission") );
      return FALSE;
   }
}

//----------------------------------------------------------------------------

bool CvsControl::removeFilesInTreeFromDisk(QString dir,bool remove) {//unused

   QDir D( dir);
   D.setFilter( QDir::Dirs); //should list all dirs
   QStringList AllDirEntries = D.entryList();
   AllDirEntries.remove( "." );
   AllDirEntries.remove( ".." );

   if (!remove && (AllDirEntries.find( "CVS") != AllDirEntries.end())) return FALSE;
   AllDirEntries.remove( "CVS");

   QStringList::Iterator it;
   for ( it = AllDirEntries.begin(); it != AllDirEntries.end(); it++ ) {
      if (!removeFilesInTreeFromDisk( dir+"/"+(*it),remove)) return FALSE;
   }

   dir += "/";
   D.setFilter( QDir::Files | QDir::Hidden); //should list everything
   QStringList AllEntriesInDir = D.entryList();

   for ( it = AllEntriesInDir.begin(); it != AllEntriesInDir.end(); it++ ) {
      QString file = dir + (*it);
      if (remove) {
	 QFile f( file);
	 if (!QFileInfo(file).isSymLink()) setPermission(f,READABLE | WRITEABLE);
	 if (!f.remove()) {
	    showWarning( tr("Warning"), tr("Cannot remove:") + "\n" + file);
	    return FALSE;
	 }
      }
   }
   return TRUE;
}

//----------------------------------------------------------------------------

void CvsControl::stopCurAction()
{
   globalStopAction = true;
   
   if(m_interactiveCmdThreadIsRunning) {//nur fuer cvs thread
      m_interactiveCmdThreadIsRunning = false;   //"prellen" verhindern
      CExtTimer::singleShot(100, this, SLOT(killInteractiveCmdThread()));
   }

}

//----------------------------------------------------------------------------

void CvsControl::killInteractiveCmdThread()
{
   //m_pInteractiveCmdThread can be 0 after cancelling cvs action by user
   if (m_pInteractiveCmdThread) m_pInteractiveCmdThread->exit();
}

//----------------------------------------------------------------------------

//write config
void CvsControl::writeCfg()
{
   conf->writeCfg();
}

//----------------------------------------------------------------------------

void CvsControl::removeDirIfNoCvsRegisteredFiles() {

   Directory * dir = static_cast<Directory *>(m_pWorkBenchTree->selectedItem());
   if(!dir) {
      showWarning( tr("Warning"), tr("There is no directory selected") );
      return;
   }

   if(!dir->loginOk(m_pMessages)) return;

   if (!showVerifyDialog(tr("Warning"),tr("This command will recursively remove\nfile(s) from disk!"))) {
      return;
   }

   bool blocked = isGuiBlocked();
   if (!blocked) blockGUI(true);
   bool isTopLevel = (dir == dir->topDir());

   if ( !(removeDirTreeFromDisk(dir->fullName(), FALSE)) ) {

      if ( !( showYesNoQuestion( tr("Warning"),
			tr("The directory contains files under CVS control:") + "\n" +
			dir->fullName() + "\n" + tr("Delete anyway?") )
		  && removeDirTreeFromDisk(dir->fullName(), TRUE)) ) {

	 showWarning( tr("Error"),
	       tr("Failure during removal of:") + "\n" + dir->fullName() );
	 if (!blocked) blockGUI(false);
	 return;
      }
   }
   if (!isTopLevel) rereadProjectOfDir( dir->parent() );
   if (!blocked) blockGUI(false);
}

//----------------------------------------------------------------------------

/*
 * This funktion should export the workdir the user klicked on to export.
 * Since the cvs export funktion only exports cvs registered files from the
 * repository, we need here the ability to only export the cvs registered
 * files as well, but maybe in modified state, just as they are currently
 * in the workdir. If a file for example is missing, we don't export it, and
 * so on. So we have here a funktion that exports our working dir, but without
 * ignored or not cvs registered files. We could build a tarbal without .o and
 * whatever files, but wouldn't need to run make clean before (very anoying in
 * big projects since it takes quite long to recompile).
 */
void CvsControl::exportFromModuleDir() {

   DirBase * dir;
   if (!(dir = getSelectedDir())) {
      return;
   }

   QString exportDir = dir->shortName()+"_export";
   QString parentDir = dir->fullName();
   parentDir = parentDir.left(parentDir.findRev("/"));
   QFileDialog* fd = new QFileDialog(parentDir, NULL,
				     this, "directory dialog", true );
   fd->setCaption("Export project: "+dir->shortName());
   fd->setMode( QFileDialog::DirectoryOnly );
   fd->setSelection(exportDir);
   if ( fd->exec() == QDialog::Accepted) {
      exportDir = fd->selectedFile();
      if ( exportDir.isEmpty()) return;
      while ( (exportDir.length() > 1) && exportDir.endsWith("/") ) exportDir.truncate(exportDir.length()-1);
   } else {
      return;
   }

   QDir exdir(exportDir);
   if (exdir.exists()) {
      QStringList entries = exdir.entryList();
      bool bEmpty = true;
      for (QStringList::Iterator it = entries.begin(); it != entries.end(); it++ ) {
         if ( (*it == ".") || (*it == "..") ) continue;
         bEmpty = false;
         break;
      }
      if (!bEmpty) {
         if (!showYesNoQuestion(tr("Warning"),
            tr("The directory:") + " "
            + exportDir
            + " " + tr("already exists.") + "\n" + tr("Overwrite it?"))) return;
      }
   }

   QApplication::setOverrideCursor(waitCursor);
   if (!dir->recCopyControlledFiles(exportDir,DirBase::Replace)) showWarning( (tr("Error")+", "+tr("export stopped")),
	 tr("Couldn't create directories"));
   QApplication::restoreOverrideCursor();
}

//----------------------------------------------------------------------------

void CvsControl::initLocalRepository(QString cvsRoot) {

   QString topModule = QString::null;
   QString file = "init";
   cvsRoot = masqWs(cvsRoot);
   callInteractiveCmd( topModule, APPDIR, cvsRoot,
	 file, CVS_NOT_INTERACTIVE_CMD,
	 ExtApps::g_cvsRsh.path);  //additional options of cvsRsh not supported yet

}

/*****************************************************************************
 *
 * Call InteractiveCmdThread!
 *      Params: topModule -        the toplevel (dir-)name of the module
 *              dir -              the dir to invoke the command in
 *              cvsRootOrCommand - the CVS/Root or command ==> noroot
 *              files -            the files to invoke the command with
 *              icmd -             Command that should be called (ie.CommandInterface.h)
 *              cvsRsh -           the remote shell to use
 *              noroot -           if noroot is true the command except 'cvs'
 *                                 has to be appended to cvsRootOrCommand, no -d option will be specified
 *              instance -         the instance (for ex. a dialog implementing CommandInterface.h)
 *                                 if instance is given with 'this', afterCall(icmd) in 'this' instance
 *                                 will be called when cvs-call has finished
 *
 *      to append additional interactive commands, InteractiveCmdThread.cpp and 
 *              InteractiveCmdThread.h have to be edited and the additional rules
 *              must be put to the switch-clauses
 *
 *      to call cvs from external classes, inherit CommandInterface, implement void afterCall(int cmd)
 *      and start cvscall: void callInteractive( dir, cvsRootOrCommand, files, icmd, cvsRsh, noroot);
 *      calling cvsCallFailed(icmd,instance) makes sure that afterCall gets called in error situation too.
 *
 ****************************************************************************/
void CvsControl::callInteractiveCmd( QString& topModule,
      QString& dir,
      QString& cvsRootOrCommand,
      QString& files,
      int icmd,
      QString& cvsRsh,
      bool noroot /*=FALSE*/,
      CommandInterface *instance /*=NULL*/) {

   if (m_interactiveCmdThreadIsRunning) {//make sure no two commands run simultaneous
      QString msg = "THREAD WARNING: InteractiveCmdThread is already running, call: "+QString::number(icmd)+" canceled";
      if (Debug::g_pLog) Debug::g_pLog->log(Debug::LL_THE_OLD_MAN_AND_THE_SEA,msg);
      cvsCallFailed(icmd,instance);//makes sure automatic recursive calls don't get mixed up
      return;
   }

   QString command;
   setStatusText("");
   m_pLastCvsCallDir = static_cast<Directory *>(m_pWorkBenchTree->find(dir.stripWhiteSpace()));
   QDir workDir(dir.stripWhiteSpace());
   if (!workDir.exists()) {//would run in LinCVS's workdir otherwise
      cvsCallFailed(icmd,instance);
      return;
   }

   // check for required login
   QString cvsRootLine;
   QString method;
   QString user;
   QString passwd;
   QString host;
   int port;
   QString rootDir;

   if (noroot) { // We have to check the cvs connection method
      QFile f;
      f.setName(dir + "/CVS/Root");
      if(f.open(IO_ReadOnly)) {
	 QTextStream textStream(&f); 
	 cvsRootLine = textStream.readLine();
	 f.close();
      }
   } else {
      int pos = cvsRootOrCommand.find("\"",1);//extract tmp files
      if (pos > -1) cvsRootLine = cvsRootOrCommand.left(pos+1);
      else cvsRootLine = cvsRootOrCommand;
      cvsRootLine = umasqWs(cvsRootLine);//only umasq for local use, not for cmd call!!!
   }
   if (!extractCVSROOT( cvsRootLine, method, user, passwd, host, port, rootDir)) {
      if (Debug::g_pLog) Debug::g_pLog->log(Debug::LL_THE_OLD_MAN_AND_THE_SEA,"ERROR in cvsRootLine: ->"+cvsRootLine+"<-");
      cvsCallFailed(icmd,instance);
      return;
   }

   globalStopAction = false;
   unsigned int pwdPos = 0;
   unsigned int pwdLen = 0;

   if ((method == "pserver") || (method == "sspi")) { // password server OR sspi

      Debug::g_pLog->log(Debug::LL_INFO, "access method: pserver OR sspi");

      if (passwd.isNull() && !isInCvsPass(cvsRootLine))
	 {

	    bool ok = false;

	    bool blocked = isGuiBlocked();
	    if (blocked) blockGUI(false);

	    QWidget * parentWidget = qApp->activeWindow();
	    //if (!parentWidget) parentWidget = LookAndFeel::g_b0AsParent ? 0 : this;
	    if (!parentWidget) parentWidget = this;
	    QString pwd = QInputDialog::getText("Enter CVS password",
		  ":" + method + ":" + user + "@" + host + ":" + QString::number(port) + rootDir, QLineEdit::Password,
		  QString::null, &ok, parentWidget, "Password Dialog");

	    if (blocked) blockGUI(true);

	    if (!ok) {
	       cvsCallFailed(icmd,instance);
	       return;
	    }

	    globalStopAction = false;

	    if (!blocked) blockGUI(true);

	    Login *l = new Login(m_pMessages, method, user, host, port, rootDir);
	    l->doLogin(pwd);
	    delete l;

	    if (!blocked) blockGUI(false);

            if(!isInCvsPass(cvsRootLine)) {
	       if (!KILLED) {
		  showWarning(tr("Warning"), tr("Login failed.") );
	       }
	       cvsCallFailed(icmd,instance);
	       return;
            }
	 }

      //export proxy settings
      QString proxyString = getProxy(user+"@"+host);
      if (!proxyString.isEmpty()) {
	 if ( (icmd==CVS_RELEASE_D_CMD) || (icmd==CVS_RELEASE_CMD)) {//cvs bug prevents proxy usage
	    showWarning( tr("Warning"), tr("can't release through proxy tunnel.") );
	    cvsCallFailed(icmd,instance);
	    return;
	 }
	 command += "export PSERVER_PROXY=" + proxyString + " && ";
         int pos = proxyString.find (':');
         if (pos > 0) {
            command += "export CVS_PROXY=" + proxyString.left (pos) + " && ";
            command += "export CVS_PROXY_PORT=" + proxyString.mid (pos+1) + " && ";
         }
      }

   } else if ( method == "ext") { // external connect method

	
#ifdef Q_WS_WIN
      command += "export CVS_RSH=" + cvsRsh + " && ";
#else
      QString rsh = APPDIR + "/" + LC_TOOLS_DIR + "/rshwrapper";
      if (!QFileInfo(rsh).exists()) {
	 showWarning( tr("Warning"), tr("can't access rshwrapper: "+rsh+": file not found") );
	 cvsCallFailed(icmd,instance);
	 return;
      }

      command += "export CVS_RSH=" + rsh + " && export WRAPPER_RSH=" + cvsRsh + " && ";
#endif

      //need project specific settings here:
      QString cvsServerCmd;
      if ( projectSettings->get(topModule,CVSSERVER,cvsServerCmd)) {
	 if (!cvsServerCmd.isEmpty()) {
	    command += "export CVS_SERVER=" + cvsServerCmd + " && ";
	 }
      }

      int sshAccess = NOSSH;
      if ( projectSettings->get(topModule,SSHACCESS,sshAccess)) {
	 bUseSsh = false;
	 bUseSshAgent = false;
	 bUseSshAgentVars = false;
	 switch( sshAccess) {
	    case USESSH: {
	       bUseSsh = true;
	       break;
	    }
	    case USESSHAGENT: {
	       bUseSshAgent = true;
	       break;
	    }
	    case USESSHAGENTVARS: {
	       bUseSshAgentVars = true;
	       break;
	    }
	 }
      }

      if (bUseSsh) {//use modified ssh with graphical login

	 Debug::g_pLog->log(Debug::LL_INFO, "access method: ext/ssh");

	 if (WINVERSION) {

	    bool ok = false;

	    bool blocked = isGuiBlocked();
	    if (blocked) blockGUI(false);

	    QWidget * parentWidget = qApp->activeWindow();
	    //if (!parentWidget) parentWidget = LookAndFeel::g_b0AsParent ? 0 : this;
	    if (!parentWidget) parentWidget = this;
	    QString pwd = QInputDialog::getText("Enter CVS password",
		  ":" + method + ":" + user + "@" + host + ":" + QString::number(port) + rootDir, QLineEdit::Password,
		  QString::null, &ok, parentWidget, "Password Dialog");

	    if (blocked) blockGUI(true);

	    if (!ok) {
	       cvsCallFailed(icmd,instance);
	       return;
	    }

	    command += "export SSH_LinCVS_PW=";
	    pwdPos = command.length();
	    pwdLen = pwd.length();
	    command += pwd;
	    command += " && ";

	 } else {

	    command += "export SSH_LinCVS=LinCVSMagic && ";

	 }

      } else if ( !cvsRsh.isEmpty() && (bUseSshAgent || bUseSshAgentVars) ) { // special for ssh

	 Debug::g_pLog->log(Debug::LL_INFO, "access method: ext/ssh-agent");

	 sshAgentIsRunning = false;
	 if (!sshAgentStarted || bUseSshAgentVars) {
	    bool blocked = isGuiBlocked();
	    if (!blocked) blockGUI(true);
	    if (!bUseSshAgentVars) setStatusText(tr("Starting ssh-agent. Please wait ..."));
	    startSshAgent();//(check for ssh agent variables if bUseSshAgentVars or start ssh agent
	    if (!blocked) blockGUI(false);
	    if (bUseSshAgentVars) {
	       if (sshAgentIsRunning) {
		  setStatusText(tr("Using running ssh-agent. Please wait ..."));
	       } else if (sshAgentStarted) {
		  setStatusText(tr("Couldn't detect running ssh-agent. Own agent started, please wait ..."));
	       } else {
		  setStatusText(tr("Cannot detect running ssh-agent and couldn't start agent, call canceled."));
		  cvsCallFailed(icmd,instance);
		  return;
	       }
	    } else if (!sshAgentStarted) {
	       setStatusText(tr("Couldn't start ssh-agent"));
	       cvsCallFailed(icmd,instance);
	       return;
	    } else {
	       setStatusText(tr("Ssh-agent started. Please wait ..."));
	    }
	 }

	 if (!sshAgentIsRunning) checkSshAgent();//read agent env vars
	 if (!sshAgentIsRunning) {//cancel the command if ssh agent could not be started
	    setStatusText(tr("Cannot detect running ssh-agent, call canceled."));
	    cvsCallFailed(icmd,instance);
	    return;
	 }

	 if (WINVERSION) {//todo: setup for win ssh-agent

	 } else {

	    command += "export ";
	    command += envSSH_AGENT_PID;
	    command += " && ";
	    command += "export ";
	    command += envSSH_AUTH_SOCK;
	    command += " && ";

	    command += "export SSH_LinCVS=LinCVSMagic && ";

	 }

      } else if (cvsRsh.isEmpty() || bUseSshAgent || bUseSshAgentVars) {
	 showWarning( tr("Warning"),
	       tr("Connect method is 'ext'")
	       +",\n"
	       +tr("but there is no ssh remote shell specified, aborting.") );
	 cvsCallFailed(icmd,instance);
	 return;

      } else {// other connect method

	 Debug::g_pLog->log(Debug::LL_INFO, "access method: ext/unknown");

      }

   } else { // local connect method

      Debug::g_pLog->log(Debug::LL_INFO, "access method: local");

   }

   //append cvsRoot (ev. including additional commands) to command
   command += "cvs ";
   if (!noroot) command +="-d ";
   command += cvsRootOrCommand;

   //complete the command
   switch(icmd) {
      case CVS_RELEASE_CMD: {
	 command += " release";
	 break;
      }
      case CVS_RELEASE_D_CMD: {
	 command += " release -d";
	 break;
      }
      case CVS_UNEDIT_CMD: {
	 command += " unedit";
	 break;
      }
      default: {
	 break;
      }
   }

   //change to absolute cvs path if given
   if (!CVSPATH.isEmpty()) {
      command.replace( QRegExp("^cvs\\s"), masqWs(CVSPATH)+" ");
      command.replace( QRegExp("&&\\scvs\\s"), "&& "+masqWs(CVSPATH)+" ");
   }

   if (!m_chainedCommand) {//don't clear output window on chained calls
      m_pMessages->reset();
   } else {
      m_pMessages->insert("\n========================== "+tr("next command")+" ===========================\n\n");
   }
   m_pMessages->setMode(icmd);
   m_pCvsBuffer->clear();

   if (!isGuiBlocked()) blockGUI(true);
   setStatusText(tr("Operation in progress.  Please wait ..."));
   suspendDirWatch();
   m_pPixmapTimer->slotStart();

   m_debugCvsCallString = command;
   if (pwdLen > 0) {
      m_debugCvsCallString.replace(pwdPos,pwdLen,"*****");
   }
   m_debugCvsCallString = "cvscall:\ndir: ->"+
      workDir.absPath()+"<-\ncommand: ->"+
      m_debugCvsCallString+"<-\nicmd: ->"+
      QString::number(icmd)+"<-\nfiles: ->"+
      files+"<-\n";

   delete m_pInteractiveCmdThread;
   m_pInteractiveCmdThread = new InteractiveCmdThread( this, m_pCvsBuffer, m_pMessages, workDir, command, icmd, files, instance);

   connect(m_pInteractiveCmdThread,SIGNAL(requestReceived(MESSAGE)),this,SLOT(handleMessage(MESSAGE)));
   connect(m_pInteractiveCmdThread,SIGNAL(cvsCommandFinished()),this,SLOT(cvsCommandFinished()));

   if (m_chainedCommand) {//delay chained cvs call, to make CVS dir timestamp detectable as beeing changed
      int from = QTime::currentTime().second();
      while (from == QTime::currentTime().second()) {
	 qApp->processEvents(100);
	 wait(100);
      }
      m_chainedCommand = false;
   }

   if (m_pInteractiveCmdThread->start()) {
      m_interactiveCmdThreadIsRunning = true;
   }
}

//----------------------------------------------------------------------------

void CvsControl::cvsCallFailed(int cmd, CommandInterface* instance) {

   if (Debug::g_pLog) Debug::g_pLog->log(Debug::LL_INFO, "cvs call failed");
   m_pLastCvsCallDir = NULL;
   setStatusText(tr("Cvs call failed"),2000);
   if (instance) {
      instance->afterCall( cmd, m_pCvsBuffer, true);
   }
}

//----------------------------------------------------------------------------

void CvsControl::cvsCommandFinished() {

   //   qDebug("-->"+m_pCvsBuffer->readAll()+"<--");
   if (!globalStopAction) {
      finishInteractiveCmd();
   } else {
      m_pMessages->append("\n" + tr("Action interrupted by user!") + "\n");
      finishInteractiveCmd();
   }
}

//----------------------------------------------------------------------------

void CvsControl::handleMessage(MESSAGE m) {
   m_pPixmapTimer->slotStop();
   setStatusText(tr("waiting for your answer ..."));
   switch( m.type) {
      case CVS_Y_N: {
	 blockGUI(false);
	 if (showYesNoQuestion(tr("cvs asks:"), m.request + "\n\n") ) {
	    m.reply = "y\n\0";
	    m_pInteractiveCmdThread->sendReply( m);
	 } else {
	    m.reply = "n\n\0";
	    m_pInteractiveCmdThread->sendReply( m);
	 }
	 blockGUI(true);
	 break;
      }
      default: {
	 Debug::g_pLog->log(Debug::LL_THE_OLD_MAN_AND_THE_SEA,"unknown cvs message to handle: ->"+m.request+"<-");
	 break;
      }
   }
   m_pPixmapTimer->slotStart();
   setStatusText(tr("Operation in progress.  Please wait ..."));
}

//----------------------------------------------------------------------------

void CvsControl::finishInteractiveCmd() {

   CommandInterface *afterCallInstance = m_pInteractiveCmdThread->getInstance();
   int cmd = m_pInteractiveCmdThread->getCommand();

   bool callFailed = !m_interactiveCmdThreadIsRunning;
   m_interactiveCmdThreadIsRunning = false;

   if (m_pLastCvsCallDir) {
      QDateTime t = QDateTime::currentDateTime();
      m_pLastCvsCallDir->setCallTime(t);
      m_pLastCvsCallDir = NULL;
   }

   if ( Debug::g_logLevel >= Debug::LL_INFO) {
      QString str;
      QTextStream stream(&str,IO_WriteOnly);
      fillCmdDebugStream(stream);
      Debug::g_pLog->log(Debug::LL_INFO, str);
   }

   if (afterCallInstance) {
      m_chainedCommand = true;
      afterCallInstance->afterCall(cmd,m_pCvsBuffer,callFailed);
      afterCallInstance = NULL;
      if (m_interactiveCmdThreadIsRunning) {// for loop commands, (i.e. commands calling another cvs command) don't continue
	 return;
      } else {
	 m_chainedCommand = false;
      }
   } else {
      Directory * dir = static_cast<Directory *>(m_pWorkBenchTree->selectedItem());
      if (dir) {
	 slot_checkStatusOfTree(dir);
      }
   }

   expurgateProjectSettings();

   m_pPixmapTimer->slotStop();
   globalStopAction = false;
   blockGUI(false);
   resumeDirWatch();

   //set status info
   DirBase * dir = m_pWorkBenchTree->selectedItem();
   if (dir) {
      setStatusText(dir->fullName());
   }
   setStatusText( tr("Ready"), 2000);
}

//----------------------------------------------------------------------------

void CvsControl::afterCall(int cmd,CvsBuffer*,bool failed) {
   switch(cmd) {
      case CVS_GET_TAG_INFO_PROJECT_CMD: {
         if (failed) {
            emit tagListFetched();
            return;
         }
         QStringList projectTagList;
         projectSettings->get(m_pFetchTagDir->topControlledDir()->relativeName(),PROJECTTAGLIST,projectTagList);
         m_pFetchTagDir->parseCallResult( m_pCvsBuffer, cmd, &projectTagList);
         projectTagList.sort();
         projectSettings->set(m_pFetchTagDir->topControlledDir()->relativeName(),PROJECTTAGLIST,projectTagList);
         emit tagListFetched();
         m_pFetchTagDir = NULL;
         break;
      }
      case CVS_GET_TAG_INFO_FILES_CMD: {
         if (failed) {
            emit tagListFetched();
            return;
         }
         m_pFetchTagDir->parseCallResult( m_pCvsBuffer, cmd, &m_currentTagList);
         m_currentTagList.sort();
         //now add to projectTagList
         if (!m_currentTagList.isEmpty()) {
            QStringList projectTagList;
            projectSettings->get(m_pFetchTagDir->topControlledDir()->relativeName(),PROJECTTAGLIST,projectTagList);
            for (QStringList::Iterator it=m_currentTagList.begin(); it != m_currentTagList.end(); ++it) {
               if (projectTagList.find(*it) == projectTagList.end()) {
                  projectTagList.append(*it);
               }
            }
            projectTagList.sort();
            projectSettings->set(m_pFetchTagDir->topControlledDir()->relativeName(),PROJECTTAGLIST,projectTagList);
         }
         emit tagListFetched();
         m_pFetchTagDir = NULL;
         break;
      }
      default: {
         Debug::g_pLog->log(Debug::LL_THE_OLD_MAN_AND_THE_SEA,"there is no afterCall implemented for cmd id: "+QString::number(cmd));
      }
   }
}

//----------------------------------------------------------------------------

