/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile: CMakeSetupDialog.cxx,v $
  Language:  C++
  Date:      $Date: 2008-07-13 21:55:25 $
  Version:   $Revision: 1.40.2.7 $

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/

#include "CMakeSetupDialog.h"
#include <QFileDialog>
#include <QProgressBar>
#include <QMessageBox>
#include <QStatusBar>
#include <QToolButton>
#include <QDialogButtonBox>
#include <QCloseEvent>
#include <QCoreApplication>
#include <QSettings>
#include <QMenu>
#include <QMenuBar>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QUrl>
#include <QShortcut>
#include <QMacInstallDialog.h>

#include "QCMake.h"
#include "QCMakeCacheView.h"
#include "AddCacheEntry.h"
#include "CMakeFirstConfigure.h"

QCMakeThread::QCMakeThread(QObject* p) 
  : QThread(p), CMakeInstance(NULL)
{
}

QCMake* QCMakeThread::cmakeInstance() const
{
  return this->CMakeInstance;
}

void QCMakeThread::run()
{
  this->CMakeInstance = new QCMake;
  // emit that this cmake thread is ready for use
  emit this->cmakeInitialized();
  this->exec();
  delete this->CMakeInstance;
  this->CMakeInstance = NULL;
}

CMakeSetupDialog::CMakeSetupDialog()
  : ExitAfterGenerate(true), CacheModified(false), CurrentState(Interrupting)
{
  // create the GUI
  QSettings settings;
  settings.beginGroup("Settings/StartPath");
  int h = settings.value("Height", 500).toInt();
  int w = settings.value("Width", 700).toInt();
  this->resize(w, h);

  QWidget* cont = new QWidget(this);
  this->setupUi(cont);
  this->Splitter->setStretchFactor(0, 3);
  this->Splitter->setStretchFactor(1, 1);
  this->setCentralWidget(cont);
  this->ProgressBar->reset();
  this->RemoveEntry->setEnabled(false);
  this->AddEntry->setEnabled(false);

  bool groupView = settings.value("GroupView", false).toBool();
  if(groupView)
  {
    this->setViewType(2);
    this->ViewType->setCurrentIndex(2);
  }

  QMenu* FileMenu = this->menuBar()->addMenu(tr("&File"));
  this->ReloadCacheAction = FileMenu->addAction(tr("&Reload Cache"));
  QObject::connect(this->ReloadCacheAction, SIGNAL(triggered(bool)), 
                   this, SLOT(doReloadCache()));
  this->DeleteCacheAction = FileMenu->addAction(tr("&Delete Cache"));
  QObject::connect(this->DeleteCacheAction, SIGNAL(triggered(bool)), 
                   this, SLOT(doDeleteCache()));
  this->ExitAction = FileMenu->addAction(tr("E&xit"));
  QObject::connect(this->ExitAction, SIGNAL(triggered(bool)), 
                   this, SLOT(close()));

  QMenu* ToolsMenu = this->menuBar()->addMenu(tr("&Tools"));
  this->ConfigureAction = ToolsMenu->addAction(tr("&Configure"));
  // prevent merging with Preferences menu item on Mac OS X
  this->ConfigureAction->setMenuRole(QAction::NoRole);
  QObject::connect(this->ConfigureAction, SIGNAL(triggered(bool)), 
                   this, SLOT(doConfigure()));
  this->GenerateAction = ToolsMenu->addAction(tr("&Generate"));
  QObject::connect(this->GenerateAction, SIGNAL(triggered(bool)), 
                   this, SLOT(doGenerate()));
#if defined(Q_WS_MAC)
  this->InstallForCommandLineAction 
    = ToolsMenu->addAction(tr("&Install For Command Line Use"));
  QObject::connect(this->InstallForCommandLineAction, SIGNAL(triggered(bool)), 
                   this, SLOT(doInstallForCommandLine()));
#endif  
  QMenu* OptionsMenu = this->menuBar()->addMenu(tr("&Options"));
  this->SuppressDevWarningsAction = OptionsMenu->addAction(tr("&Suppress dev Warnings (-Wno-dev)"));
  this->SuppressDevWarningsAction->setCheckable(true);

  QAction* debugAction = OptionsMenu->addAction(tr("&Debug Output"));
  debugAction->setCheckable(true);
  QObject::connect(debugAction, SIGNAL(toggled(bool)), 
                   this, SLOT(setDebugOutput(bool)));
  
  OptionsMenu->addSeparator();
  QAction* expandAction = OptionsMenu->addAction(tr("&Expand Grouped Entries"));
  QObject::connect(expandAction, SIGNAL(triggered(bool)), 
                   this->CacheValues, SLOT(expandAll()));
  QAction* collapseAction = OptionsMenu->addAction(tr("&Collapse Grouped Entries"));
  QObject::connect(collapseAction, SIGNAL(triggered(bool)), 
                   this->CacheValues, SLOT(collapseAll()));

  QMenu* HelpMenu = this->menuBar()->addMenu(tr("&Help"));
  QAction* a = HelpMenu->addAction(tr("About"));
  QObject::connect(a, SIGNAL(triggered(bool)),
                   this, SLOT(doAbout()));
  a = HelpMenu->addAction(tr("Help"));
  QObject::connect(a, SIGNAL(triggered(bool)),
                   this, SLOT(doHelp()));
  
  QShortcut* filterShortcut = new QShortcut(QKeySequence::Find, this);
  QObject::connect(filterShortcut, SIGNAL(activated()), 
                   this, SLOT(startSearch()));
  
  this->setAcceptDrops(true);
  
  // get the saved binary directories
  QStringList buildPaths = this->loadBuildPaths();
  this->BinaryDirectory->addItems(buildPaths);
 
  this->BinaryDirectory->setCompleter(new QCMakeFileCompleter(this, true));
  this->SourceDirectory->setCompleter(new QCMakeFileCompleter(this, true));

  // fixed pitch font in output window
  QFont outputFont("Courier");
  this->Output->setFont(outputFont);
  this->ErrorFormat.setForeground(QBrush(Qt::red));

  // start the cmake worker thread
  this->CMakeThread = new QCMakeThread(this);
  QObject::connect(this->CMakeThread, SIGNAL(cmakeInitialized()),
                   this, SLOT(initialize()), Qt::QueuedConnection);  
  this->CMakeThread->start();
  
  this->enterState(ReadyConfigure);
}

