/****************************************************************************
**
** Copyright (C) 2004-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 "PatchDialogImpl.h"

#include <qapplication.h>
#include <qpushbutton.h>
#include <qradiobutton.h>
#include <qbuttongroup.h>
#include <qcombobox.h>
#include <qlineedit.h>
#include <qwhatsthis.h>
#include <qfiledialog.h>
#include <qmessagebox.h>
#include <qlayout.h>
#include <qtimer.h>
#include <assert.h>

#include "globals.h"
#include "pixmapcache.h"
#include "Validators.h"

// dir
PatchDialogImpl::PatchDialogImpl( QString dirName, QString shortDirName, QStringList * list,
				  QString moduleName, const QIconSet &whatsThisIconSet,
				  QWidget * parent)
   : PatchDialog(parent, "PatchDialogImpl", true, LookAndFeel::g_modalF),
     m_parent(parent),
     m_name(FetchButton->name()),
     m_dirName(dirName),
     m_file(shortDirName),
     m_pSelectedTagList(list),
     m_topModuleName(moduleName),
     m_fetchOverall(TRUE),
     m_firstTagListOnEdit(FALSE),
     m_pOldFirstTagPixmap(NULL),
     m_lastFirstTagLen(0),
     m_running(false),
     m_patchFile(QString::null)
{
   setCaption (tr("Create patch (dir)"));
   removeRevisionSelection();
   init(whatsThisIconSet);
}

// files
PatchDialogImpl::PatchDialogImpl( QString dirName, QStringList fileList, QStringList * list,
				  QString moduleName, const QIconSet &whatsThisIconSet,
				  QWidget * parent)
   : PatchDialog(parent, "PatchDialogImpl", true, LookAndFeel::g_modalF),
     m_parent(parent),
     m_name(FetchButton->name()),
     m_dirName(dirName),
     m_file(fileList.join("-")),
     m_pSelectedTagList(list),
     m_topModuleName(moduleName),
     m_fetchOverall(TRUE),
     m_firstTagListOnEdit(FALSE),
     m_pOldFirstTagPixmap(NULL),
     m_lastFirstTagLen(0),
     m_running(false),
     m_patchFile(QString::null)
{
   setCaption (tr("Create patch (files)"));
   if (fileList.count() > 1) {
      removeRevisionSelection();
   } else {
      assert(fileList.count());
      revisionEdit->setValidator(new RevisionValidator(revisionEdit));
      RevisionButton->setFocusPolicy(QWidget::TabFocus);
   }
   init(whatsThisIconSet);
}

void PatchDialogImpl::init(const QIconSet &whatsThisIconSet) {

   m_pWhatsThis->setIconSet(whatsThisIconSet);
#ifdef Q_WS_MAC
   m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height() * 2);
#else
   m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height());
#endif
   
   dateTimeEdit->dateEdit()->setOrder(LookAndFeel::g_dateTimeOrder);
   dateTimeEdit->setDateTime(QDateTime::currentDateTime());

   DetailGroup->hide();
   
   tagEdit->setAutoCompletion(FALSE);
   tagEdit->setValidator(new TagValidator(tagEdit));

   connect( tagEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChangedFirstTag(const QString &)));
   connect( tagEdit, SIGNAL(activated(const QString &)), this, SLOT(activatedFirstTag(const QString &)));
   connect( tagEdit->lineEdit(), SIGNAL(selectionChanged()), this, SLOT(setSelectionFirstTag()));

   connect( m_parent, SIGNAL(tagListFetched()), this, SLOT(tagListUpdated()));
   connect( this, SIGNAL(getProjectTags()), m_parent, SLOT(updateProjectTagList()) );
   connect( this, SIGNAL(getSelectedTags()), m_parent, SLOT(readCurrentTagList()));
   
   tagListUpdated();
   
   qApp->processEvents();
   adjustSize();
}

PatchDialogImpl::~PatchDialogImpl()
{
    // no need to delete child widgets, Qt does it all for us
}

void PatchDialogImpl::removeRevisionSelection() {
   DetailGroupLayout->remove(RevisionButton);
   delete RevisionButton;
   RevisionButton = NULL;
   DetailGroupLayout->remove(revisionEdit);
   delete revisionEdit;
   revisionEdit = NULL;
   DetailGroupLayout->removeItem(revisionSpacer);
   delete revisionSpacer;
   revisionSpacer = NULL;
   TagButton->setFocusPolicy(QWidget::TabFocus);
   TagButton->setChecked(true);
   setTabOrder(BrowseButton,TagButton);
}

void PatchDialogImpl::readProjectNameList() {
   m_ProjectTagList.clear();
   projectSettings->get(m_topModuleName,PROJECTTAGLIST,m_ProjectTagList);
}

void PatchDialogImpl::fetchClicked() {

   if (m_running ^= true) {//not running
      if (m_fetchOverall ^= TRUE) {//overall mode
	 emit getProjectTags();
      } else {
	 emit getSelectedTags();
      }
      FetchButton->setName("stop action");
      FetchButton->setText(tr("Stop"));
   } else {
      QTimer::singleShot(0,m_parent,SLOT(stopCurAction()));
      FetchButton->setName(m_name);
      if (m_fetchOverall ^= TRUE) {//overall mode
	 FetchButton->setText( tr("Fetch selected"));
      } else {
	 FetchButton->setText( tr("Fetch all"));
      }
   }
}

void PatchDialogImpl::tagListUpdated() {

   m_running = false;
   if (!globalStopAction) {
      m_firstTagListOnUpdate = TRUE;
      m_firstTagListOnEdit = FALSE;
      
      tagEdit->clear();
      tagEdit->insertItem("T: HEAD");
      if (m_fetchOverall) {
	 FetchButton->setText( tr("Fetch selected"));
	 readProjectNameList();
	 tagEdit->insertStringList(m_ProjectTagList);
      } else {
	 FetchButton->setText( tr("Fetch all"));
	 tagEdit->insertStringList(*m_pSelectedTagList);
      }
      setPixmaps(tagEdit);
      m_oldFirstTagText = tagEdit->currentText();
      m_firstTagListOnUpdate = FALSE;
      tagEdit->lineEdit()->selectAll();
   }
}

void PatchDialogImpl::setPixmaps(QComboBox * box) {
   int i;
   for (i = 0; i < box->count(); ++i) {
      QString txt = box->text(i);
      if (txt.startsWith("T: ")) box->changeItem(findEmbeddedPixmap("TagTag30x16"),txt.mid(3),i);
      else if (txt.startsWith("B: ")) box->changeItem(findEmbeddedPixmap("BranchTag30x16"),txt.mid(3),i);
   }
   m_oldFirstTagIdx = 0;
   
   if (i == 0) {
      box->insertItem(findEmbeddedPixmap("Tag30x16"),"",m_oldFirstTagIdx);
   }
   box->setCurrentItem(m_oldFirstTagIdx);//removes T: or B: for the lineEdit
}

void PatchDialogImpl::setSelectionFirstTag() {

   if (m_firstTagListOnUpdate) return;

   int start;
   int end;
   if (tagEdit->lineEdit()->getSelection(&start,&end)) {
      m_lastFirstTagLen = start;
   }
}

void PatchDialogImpl::activatedFirstTag(const QString & s) {

   if (m_pOldFirstTagPixmap) {
      tagEdit->changeItem(*m_pOldFirstTagPixmap,m_oldFirstTagText,m_oldFirstTagIdx);
      m_pOldFirstTagPixmap = NULL;
   }
   m_oldFirstTagText = s;
   m_oldFirstTagIdx = tagEdit->currentItem();
   m_firstTagListOnEdit = FALSE;
   tagEdit->lineEdit()->selectAll();
}

void PatchDialogImpl::textChangedFirstTag(const QString &txt) {

   if (m_firstTagListOnUpdate) return;

   int idx = -1;
   for (int i = 0; i<tagEdit->count(); ++i) {
      if (tagEdit->text(i).startsWith(txt)) {
	 idx = i;
	 break;
      }
   }

   if (idx > -1 && (m_lastFirstTagLen < (int)txt.length()) || (tagEdit->text(idx).length() == txt.length()) ) {

      m_firstTagListOnUpdate = TRUE;
      int cursorPos = tagEdit->lineEdit()->cursorPosition();
      tagEdit->setCurrentItem(idx);
      QString current = tagEdit->currentText();
      activatedFirstTag(current);
      if (current.length() != txt.length()) tagEdit->lineEdit()->setSelection(txt.length(),current.length()-txt.length());
      else tagEdit->lineEdit()->setCursorPosition(cursorPos);
      m_firstTagListOnUpdate = FALSE;
      
   } else if (m_oldFirstTagIdx == tagEdit->currentItem()) {
     
      if (!m_firstTagListOnEdit) {
	 m_firstTagListOnEdit = TRUE;
	 QPixmap const * p = tagEdit->pixmap(m_oldFirstTagIdx);
	 if (p) {
	    m_pOldFirstTagPixmap = new QPixmap(*p);
	 } else {
	    m_pOldFirstTagPixmap = NULL;
	 }
	 tagEdit->changeItem(findEmbeddedPixmap("Tag30x16"),m_oldFirstTagText,m_oldFirstTagIdx);
      }
  }

   m_lastFirstTagLen = txt.length();
}

QString PatchDialogImpl::getCvsCmd() {

   if (DetailGroup->isVisible()) {
      if (RevisionButton && RevisionButton->isChecked()) return "-r "+revisionEdit->text().stripWhiteSpace();
      else if (TagButton->isChecked()) return "-r "+tagEdit->currentText().stripWhiteSpace();
      else return "-D \""+dateTimeEdit->dateTime().toString("yyyy-MM-dd hh:mm")+"\"";
   }
   else return QString::null;
}

QString PatchDialogImpl::getPatchFile() {
   return m_patchFile;
}

void PatchDialogImpl::browseClicked() {

   QFileDialog* fd = new QFileDialog( this, "create patch", TRUE );
   fd->setCaption(tr("Enter a file name for the patch file"));
   fd->setMode( QFileDialog::AnyFile );
   fd->setDir(m_dirName);
   QString defaultName = m_file
      + "-diff-"
      + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss")
      + ".patch";
   fd->setSelection(defaultName);

   bool bAgain = true;
   while (bAgain) {
      if ( fd->exec() == QDialog::Accepted) m_patchFile = fd->selectedFile();
      else return;

      QFile f(m_patchFile);
      if (f.exists()) {
	 if (QMessageBox::warning (this, tr("Warning"), 
				   tr("The file: ") + m_patchFile 
				   + tr(" already exists.") + "\n" + tr("Overwrite it?"), 
				   tr("Yes"), tr("No")) != 0) return;
	 
	 if (!f.remove()) {
	   QString msg = tr("Could not remove ") + m_patchFile + ".\n";
	   msg += tr("Maybe you don't have the needed permissions.") + "\n\n";
	   msg += tr("Do you want to save the patch into another file?");
	   if (QMessageBox::warning (this, tr("Warning"), msg, tr("Yes"), tr("No")) != 0) return;
	   continue;
	 }
      }
  
      // try to create the file as check for permissions
      if (!f.open(IO_WriteOnly)) {
	 QString msg = tr("Could not create ") + m_patchFile + ".\n";
	 msg += tr("Maybe you don't have the needed permissions.") + "\n\n";
	 msg += tr("Do you want to save the patch into another file?");
	 if (QMessageBox::warning (this, tr("Warning"), msg, tr("Yes"), tr("No")) != 0) return;
      } else {
	 f.close();
	 bAgain = false;
      }
   }
   PatchFileEdit->setText(m_patchFile);
}

void PatchDialogImpl::fileNameChanged(const QString & f) {
   OkButton->setDisabled(f.isEmpty());
   m_patchFile = f;
}

void PatchDialogImpl::detailsToggled(bool state) {
   if (!state) {
      DetailGroup->hide();
   } else {
     DetailGroup->show();
   }
   qApp->processEvents();
   adjustSize();
}

void PatchDialogImpl::okClicked () {

   QFile f(m_patchFile);
   if (!f.open(IO_WriteOnly)) {
      QString msg = tr("Could not create ") + m_patchFile + ".\n";
      msg += tr("Maybe you don't have the needed permissions.");
      QMessageBox::warning (this, tr("Warning"), msg, tr("OK"));
      PatchFileEdit->setFocus();
   } else if (DetailGroup->isVisible()) {
      if (RevisionButton && RevisionButton->isChecked() && revisionEdit->text().isEmpty()) ValidatorWarning::RevisionWarning(this);
      else if (TagButton->isChecked() && tagEdit->currentText().isEmpty()) ValidatorWarning::TagWarning(this);
      else emit accept();
   } else emit accept();
}

void PatchDialogImpl::cancelClicked() {
   emit reject();
}

void PatchDialogImpl::closeEvent(QCloseEvent *) {
   emit reject();
}

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



syntax highlighted by Code2HTML, v. 0.9.1