void CMakeSetupDialog::initialize()
{
  // now the cmake worker thread is running, lets make our connections to it
  QObject::connect(this->CMakeThread->cmakeInstance(), 
      SIGNAL(propertiesChanged(const QCMakePropertyList&)),
      this->CacheValues->cacheModel(),
      SLOT(setProperties(const QCMakePropertyList&)));

  QObject::connect(this->ConfigureButton, SIGNAL(clicked(bool)),
                   this, SLOT(doConfigure()));
  QObject::connect(this->CMakeThread->cmakeInstance(), 
                   SIGNAL(configureDone(int)),
                   this, SLOT(finishConfigure(int)));
  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(generateDone(int)),
                   this, SLOT(finishGenerate(int)));

  QObject::connect(this->GenerateButton, SIGNAL(clicked(bool)),
                   this, SLOT(doGenerate()));
  
  QObject::connect(this->BrowseSourceDirectoryButton, SIGNAL(clicked(bool)),
                   this, SLOT(doSourceBrowse()));
  QObject::connect(this->BrowseBinaryDirectoryButton, SIGNAL(clicked(bool)),
                   this, SLOT(doBinaryBrowse()));
  
  QObject::connect(this->BinaryDirectory, SIGNAL(editTextChanged(QString)),
                   this, SLOT(onBinaryDirectoryChanged(QString)));
  QObject::connect(this->SourceDirectory, SIGNAL(textChanged(QString)),
                   this, SLOT(onSourceDirectoryChanged(QString)));

  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(sourceDirChanged(QString)),
                   this, SLOT(updateSourceDirectory(QString)));
  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(binaryDirChanged(QString)),
                   this, SLOT(updateBinaryDirectory(QString)));
  
  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(progressChanged(QString, float)),
                   this, SLOT(showProgress(QString,float)));
  
  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(errorMessage(QString)),
                   this, SLOT(error(QString)));

  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(outputMessage(QString)),
                   this, SLOT(message(QString)));

  QObject::connect(this->ViewType, SIGNAL(currentIndexChanged(int)), 
                   this, SLOT(setViewType(int)));
  QObject::connect(this->Search, SIGNAL(textChanged(QString)), 
                   this->CacheValues, SLOT(setSearchFilter(QString)));
  
  QObject::connect(this->CMakeThread->cmakeInstance(),
                   SIGNAL(generatorChanged(QString)),
                   this, SLOT(updateGeneratorLabel(QString)));
  this->updateGeneratorLabel(QString());
  
  QObject::connect(this->CacheValues->cacheModel(),
                   SIGNAL(dataChanged(QModelIndex,QModelIndex)), 
                   this, SLOT(setCacheModified()));
  
  QObject::connect(this->CacheValues->selectionModel(),
                   SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 
                   this, SLOT(selectionChanged()));
  QObject::connect(this->RemoveEntry, SIGNAL(clicked(bool)), 
                   this, SLOT(removeSelectedCacheEntries()));
  QObject::connect(this->AddEntry, SIGNAL(clicked(bool)), 
                   this, SLOT(addCacheEntry()));

  QObject::connect(this->SuppressDevWarningsAction, SIGNAL(triggered(bool)), 
                   this->CMakeThread->cmakeInstance(), SLOT(setSuppressDevWarnings(bool)));
  
  if(!this->SourceDirectory->text().isEmpty() ||
     !this->BinaryDirectory->lineEdit()->text().isEmpty())
    {
    this->onSourceDirectoryChanged(this->SourceDirectory->text());
    this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text());
    }
  else
    {
    this->onBinaryDirectoryChanged(this->BinaryDirectory->lineEdit()->text());
    }
}

CMakeSetupDialog::~CMakeSetupDialog()
{
  QSettings settings;
  settings.beginGroup("Settings/StartPath");
  settings.setValue("Height", this->height());
  settings.setValue("Width", this->width());

  // wait for thread to stop
  this->CMakeThread->quit();
  this->CMakeThread->wait(2000);
}
  
void CMakeSetupDialog::doConfigure()
{
  if(this->CurrentState == Configuring)
    {
    // stop configure
    doInterrupt();
    return;
    }

  // make sure build directory exists
  QString bindir = this->CMakeThread->cmakeInstance()->binaryDirectory();
  QDir dir(bindir);
  if(!dir.exists())
    {
    QString message = tr("Build directory does not exist, "
                         "should I create it?")
                      + "\n\n"
                      + tr("Directory: ");
    message += bindir;
    QString title = tr("Create Directory");
    QMessageBox::StandardButton btn;
    btn = QMessageBox::information(this, title, message, 
                                   QMessageBox::Yes | QMessageBox::No);
    if(btn == QMessageBox::No)
      {
      return;
      }
    dir.mkpath(".");
    }

  // if no generator, prompt for it and other setup stuff
  if(this->CMakeThread->cmakeInstance()->generator().isEmpty())
    {
    if(!this->setupFirstConfigure())
      {
      return;
      }
    }

  // remember path
  this->addBinaryPath(dir.absolutePath());
    
  this->enterState(Configuring);

  this->CacheValues->selectionModel()->clear();
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "setProperties", Qt::QueuedConnection, 
    Q_ARG(QCMakePropertyList,
      this->CacheValues->cacheModel()->properties()));
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "configure", Qt::QueuedConnection);
}

void CMakeSetupDialog::finishConfigure(int err)
{
  if(0 == err && !this->CacheValues->cacheModel()->newPropertyCount())
    {
    this->enterState(ReadyGenerate);
    }
  else
    {
    this->enterState(ReadyConfigure);
    this->CacheValues->scrollToTop();
    }
  
  if(err != 0)
    {
    QMessageBox::critical(this, tr("Error"), 
      tr("Error in configuration process, project files may be invalid"), 
      QMessageBox::Ok);
    }
}

void CMakeSetupDialog::finishGenerate(int err)
{
  this->enterState(ReadyGenerate);
  if(err != 0)
    {
    QMessageBox::critical(this, tr("Error"), 
      tr("Error in generation process, project files may be invalid"),
      QMessageBox::Ok);
    }
}

void CMakeSetupDialog::doInstallForCommandLine()
{
  QMacInstallDialog setupdialog(0);
  setupdialog.exec();
}

void CMakeSetupDialog::doGenerate()
{
  if(this->CurrentState == Generating)
    {
    // stop generate
    doInterrupt();
    return;
    }
  this->enterState(Generating);
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "generate", Qt::QueuedConnection);
}
  
void CMakeSetupDialog::closeEvent(QCloseEvent* e)
{
  // prompt for close if there are unsaved changes, and we're not busy
  if(this->CacheModified)
    {
    QString message = tr("You have changed options but not rebuilt, "
                    "are you sure you want to exit?");
    QString title = tr("Confirm Exit");
    QMessageBox::StandardButton btn;
    btn = QMessageBox::critical(this, title, message,
                                QMessageBox::Yes | QMessageBox::No);
    if(btn == QMessageBox::No)
      {
      e->ignore();
      }
    }

  // don't close if we're busy, unless the user really wants to
  if(this->CurrentState == Configuring)
    {
    QString message = "You are in the middle of a Configure.\n"
                   "If you Exit now the configure information will be lost.\n"
                   "Are you sure you want to Exit?";
    QString title = tr("Confirm Exit");
    QMessageBox::StandardButton btn;
    btn = QMessageBox::critical(this, title, message,
                                QMessageBox::Yes | QMessageBox::No);
    if(btn == QMessageBox::No)
      {
      e->ignore();
      }
    else
      {
      this->doInterrupt();
      }
    }

  // let the generate finish
  if(this->CurrentState == Generating)
    {
    e->ignore();
    }
}

void CMakeSetupDialog::doHelp()
{
  QString msg = tr("CMake is used to configure and generate build files for "
    "software projects.   The basic steps for configuring a project are as "
    "follows:\r\n\r\n1. Select the source directory for the project.  This should "
    "contain the CMakeLists.txt files for the project.\r\n\r\n2. Select the build "
    "directory for the project.   This is the directory where the project will be "
    "built.  It can be the same or a different directory than the source "
    "directory.   For easy clean up, a separate build directory is recommended. "
    "CMake will create the directory if it does not exist.\r\n\r\n3. Once the "
    "source and binary directories are selected, it is time to press the "
    "Configure button.  This will cause CMake to read all of the input files and "
    "discover all the variables used by the project.   The first time a variable "
    "is displayed it will be in Red.   Users should inspect red variables making "
    "sure the values are correct.   For some projects the Configure process can "
    "be iterative, so continue to press the Configure button until there are no "
    "longer red entries.\r\n\r\n4. Once there are no longer red entries, you "
    "should click the Generate button.  This will write the build files to the build "
    "directory.");

  QDialog dialog;
  QFontMetrics met(this->font());
  int msgWidth = met.width(msg);
  dialog.setMinimumSize(msgWidth/15,20);
  dialog.setWindowTitle(tr("Help"));
  QVBoxLayout* l = new QVBoxLayout(&dialog);
  QLabel* lab = new QLabel(&dialog);
  lab->setText(msg);
  lab->setWordWrap(true);
  QDialogButtonBox* btns = new QDialogButtonBox(QDialogButtonBox::Ok,
                                                Qt::Horizontal, &dialog);
  QObject::connect(btns, SIGNAL(accepted()), &dialog, SLOT(accept()));
  l->addWidget(lab);
  l->addWidget(btns);
  dialog.exec();
}

void CMakeSetupDialog::doInterrupt()
{
  this->enterState(Interrupting);
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "interrupt", Qt::QueuedConnection);
}

void CMakeSetupDialog::doSourceBrowse()
{
  QString dir = QFileDialog::getExistingDirectory(this, 
    tr("Enter Path to Source"), this->SourceDirectory->text());
  if(!dir.isEmpty())
    {
    this->setSourceDirectory(dir);
    }
}

void CMakeSetupDialog::updateSourceDirectory(const QString& dir)
{
  if(this->SourceDirectory->text() != dir)
    {
    this->SourceDirectory->blockSignals(true);
    this->SourceDirectory->setText(dir);
    this->SourceDirectory->blockSignals(false);
    }
}

void CMakeSetupDialog::updateBinaryDirectory(const QString& dir)
{
  if(this->BinaryDirectory->currentText() != dir)
    {
    this->BinaryDirectory->blockSignals(true);
    this->BinaryDirectory->setEditText(dir);
    this->BinaryDirectory->blockSignals(false);
    }
}

void CMakeSetupDialog::doBinaryBrowse()
{
  QString dir = QFileDialog::getExistingDirectory(this, 
    tr("Enter Path to Build"), this->BinaryDirectory->currentText());
  if(!dir.isEmpty() && dir != this->BinaryDirectory->currentText())
    {
    this->setBinaryDirectory(dir);
    }
}

void CMakeSetupDialog::setBinaryDirectory(const QString& dir)
{
  this->BinaryDirectory->setEditText(dir);
}

void CMakeSetupDialog::onSourceDirectoryChanged(const QString& dir)
{
  this->Output->clear();
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "setSourceDirectory", Qt::QueuedConnection, Q_ARG(QString, dir));
}

void CMakeSetupDialog::onBinaryDirectoryChanged(const QString& dir)
{
  this->CacheModified = false;
  this->CacheValues->cacheModel()->clear();
  this->Output->clear();
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "setBinaryDirectory", Qt::QueuedConnection, Q_ARG(QString, dir));
}

void CMakeSetupDialog::setSourceDirectory(const QString& dir)
{
  this->SourceDirectory->setText(dir);
}

void CMakeSetupDialog::showProgress(const QString& /*msg*/, float percent)
{
  this->ProgressBar->setValue(qRound(percent * 100));
}

void CMakeSetupDialog::error(const QString& message)
{
  this->Output->setCurrentCharFormat(this->ErrorFormat);
  this->Output->append(message);
}

void CMakeSetupDialog::message(const QString& message)
{
  this->Output->setCurrentCharFormat(this->MessageFormat);
  this->Output->append(message);
}

void CMakeSetupDialog::setEnabledState(bool enabled)
{
  // disable parts of the GUI during configure/generate
  this->CacheValues->cacheModel()->setEditEnabled(enabled);
  this->SourceDirectory->setEnabled(enabled);
  this->BrowseSourceDirectoryButton->setEnabled(enabled);
  this->BinaryDirectory->setEnabled(enabled);
  this->BrowseBinaryDirectoryButton->setEnabled(enabled);
  this->ReloadCacheAction->setEnabled(enabled);
  this->DeleteCacheAction->setEnabled(enabled);
  this->ExitAction->setEnabled(enabled);
  this->ConfigureAction->setEnabled(enabled);
  this->AddEntry->setEnabled(enabled);
  this->RemoveEntry->setEnabled(false);  // let selection re-enable it
}

bool CMakeSetupDialog::setupFirstConfigure()
{
  CMakeFirstConfigure dialog;

  // initialize dialog and restore saved settings

  // add generators
  dialog.setGenerators(this->CMakeThread->cmakeInstance()->availableGenerators());

  // restore from settings
  dialog.loadFromSettings();
  
  if(dialog.exec() == QDialog::Accepted)
    {
    dialog.saveToSettings();
    this->CMakeThread->cmakeInstance()->setGenerator(dialog.getGenerator());
    
    QCMakeCacheModel* m = this->CacheValues->cacheModel();

    if(dialog.compilerSetup())
      {
      QString fortranCompiler = dialog.getFortranCompiler();
      if(!fortranCompiler.isEmpty())
        {
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER", 
                          "Fortran compiler.", fortranCompiler, false);
        }
      QString cxxCompiler = dialog.getCXXCompiler();
      if(!cxxCompiler.isEmpty())
        {
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER", 
                          "CXX compiler.", cxxCompiler, false);
        }
      
      QString cCompiler = dialog.getCCompiler();
      if(!cCompiler.isEmpty())
        {
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER", 
                          "C compiler.", cCompiler, false);
        }
      }
    else if(dialog.crossCompilerSetup())
      {
      QString toolchainFile = dialog.crossCompilerToolChainFile();
      if(!toolchainFile.isEmpty())
        {
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_TOOLCHAIN_FILE", 
                          "Cross Compile ToolChain File", toolchainFile, false);
        }
      else
        {
        QString fortranCompiler = dialog.getFortranCompiler();
        if(!fortranCompiler.isEmpty())
          {
          m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_Fortran_COMPILER", 
                            "Fortran compiler.", fortranCompiler, false);
          }

        QString mode = dialog.getCrossIncludeMode();
        m->insertProperty(QCMakeProperty::STRING, "CMAKE_FIND_ROOT_PATH_MODE_INCLUDE", 
                          "CMake Find Include Mode", mode, false);
        mode = dialog.getCrossLibraryMode();
        m->insertProperty(QCMakeProperty::STRING, "CMAKE_FIND_ROOT_PATH_MODE_LIBRARY", 
                          "CMake Find Library Mode", mode, false);
        mode = dialog.getCrossProgramMode();
        m->insertProperty(QCMakeProperty::STRING, "CMAKE_FIND_ROOT_PATH_MODE_PROGRAM", 
                          "CMake Find Program Mode", mode, false);
        
        QString rootPath = dialog.getCrossRoot();
        m->insertProperty(QCMakeProperty::PATH, "CMAKE_FIND_ROOT_PATH", 
                          "CMake Find Root Path", rootPath, false);

        QString systemName = dialog.getSystemName();
        m->insertProperty(QCMakeProperty::STRING, "CMAKE_SYSTEM_NAME", 
                          "CMake System Name", systemName, false);
        QString cxxCompiler = dialog.getCXXCompiler();
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_CXX_COMPILER", 
                          "CXX compiler.", cxxCompiler, false);
        QString cCompiler = dialog.getCCompiler();
        m->insertProperty(QCMakeProperty::FILEPATH, "CMAKE_C_COMPILER", 
                          "C compiler.", cCompiler, false);
        }
      }
    return true;
    }

  return false;
}

void CMakeSetupDialog::updateGeneratorLabel(const QString& gen)
{
  QString str = tr("Current Generator: ");
  if(gen.isEmpty())
    {
    str += tr("None");
    }
  else
    {
    str += gen;
    }
  this->Generator->setText(str);
}

void CMakeSetupDialog::doReloadCache()
{
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "reloadCache", Qt::QueuedConnection);
}

void CMakeSetupDialog::doDeleteCache()
{   
  QString title = tr("Delete Cache");
  QString message = "Are you sure you want to delete the cache?";
  QMessageBox::StandardButton btn;
  btn = QMessageBox::information(this, title, message, 
                                 QMessageBox::Yes | QMessageBox::No);
  if(btn == QMessageBox::No)
    {
    return;
    }
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
                            "deleteCache", Qt::QueuedConnection);
}

void CMakeSetupDialog::doAbout()
{
  QString msg = "CMake\nwww.cmake.org";

  QDialog dialog;
  dialog.setWindowTitle(tr("About"));
  QVBoxLayout* l = new QVBoxLayout(&dialog);
  QLabel* lab = new QLabel(&dialog);
  l->addWidget(lab);
  lab->setText(msg);
  lab->setWordWrap(true);
  QDialogButtonBox* btns = new QDialogButtonBox(QDialogButtonBox::Ok,
                                                Qt::Horizontal, &dialog);
  QObject::connect(btns, SIGNAL(accepted()), &dialog, SLOT(accept()));
  l->addWidget(btns);
  dialog.exec();
}

void CMakeSetupDialog::setExitAfterGenerate(bool b)
{
  this->ExitAfterGenerate = b;
}

void CMakeSetupDialog::addBinaryPath(const QString& path)
{
  QString cleanpath = QDir::cleanPath(path);
  
  // update UI
  this->BinaryDirectory->blockSignals(true);
  int idx = this->BinaryDirectory->findText(cleanpath);
  if(idx != -1)
    {
    this->BinaryDirectory->removeItem(idx);
    }
  this->BinaryDirectory->insertItem(0, cleanpath);
  this->BinaryDirectory->setCurrentIndex(0);
  this->BinaryDirectory->blockSignals(false);
  
  // save to registry
  QStringList buildPaths = this->loadBuildPaths();
  buildPaths.removeAll(cleanpath);
  buildPaths.prepend(cleanpath);
  this->saveBuildPaths(buildPaths);
}

void CMakeSetupDialog::dragEnterEvent(QDragEnterEvent* e)
{
  if(!(this->CurrentState == ReadyConfigure || 
     this->CurrentState == ReadyGenerate))
    {
    e->ignore();
    return;
    }

  const QMimeData* dat = e->mimeData();
  QList<QUrl> urls = dat->urls();
  QString file = urls.count() ? urls[0].toLocalFile() : QString();
  if(!file.isEmpty() && 
    (file.endsWith("CMakeCache.txt", Qt::CaseInsensitive) ||
    file.endsWith("CMakeLists.txt", Qt::CaseInsensitive) ) )
    {
    e->accept();
    }
  else
    {
    e->ignore();
    }
}

void CMakeSetupDialog::dropEvent(QDropEvent* e)
{
  if(!(this->CurrentState == ReadyConfigure || 
     this->CurrentState == ReadyGenerate))
    {
    return;
    }

  const QMimeData* dat = e->mimeData();
  QList<QUrl> urls = dat->urls();
  QString file = urls.count() ? urls[0].toLocalFile() : QString();
  if(file.endsWith("CMakeCache.txt", Qt::CaseInsensitive))
    {
    QFileInfo info(file);
    if(this->CMakeThread->cmakeInstance()->binaryDirectory() != info.absolutePath())
      {
      this->setBinaryDirectory(info.absolutePath());
      }
    }
  else if(file.endsWith("CMakeLists.txt", Qt::CaseInsensitive))
    {
    QFileInfo info(file);
    if(this->CMakeThread->cmakeInstance()->binaryDirectory() != info.absolutePath())
      {
      this->setSourceDirectory(info.absolutePath());
      this->setBinaryDirectory(info.absolutePath());
      }
    }
}

QStringList CMakeSetupDialog::loadBuildPaths()
{
  QSettings settings;
  settings.beginGroup("Settings/StartPath");

  QStringList buildPaths;
  for(int i=0; i<10; i++)
    { 
      QString p = settings.value(QString("WhereBuild%1").arg(i)).toString();
      if(!p.isEmpty())
        {
        buildPaths.append(p);
        }
    }
  return buildPaths;
}

void CMakeSetupDialog::saveBuildPaths(const QStringList& paths)
{
  QSettings settings;
  settings.beginGroup("Settings/StartPath");

  int num = paths.count();
  if(num > 10)
    {
    num = 10;
    }

  for(int i=0; i<num; i++)
    { 
    settings.setValue(QString("WhereBuild%1").arg(i), paths[i]);
    }
}
  
void CMakeSetupDialog::setCacheModified()
{
  this->CacheModified = true;
  this->enterState(ReadyConfigure);
}

void CMakeSetupDialog::removeSelectedCacheEntries()
{
  QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows();
  QList<QPersistentModelIndex> pidxs;
  foreach(QModelIndex i, idxs)
    {
    pidxs.append(i);
    }
  foreach(QPersistentModelIndex pi, pidxs)
    {
    this->CacheValues->model()->removeRow(pi.row(), pi.parent());
    }
}

void CMakeSetupDialog::selectionChanged()
{
  QModelIndexList idxs = this->CacheValues->selectionModel()->selectedRows();
  if(idxs.count() && 
      (this->CurrentState == ReadyConfigure || 
       this->CurrentState == ReadyGenerate) )
    {
    this->RemoveEntry->setEnabled(true);
    }
  else
    {
    this->RemoveEntry->setEnabled(false);
    }
}
  
void CMakeSetupDialog::enterState(CMakeSetupDialog::State s)
{
  if(s == this->CurrentState)
    {
    return;
    }

  this->CurrentState = s;

  if(s == Interrupting)
    {
    this->ConfigureButton->setEnabled(false);
    this->GenerateButton->setEnabled(false);
    }
  else if(s == Configuring)
    {
    this->Output->clear();
    this->setEnabledState(false);
    this->GenerateButton->setEnabled(false);
    this->GenerateAction->setEnabled(false);
    this->ConfigureButton->setText(tr("&Stop"));
    }
  else if(s == Generating)
    {
    this->CacheModified = false;
    this->setEnabledState(false);
    this->ConfigureButton->setEnabled(false);
    this->GenerateAction->setEnabled(false);
    this->GenerateButton->setText(tr("&Stop"));
    }
  else if(s == ReadyConfigure)
    {
    this->ProgressBar->reset();
    this->setEnabledState(true);
    this->GenerateButton->setEnabled(false);
    this->GenerateAction->setEnabled(false);
    this->ConfigureButton->setEnabled(true);
    this->ConfigureButton->setText(tr("&Configure"));
    this->GenerateButton->setText(tr("&Generate"));
    }
  else if(s == ReadyGenerate)
    {
    this->ProgressBar->reset();
    this->setEnabledState(true);
    this->GenerateButton->setEnabled(true);
    this->GenerateAction->setEnabled(true);
    this->ConfigureButton->setEnabled(true);
    this->ConfigureButton->setText(tr("&Configure"));
    this->GenerateButton->setText(tr("&Generate"));
    }
}

void CMakeSetupDialog::addCacheEntry()
{
  QDialog dialog(this);
  dialog.resize(400, 200);
  dialog.setWindowTitle(tr("Add Cache Entry"));
  QVBoxLayout* l = new QVBoxLayout(&dialog);
  AddCacheEntry* w = new AddCacheEntry(&dialog);
  QDialogButtonBox* btns = new QDialogButtonBox(
      QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
      Qt::Horizontal, &dialog);
  QObject::connect(btns, SIGNAL(accepted()), &dialog, SLOT(accept()));
  QObject::connect(btns, SIGNAL(rejected()), &dialog, SLOT(reject()));
  l->addWidget(w);
  l->addStretch();
  l->addWidget(btns);
  if(QDialog::Accepted == dialog.exec())
    {
    QCMakeCacheModel* m = this->CacheValues->cacheModel();
    m->insertProperty(w->type(), w->name(), w->description(), w->value(), false);
    }
}

void CMakeSetupDialog::startSearch()
{
  this->Search->setFocus(Qt::OtherFocusReason);
  this->Search->selectAll();
}

void CMakeSetupDialog::setDebugOutput(bool flag)
{
  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(),
    "setDebugOutput", Qt::QueuedConnection, Q_ARG(bool, flag));
}

void CMakeSetupDialog::setViewType(int v)
{
  if(v == 0)  // simple view
    {
    this->CacheValues->cacheModel()->setViewType(QCMakeCacheModel::FlatView);
    this->CacheValues->setRootIsDecorated(false);
    this->CacheValues->setShowAdvanced(false);
    }
  else if(v == 1)  // advanced view
    {
    this->CacheValues->cacheModel()->setViewType(QCMakeCacheModel::FlatView);
    this->CacheValues->setRootIsDecorated(false);
    this->CacheValues->setShowAdvanced(true);
    }
  else if(v == 2)  // grouped view
    {
    this->CacheValues->cacheModel()->setViewType(QCMakeCacheModel::GroupView);
    this->CacheValues->setRootIsDecorated(true);
    this->CacheValues->setShowAdvanced(true);
    }
  
  QSettings settings;
  settings.beginGroup("Settings/StartPath");
  settings.setValue("GroupView", v == 2);
  
}

