/***************************************************************************
**
**  This file is part of geopsy.
**
**  geopsy 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, either version 3 of the License, or
**  (at your option) any later version.
**
**  geopsy 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 for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-12-01
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsySLinkGui.h>

#include "Engine.h"
#include "ExportFileType.h"
#include "ExportPick.h"
#include "FileView.h"
#include "geopsyVersion.h"
#include "GroupProperties.h"
#include "GroupWidget.h"
#include "SeismicEventWidget.h"
#include "LoadFilePattern.h"
#include "MainWindow.h"
#include "Preferences.h"
#include "SignalDisplay.h"
#include "SignalTableItem.h"
#include "SigSelectionDnD.h"
#include "SortKeys.h"
#include "SourceCoordinates.h"
#include "StationCoordinates.h"
#include "SubtractSignals.h"
#include "ToolFactory.h"
#include "ViewParamEditor.h"
#include "WaveformConsole.h"
#include "TableWindow.h"
#include "GraphicWindow.h"

MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags f)
  : MultiDocumentWindow(parent, f)
{
  TRACE;

  resize(QSize(674, 523).expandedTo(minimumSizeHint()));
  Settings::getRect(this, "MainWindow");
  setWindowIcon(QIcon(":logo-16x16.png"));
  // Window title automatically set by showDBStatus()

  _dbStatusChecksum=0;
  _dbStatusTimer.setInterval(1000);
  connect (&_dbStatusTimer, SIGNAL(timeout()), this, SLOT(showDBStatus()));
  _dbStatusTimer.start();
  _db=new SignalDatabase;

  connect (this, SIGNAL(asyncSetProgressMaximum(int)), this, SLOT(setProgressMaximum(int)), Qt::QueuedConnection);
  connect (this, SIGNAL(asyncSetProgressValue(int)), this, SLOT(setProgressValue(int)), Qt::QueuedConnection);
  connect (this, SIGNAL(asyncIncreaseProgressValue(int)), this, SLOT(increaseProgressValue(int)), Qt::QueuedConnection);
  connect (this, SIGNAL(asyncShowMessage(QString)), this, SLOT(showMessage( QString) ), Qt::QueuedConnection);

  QDockWidget * gDock=new DockWidget(this);
  gDock->setObjectName("Groups");
  gDock->setWindowTitle(tr("Groups"));
  gDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
  gDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
  _groupList=new GroupWidget(gDock);
  gDock->setWidget(_groupList);
  addDockWidget(Qt::LeftDockWidgetArea, gDock);

  QDockWidget * fDock=new DockWidget(this);
  fDock->setObjectName("Files");
  fDock->setWindowTitle(tr("Files"));
  fDock->setFeatures (QDockWidget::AllDockWidgetFeatures);
  fDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
  _fileList=new FileView(fDock);
  fDock->setWidget(_fileList);
  addDockWidget(Qt::LeftDockWidgetArea, fDock);
  tabifyDockWidget(gDock, fDock);

  QDockWidget * eDock=new DockWidget(this);
  eDock->setObjectName("Events");
  eDock->setWindowTitle(tr("Events"));
  eDock->setFeatures (QDockWidget::AllDockWidgetFeatures);
  eDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
  _eventList=new SeismicEventWidget(eDock);
  eDock->setWidget(_eventList);
  addDockWidget(Qt::LeftDockWidgetArea, eDock);
  tabifyDockWidget(fDock, eDock);

  QDockWidget * cDock=new DockWidget(this);
  cDock->setObjectName("WaveformConsole");
  cDock->setWindowTitle(tr("Waveform console"));
  cDock->setFeatures (QDockWidget::AllDockWidgetFeatures);
  cDock->setAllowedAreas(Qt::AllDockWidgetAreas);
  _waveformConsole=new WaveformConsole (cDock);
  cDock->setWidget(_waveformConsole);
  addDockWidget(Qt::LeftDockWidgetArea, cDock);

  QDockWidget * logDock=new QDockWidget(this);
  logDock->setObjectName("Log");
  logDock->setWindowTitle(tr("Log"));
  logDock->setFeatures (QDockWidget::AllDockWidgetFeatures);
  logDock->setAllowedAreas(Qt::AllDockWidgetAreas);
  LogWidget * logs=new LogWidget(logDock);
  _log=logs->addView(QThread::currentThread(), tr("Messages"));
  logDock->setWidget(logs);
  addDockWidget(Qt::LeftDockWidgetArea, logDock);
  tabifyDockWidget(cDock, logDock);

  databaseInfo=new QLabel(statusBar());
  statusBar()->addPermanentWidget(databaseInfo, 0);

  UpdateIcon * liveUpdate=new UpdateIcon(statusBar());
  liveUpdate->setName("geopsy");
  liveUpdate->setVersion(GEOPSY_VERSION);
  liveUpdate->setVersionType(GEOPSY_VERSION_TYPE);
  {
    GeopsyPluginSettings reg;
    liveUpdate->addPlugins(reg.pluginList());
    statusBar()->addPermanentWidget(liveUpdate, 0);
  }

  _progressBar=new ProgressBar(statusBar());
  _progressBar->setMinimum(0);
  _progressBar->setMaximum(100);
  _progressBar->setMaximumWidth(100);
  _progressBar->setMaximumHeight(20);
  _progressBar->setVisible(false);
  connect(&_progressHideTimer, SIGNAL(timeout()), this, SLOT(hideProgressBar()));
  _progressHideTimer.setInterval(2000);
  _progressHideTimer.setSingleShot(true);
  statusBar()->addPermanentWidget(_progressBar, 0);
  statusBar()->setSizeGripEnabled(true);
  statusBar()->showMessage(tr("Ready"), 2000);

  addActions();

  _subPoolWin=nullptr;
  _tableEditionLocked=true;
  connect(this, SIGNAL(activeWindowChanged(MultiDocumentSubWindow *)),
          this, SLOT(activeWindowChanged(MultiDocumentSubWindow *)));
  activeWindowChanged(nullptr);
  _seedLink=nullptr;
  _directoryMonitor=nullptr;

  // The ToolButton are added automatically by actions
  // Try to get a pointer to them and add the DnD event filter
  QToolButton * viewGraphicToolBut=nullptr,
              * viewTableToolBut=nullptr,
              * viewMapToolBut=nullptr,
              * viewChronogramToolBut=nullptr;
  QList<QToolButton *> toolButList=findChildren<QToolButton *>();
  for(QList<QToolButton *>::iterator it=toolButList.begin(); it!=toolButList.end(); ++it) {
    if(( *it) ->defaultAction()==_viewGraphicAction)
      viewGraphicToolBut=*it;
    else if((*it)->defaultAction()==_viewTableAction)
      viewTableToolBut=* it;
    else if((*it)->defaultAction()==_viewMapAction)
      viewMapToolBut=* it;
    else if((*it)->defaultAction()==_viewChronogramAction)
      viewChronogramToolBut=*it;
  }
  ASSERT(viewGraphicToolBut!=nullptr &&
         viewTableToolBut!=nullptr &&
         viewMapToolBut!=nullptr);
  if(viewGraphicToolBut &&
     viewTableToolBut &&
     viewMapToolBut &&
     viewChronogramToolBut) {
    SigSelectionDnD * sigDnD=new SigSelectionDnD(viewGraphicToolBut);
    sigDnD->setDragEnabled(false);
    connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)),
             Engine::instance(), SLOT(newGraphicWindow(QWidget *, const SubSignalPool&)));
    sigDnD=new SigSelectionDnD(viewTableToolBut);
    sigDnD->setDragEnabled(false);
    connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)),
             Engine::instance(), SLOT(newTableWindow(QWidget *, const SubSignalPool&)));
    sigDnD=new SigSelectionDnD(viewMapToolBut);
    sigDnD->setDragEnabled(false);
    connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)),
             Engine::instance(), SLOT(newMapWindow(QWidget *, const SubSignalPool&)));
    sigDnD=new SigSelectionDnD(viewChronogramToolBut);
    sigDnD->setDragEnabled(false);
    connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)),
             Engine::instance(), SLOT(newChronogramWindow(QWidget *, const SubSignalPool&)));
  }

  QSettings& reg=CoreApplication::instance()->settings();
  if(!restoreState(reg.value("Workspace").toByteArray(), 0)) {
    App::log(tr("Cannot restore main window state\n") );
  }

  // Add at least one tab (after windows menu is created)
  addTab();

  setAcceptDrops(true);

  _toolFactory=new ToolFactory(this);
  _toolFactory->createToolActions();
  _toolFactory->addActions(_toolsMenu);
  // Get list of tool buttons before actions of tools
  QList<QToolButton *> toolButListBefore=findChildren<QToolButton *>();
  _toolFactory->addActions(_toolsBar);
  // Get list of tool buttons after actions of tools
  QList<QToolButton *> toolButListAfter=findChildren<QToolButton *>();
  for(QList<QToolButton *>::iterator it=toolButListAfter.begin(); it!=toolButListAfter.end(); ++it) {
    if(!toolButListBefore.contains(*it)) {
      SigSelectionDnD * sigDnD=new SigSelectionDnD(*it);
      sigDnD->setDragEnabled(false);
      connect(sigDnD, SIGNAL(selectionDropped(QWidget *, const SubSignalPool&)),
              _toolFactory, SLOT(newGraphicWindow(QWidget *, const SubSignalPool&)));
    }
  }
  // Add eventually actions defined in user plugins for importing signals
  QList<QAction *> customActions=_toolFactory->createImportActions(this);
  foreach(QAction * a, customActions) {
    _importSignalMenu->addAction(a);
  }
}

MainWindow::~MainWindow()
{
  TRACE;
  delete _db;
  QSettings& reg=CoreApplication::instance()->settings();
  reg.setValue("Workspace", saveState(0));
  Settings::setRect(this, "MainWindow");
  delete _toolFactory;
}

void MainWindow::startupPreferences()
{
  TRACE;
  // Ask for preferences if specified in QSettings
  QSettings& reg=CoreApplication::instance()->settings();
  reg.beginGroup("DialogOptions");
  reg.beginGroup("Preferences");
  if(reg.value("again", true).toBool()) {
    reg.setValue("again", true);
    setPreferences();
  }
}

void MainWindow::showDBStatus()
{
  TRACE;
  Cache& cache=*GeopsyCoreEngine::instance()->cache();

  uint checksum=static_cast<uint>(_db->count());
  checksum+=static_cast<uint>(_db->filePool().count());
  checksum+=static_cast<uint>(cache.freeMegaBytes()*1e6);
  checksum+=qHash(_db->name());
  if(checksum!=_dbStatusChecksum) {
    databaseInfo->setText(tr("%1 signals, %2 files, free cache %3 Mb")
                           .arg(_db->count())
                           .arg(_db->filePool().count())
                           .arg(cache.freeMegaBytes()));
    if(_db->name().isEmpty()) {
      setWindowTitle("Geopsy");
    } else {
      setWindowTitle(_db->name()+" - Geopsy");
    }
    _dbStatusChecksum=checksum;
  }
}

void MainWindow::saveDBAs()
{
  TRACE;
  QString fileName=SignalDatabase::askSaveAs();
  if(fileName.isEmpty()) {
    return;
  }
  _db->saveAs(fileName);
  WindowEnvironment::instance()->addRecentDocument(_db->name());
  update();
}

void MainWindow::openDB(QString fileName)
{
  TRACE;
  if(!_db->isEmpty() && _db->isModified()) {
    switch(Message::warning(MSG_ID, tr("Opening Database ..."),
                             tr("You are about to merge the current dababase with another one. You may loose data during this operation. "
                                "Do you want to save modifications before proceeding?"),
                             Message::yes(), Message::no(), Message::cancel())) {
    case Message::Answer0:
      _db->save();
      break;
    case Message::Answer1:
      break;
    default:
      return;
    }
  }
  if(fileName.isEmpty()) {
    fileName=Message::getOpenFileName(tr("Open a database"),  tr("Geopsy database (*.gpy *.sdb)"));
  }
  if(fileName.isEmpty()) {
    return;
  }
  GeopsyGuiEngine::instance()->beginSignalChange(_db);
  if(_db->open(fileName)) {
    if(_db->isModified()) { // Due to PathTranslator
      /*if(Message::warning(MSG_ID, tr("Opening Database ..."),
                         tr("Some file paths were modified while opening the database. Do you want to save these new paths?"),
                         Message::yes(), Message::no(), true)==Message::Answer0) {
       _db->save();
      }*/
    }
  } else {
    Message::warning(MSG_ID, tr("Opening Database ..."),
                     tr("Errors occured while opening database, for more details read log."),
                     Message::ok());
    // Set name as empty to force saveAs()
    _db->setName(QString());
  }
  GeopsyGuiEngine::instance()->endSignalChange(_db);
  WindowEnvironment::instance()->addRecentDocument(_db->name());
  _groupList->expandAll();
  update();
}

void MainWindow::saveDB ()
{
  TRACE;
  if(_db->exists()) {
    GeopsyGuiEngine::instance()->beginSignalChange(_db);
    _db->save();
    GeopsyGuiEngine::instance()->endSignalChange(_db);
  } else {
    saveDBAs();
  }
}

void MainWindow::setPreferences()
{
  TRACE;
  Preferences * d=new Preferences(this);
  _toolFactory->createPreferenceTabs(d->preferenceTab);
  Settings::getWidget(d);
  d->paramEditor->initTableFields(GeopsyGuiEngine::instance()->preferences()->tableFields());
  d->updateAll();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    d->saveToolList();
    d->paramEditor->getTableFields(GeopsyGuiEngine::instance()->preferences()->tableFields());
    GeopsyGuiEngine::instance()->preferences()->saveNoAuto();
    GeopsyGuiEngine::instance()->preferences()->load();
    _toolFactory->setPreferences();
  }
  delete d;
}

void MainWindow::load()
{
  TRACE;
  MessageContext mc;
  _db->load(QStringList(), SignalFileFormat::Unknown, true);
}

void MainWindow::loadFilePattern()
{
  TRACE;
  LoadFilePattern * d=new LoadFilePattern;
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    d->saveHistory();
    MessageContext mc;
    QDir baseDir=d->baseDirectory();
    _db->load(baseDir.absoluteFilePath(d->filePattern()), SignalFileFormat::Unknown, true);
  }
  delete d;
}

void MainWindow::directoryMonitor()
{
  TRACE;
  MessageContext mc;
  if(!_directoryMonitor) {
    _directoryMonitor=new DirectoryMonitor();
    connect(_directoryMonitor, SIGNAL(destroyed()), this, SLOT(directoryMonitorDestroyed()));
    addSubWindow(_directoryMonitor);
  }
}

void MainWindow::directoryMonitorDestroyed()
{
  TRACE;
  _directoryMonitor=nullptr;
}

void MainWindow::seedlink(QByteArray serverAddress, qint16 serverPort, QString streamSelection)
{
  TRACE;
  if(!_seedLink) {
    _seedLink=new SeedLinkLoader;
    connect(_seedLink, SIGNAL(destroyed()), this, SLOT(seedlinkDestroyed()));
    _seedLink->setCurrentDatabase(_db);
    addSubWindow(_seedLink);
    if(!serverAddress.isEmpty() && serverPort!=0) {
      _seedLink->setServer(serverAddress, serverPort);
      if(!streamSelection.isEmpty()) {
        _seedLink->selectStreams(streamSelection);
      }
    }
  }
}

void MainWindow::seedlinkDestroyed()
{
  TRACE;
  _seedLink=nullptr;
}

void MainWindow::loadCity( )
{
  TRACE;
  CityLoader * d=new CityLoader(this);
  Settings::getWidget(d);
  d->scan();
  d->exec();
  Settings::setWidget(d);
  delete d;
}

void MainWindow::closeEvent(QCloseEvent * event)
{
  TRACE;
  if(warnBeforeClear(tr("Closing ...")) && closeAllSubWindowsFromAllTabs()) {
    event->accept();
  } else {
    event->ignore();
  }
}

void MainWindow::quit(bool force)
{
  TRACE;
  if(force || warnBeforeClear(tr("Quitting ..."))) {
    if(closeAllSubWindowsFromAllTabs()) {
      qApp->quit();
    }
  }
}

void MainWindow::clearAll()
{
  TRACE;
  if(warnBeforeClear(tr("Clearing all ..." ))) {
    closeAllSubWindowsFromAllTabs();
    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
    _fileList->beginReset();
    _db->clear();
    _fileList->endReset();
    _log->clear();
    if(_directoryMonitor) _directoryMonitor->removeAllFiles();
    update();
  }
}

void MainWindow::beginAddFile()
{
  TRACE;
  _fileList->beginAddFile();
}

void MainWindow::endAddFile()
{
  TRACE;
  _fileList->endAddFile();
}

void MainWindow::filesRemoved(const QList<SignalFile *>& fList)
{
  if(_directoryMonitor) {
    for(QList<SignalFile *>::ConstIterator it=fList.begin();it!=fList.end();++it) {
      _directoryMonitor->removeFile((*it)->name());
    }
  }
}

bool MainWindow::warnBeforeClear(QString action)
{
  TRACE;
  if(_db->isModified()) {
    switch (Message::warning(MSG_ID, action,
                             tr("Do you want to save the changes to the current database?"),
                             Message::yes(), Message::no(), Message::cancel())) {
    case Message::Answer0:
      saveDB();
      if(_db->isModified()) {
        return false;
      }
      break;
    case Message::Answer1:
      break;
    default:
      return false;
    }
  } else if(Message::question(MSG_ID, action, tr("Are you sure?"), Message::no(), Message::yes())==Message::Answer0) {
    return false;
  }

  return true;
}

void MainWindow::showToolBarMenu()
{
  TRACE;
  _toolBarsMenu->clear();
  QList<QDockWidget *> dockwidgetList=findChildren<QDockWidget *>();
  QAction * a;
  for(QList<QDockWidget *>::iterator it=dockwidgetList.begin();it!=dockwidgetList.end();++it) {
    a=new QAction(( *it) ->windowTitle(), this);
    a->setCheckable(true);
    if(( *it) ->isVisible())
      a->setChecked(true);
    connect(a, SIGNAL(toggled( bool) ), *it, SLOT(setVisible( bool) ));
    _toolBarsMenu->addAction(a);
  }
  _toolBarsMenu->addSeparator();
  QList<QToolBar *> toolBarList=findChildren<QToolBar *>();
  for(QList<QToolBar *>::iterator it=toolBarList.begin();it!=toolBarList.end();++it) {
    a=new QAction(( *it) ->windowTitle(), this);
    a->setCheckable(true);
    if(( *it) ->isVisible())
      a->setChecked(true);
    connect(a, SIGNAL(toggled( bool) ), *it, SLOT(setVisible( bool) ));
    _toolBarsMenu->addAction(a);
  }
}

void MainWindow::activeWindowChanged(MultiDocumentSubWindow * w)
{
  TRACE;
  SubPoolWindow * subPoolWin;
  if(w) {
    subPoolWin=qobject_cast<SubPoolWindow *>(w->widget());
  } else {
    subPoolWin=nullptr;
  }
  if(subPoolWin) {
    setSubPoolActionEnabled(!subPoolWin->tool(), !subPoolWin->subPool().isReadOnlySamples());
    if(subPoolWin->inherits("TableWindow")) {
      _fileImportAction->setEnabled(true);
      _viewSetDataFieldsAction->setEnabled(true);
    } else {
      _fileImportAction->setEnabled(false);
      _viewSetDataFieldsAction->setEnabled(false);
    }
    _waveformConsole->setCurrentSubPool(subPoolWin);
    _subPoolWin=subPoolWin;
  } else {
    setSubPoolActionEnabled(false, true);
    _subPoolWin=nullptr;
    _waveformConsole->setCurrentSubPool(nullptr);
  }
}

void MainWindow::setSubPoolActionEnabled(bool b, bool rw)
{
  TRACE;
  _fileExportAction->setEnabled(b);
  _fileImportAction->setEnabled(b);
  _viewSetDataFieldsAction->setEnabled(b);
  _waveMenu->setEnabled(b);
  if(b) {
    QList<QAction *> aList=_waveMenu->actions();
    for(QList<QAction *>::iterator it=aList.begin(); it!=aList.end(); it++ ) {
      if((*it)->data().toBool()) (*it)->setEnabled(rw);
    }
  }
  _editMenu->setEnabled(b);
  _toolsMenu->setEnabled(b);
}

QAction * MainWindow::newAction(const QAction& original, QWidget * parent)
{
  TRACE;
  QAction * a=new QAction(original.icon(), original.text(), parent);
  a->setShortcut(original.shortcut());
  a->setToolTip(original.toolTip());
  return a;
}

void MainWindow::setProgressMaximum(int value)
{
  TRACE;
  if(QThread::currentThread()==Application::instance()->mainThread()) {
    _progressBar->setMaximum(value);
    _progressBar->show();
    _progressHideTimer.stop();
  } else {
    emit asyncSetProgressMaximum(value);
  }
}

int MainWindow::progressMaximum()
{
  TRACE;
  return _progressBar->maximum();
}

void MainWindow::setProgressValue(int value)
{
  TRACE;
  if(QThread::currentThread()==Application::instance()->mainThread()) {
    if(value==_progressBar->maximum()) {
      _progressHideTimer.start();
    } else {
      _progressHideTimer.stop();
    }
    _progressBar->setValue(value);
  } else {
    emit asyncSetProgressValue(value);
  }
}

int MainWindow::progressValue()
{
  TRACE;
  return _progressBar->value();
}

void MainWindow::increaseProgressValue(int value)
{
  TRACE;
  if(QThread::currentThread()==Application::instance()->mainThread()) {
    _progressBar->increaseValue(value);
  } else {
    emit asyncIncreaseProgressValue(value);
  }
}

void MainWindow::hideProgressBar()
{
  TRACE;
  _progressBar->hide();
}

void MainWindow::showMessage(QString message)
{
  TRACE;
  if(QThread::currentThread()==Application::instance()->mainThread()) {
    statusBar()->showMessage(message);
  } else {
    emit asyncShowMessage(message);
  }
}
void MainWindow::dragEnterEvent(QDragEnterEvent * e)
{
  TRACE;
  if(e->mimeData()->hasUrls()) {
    e->acceptProposedAction();
  }
}

void MainWindow::dragMoveEvent(QDragMoveEvent * e)
{
  TRACE;
  if(e->mimeData()->hasUrls()) {
    e->acceptProposedAction();
  }
}

void MainWindow::dropEvent(QDropEvent * e)
{
  TRACE;
  if(e->mimeData()->hasUrls()) {
    QList<QUrl> urls=e->mimeData()->urls();
    QUrl url;
    QStringList files;
    MessageContext mc;
    foreach(url,urls) {
      files << url.toLocalFile();
    }
    _db->load(files, SignalFileFormat::Unknown, true);
  }
  e->acceptProposedAction();
}

void MainWindow::addActions()
{
  TRACE;
  addFileActions();
  addEditActions();
  addViewActions();
  addWaveformActions();
  addToolsActions();
  addWindowsActions();
  addHelpActions();

  _groupList->addActions();
  _fileList->addActions();
  _eventList->addActions();
}

void MainWindow::addFileActions()
{
  TRACE;
  QAction * a;
  QMenu * m;
  QToolBar * tb;

  m=menuBar()->addMenu(tr("&File"));
  m->setTearOffEnabled (true);
  tb=addToolBar(tr("File"));
  tb->setObjectName("FileBar");
  tb->setIconSize(QSize(24, 24));

  a=new QAction(QIcon(":fileopen.png"), tr("&Open"), this);
  a->setShortcut(tr("Ctrl+O"));
  a->setStatusTip(tr("Open an existing database"));
  connect(a, SIGNAL(triggered()), this, SLOT(openDB()));
  m->addAction(a);
  tb->addAction(a);

  a=WindowEnvironment::instance()->_fileOpenRecentAction;
  a->setStatusTip(tr("Open an existing database"));
  m->addAction(a);

  a=new QAction(QIcon(":filesave.png"), tr("&Save"), this);
  a->setShortcut(tr("Ctrl+S"));
  a->setStatusTip(tr("Save the current database to disk"));
  connect(a, SIGNAL(triggered()), this, SLOT(saveDB()));
  m->addAction(a);
  tb->addAction(a);

  a=new QAction(tr("&Save as ..."), this);
  a->setStatusTip(tr("Create a new database on disk"));
  connect(a, SIGNAL(triggered()), this, SLOT(saveDBAs()));
  m->addAction(a);

  a=new QAction(tr("Close"), this);
  a->setStatusTip(tr("Close current database"));
  connect(a, SIGNAL(triggered()), this, SLOT(clearAll()));
  m->addAction(a);

  m->addSeparator();

  _importSignalMenu=new QMenu(this);

  a=new QAction(tr("&File"), this);
  a->setStatusTip(tr("New signals are read from local files"));
  connect(a, SIGNAL(triggered()), this, SLOT(load()));
  _importSignalMenu->addAction(a);

  a=new QAction(tr("File &pattern"), this);
  a->setStatusTip(tr("Complex selection of local files with wildchars (? and *)"));
  connect(a, SIGNAL(triggered()), this, SLOT(loadFilePattern()));
  _importSignalMenu->addAction(a);

  a=new QAction(tr("&Directory monitoring"), this);
  a->setStatusTip(tr("New signals from a local directory are automatically loaded"));
  connect(a, SIGNAL(triggered()), this, SLOT(directoryMonitor()));
  _importSignalMenu->addAction(a);

  a=new QAction(tr("&Seed stream"), this);
  a->setStatusTip(tr("New signals are read on a continuous seedLink"));
  connect(a, SIGNAL(triggered()), this, SLOT(seedlink()));
  _importSignalMenu->addAction(a);

  a=new QAction(tr("&Cityshark card"), this);
  a->setStatusTip(tr("New signals are extracted from a Cityshark flash card"));
  connect(a, SIGNAL(triggered()), this, SLOT(loadCity()));
  _importSignalMenu->addAction(a);

  a=new QAction(QIcon(":fileimportsignal.png"), tr("&Import signals"), this);
  a->setStatusTip(tr("Add new signals into current database"));
  a->setMenu(_importSignalMenu);
  connect(a, SIGNAL(triggered()), this, SLOT(load()));
  m->addAction(a);
  tb->addAction(a);

  a=new QAction(tr("&Import headers"), this);
  a->setShortcut(tr("Ctrl+I"));
  a->setStatusTip(tr("Import header properties of signals (works only for Tables)"));
  connect(a, SIGNAL(triggered()), this, SLOT(importTable()));
  m->addAction(a);
  _fileImportAction=a;

  a=new QAction(tr("&Export ..."), this);
  a->setShortcut(tr("Ctrl+E"));
  a->setStatusTip(tr("Export signals to common file formats"));
  connect(a, SIGNAL(triggered()), this, SLOT(exportSignal()));
  m->addAction(a);
  _fileExportAction=a;

  m->addSeparator();

  a=new QAction(tr("&Preferences"), this);
  a->setStatusTip(tr("Customize Geopsy"));
  connect(a, SIGNAL(triggered()), this, SLOT(setPreferences()));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("&Quit"), this);
  a->setShortcut(tr("Ctrl+Q"));
  a->setStatusTip(tr("Quit Geopsy"));
  connect(a, SIGNAL(triggered()), this, SLOT(quit()));
  m->addAction(a);
}

void MainWindow::addEditActions()
{
  TRACE;
  QAction * a;
  QMenu * m;

  m=menuBar()->addMenu(tr("&Edit"));
  m->setTearOffEnabled (true);
  _editMenu=m;

  a=new QAction(tr("&Lock table edition"), this);
  a->setShortcut(tr("Ctrl+K"));
  a->setCheckable(true);
  a->setChecked(true);
  a->setStatusTip(tr("Uncheck this option to edit tables (like regular worksheets)."));
  connect(a, SIGNAL(toggled(bool)), this, SLOT(setTableLocked(bool)));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("&Sort"), this);
  a->setStatusTip(tr("Sort the signals in the current viewer"));
  connect(a, SIGNAL(triggered()), this, SLOT(sort()));
  m->addAction(a);

  a=new QAction(tr("Set &headers"), this);
  a->setShortcut(tr("Ctrl+H"));
  a->setStatusTip(tr("Automatic modifications of header information in the current viewer"));
  connect(a, SIGNAL(triggered()), this, SLOT(setHeader()));
  m->addAction(a);

  a=new QAction(tr("Set &receivers"), this);
  a->setShortcut(tr("Ctrl+R"));
  a->setStatusTip(tr("Change the receiver of the signals in the current viewer"));
  connect(a, SIGNAL(triggered()), this, SLOT(setReceivers()));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("New group(s)"), this);
  a->setShortcut(tr("Ctrl+N"));
  a->setStatusTip(tr("Store the list of signals in current viewer into one or several new groups"));
  connect(a, SIGNAL(triggered()), this, SLOT(createNewGroup()));
  m->addAction(a);
}

void MainWindow::addViewActions()
{
  TRACE;
  QAction * a;
  QMenu * m;
  QToolBar * tb;

  m=menuBar()->addMenu(tr("&View"));
  m->setTearOffEnabled (true);
  tb=addToolBar(tr("View"));
  tb->setObjectName("ViewBar");
  tb->setIconSize(QSize(24, 24));
  _viewMenu=m;

  a=new QAction(QIcon(":table-22x22.png"), tr("&Table"), this);
  a->setShortcut(tr("Ctrl+T"));
  a->setStatusTip(tr("Show signal header information in a table"));
  connect(a, SIGNAL(triggered()), Engine::instance(), SLOT(newTableWindow()));
  m->addAction(a);
  tb->addAction(a);
  _viewTableAction=a;

  a=new QAction(QIcon(":graph-22x22.png"), tr("&Graphic"), this);
  a->setShortcut(tr("Ctrl+G"));
  a->setStatusTip(tr("Show signals on a plot versus time or frequency"));
  connect(a, SIGNAL(triggered()), Engine::instance(), SLOT(newGraphicWindow()));
  m->addAction(a);
  tb->addAction(a);
  _viewGraphicAction=a;

  a=new QAction(QIcon(":map-22x22.png"), tr("&Map"), this);
  a->setShortcut(tr("Ctrl+M"));
  a->setStatusTip(tr( "Show receiver and source coordinates on a XY map"));
  connect(a, SIGNAL(triggered()), Engine::instance(), SLOT(newMapWindow()));
  m->addAction(a);
  tb->addAction(a);
  _viewMapAction=a;

  a=new QAction(QIcon(":chronogram-22x22.png"), tr("&Chronogram"), this);
  a->setStatusTip(tr("Show time availability of signals"));
  connect(a, SIGNAL(triggered()), Engine::instance(), SLOT(newChronogramWindow()));
  m->addAction(a);
  tb->addAction(a);
  _viewChronogramAction=a;

  m->addSeparator();

  a=new QAction(tr("&Time"), this);
  a->setObjectName("Time");
  a->setShortcut(tr("Ctrl+Shift+E"));
  a->setStatusTip(tr("Switch the signals of the current viewer to time domain"));
  a->setCheckable(true);
  a->setChecked(true);
  connect(a, SIGNAL(triggered()), this, SLOT(fftTime()));
  m->addAction(a);
  _fftTimeAction=a;

  a=new QAction(tr("Frequency (&amplitude)"), this);
  a->setObjectName("FrequencyAmplitude");
  a->setShortcut(tr("Ctrl+Shift+F"));
  a->setStatusTip(tr("Switch the signals of the current viewer to frequency domain (amplitude plot)"));
  a->setCheckable(true);
  connect(a, SIGNAL(triggered()), this, SLOT(fftAmpl()));
  m->addAction(a);
  _fftAmplitudeAction=a;

  a=new QAction(tr("Frequency (&phase)"), this);
  a->setObjectName("FrequencyPhase");
  a->setStatusTip(tr("Switch the signals of the current viewer to frequency domain (phase plot)"));
  a->setCheckable(true);
  connect(a, SIGNAL(triggered()), this, SLOT(fftPhase()));
  m->addAction(a);
  _fftPhaseAction=a;

  m->addSeparator();

  a=new QAction(tr("Set data fields"), this);
  a->setStatusTip(tr("Customize the headers of the current table"));
  connect(a, SIGNAL(triggered()), this, SLOT(viewSetDataFields()));
  m->addAction(a);
  _viewSetDataFieldsAction=a;
}

void MainWindow::addWaveformActions()
{
  TRACE;
  QMenu * m;

  m=menuBar()->addMenu(tr("W&aveform"));
  m->setTearOffEnabled (true);
  _waveMenu=m;

  _waveformConsole->addActions(m);
}

void MainWindow::addToolsActions()
{
  TRACE;
  QMenu * m;
  QToolBar * tb;

  m=menuBar()->addMenu(tr("&Tools"));
  m->setTearOffEnabled (true);
  tb=addToolBar(tr("Tools"));
  tb->setObjectName("ToolsBar");
  tb->setIconSize(QSize( 24, 24));
  _toolsMenu=m;
  _toolsBar=tb;
}

/**************************************************************************
*                                                                         *
*   Edit actions                                                          *
*                                                                         *
***************************************************************************/

void MainWindow::setTableLocked(bool l)
{
  TRACE;
  _tableEditionLocked=l;
  QList<MultiDocumentSubWindow *> windows=subWindowList();
  for(QList<MultiDocumentSubWindow *>::iterator it=windows.begin(); it!=windows.end(); it++) {
    TableWindow * table=qobject_cast<TableWindow *>((*it)->widget());
    if(table) {
      table->setEditionLocked(_tableEditionLocked);
    }
  }
}

void MainWindow::sort()
{
  TRACE;
  if(!_subPoolWin) return ;
  SortKeys * d=new SortKeys(this);
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    d->getKeys();
    _subPoolWin->subPool().sort();
    _subPoolWin->subPoolUpdate();
  }
  delete d;
}

void MainWindow::setHeader()
{
  TRACE;
  if(!_subPoolWin) return;
  if(_subPoolWin->headerWidget()) {
    _subPoolWin->headerWidget()->parentWidget()->raise();
  } else {
    HeaderWidget * d=new HeaderWidget(&_subPoolWin->subPool(), this);
    _subPoolWin->setHeaderWidget(d);
    addSubWindow(d);
  }
}

void MainWindow::setReceivers()
{
  TRACE;
  if(!_subPoolWin) return ;
  StationCoordinates * d=new StationCoordinates(this);
  Settings::getWidget(d);
  QString log;
  StreamRedirection * sr=new StreamRedirection(new StringStream(&log));
  if(!d->init(&_subPoolWin->subPool())) {
    Message::warning(MSG_ID, tr("Set receivers"),
                     tr("Found errors during initialization of this tool. They are listed here below. Fix them if you want to edit signal coordinated.\n\n%1").arg(log),
                     Message::ok());
    d->setReadOnly(true);
  }
  delete sr;
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    SubSignalPool::iterator it;
    SubSignalPool& subPool=_subPoolWin->subPool();
    for(it=subPool.begin();it!=subPool.end();++it) {
      Signal * sig=*it;
      bool ok;
      Point p=d->lookup(sig, ok);
      if(ok) {
        sig->setReceiver(p);
        sig->setUtmZone(d->utmZone());
      }
      sig->setHeaderModified(true);
    }
    _subPoolWin->subPoolUpdate();
  }
  delete d;
}

void MainWindow::createNewGroup()
{
  TRACE;
  if(!_subPoolWin) return;
  SignalGroup * g=new SignalGroup;
  g->setSignals(_subPoolWin->subPool());
  Dialog * d=new Dialog(this);
  GroupProperties * dg=new GroupProperties(this);
  d->setMainWidget(dg);
  Settings::getWidget(d, "GroupProperties");
  QList<AbstractSignalGroup *> gList;
  gList.append(g);
  dg->setDatabase(database());
  dg->setValues(gList);
  dg->setNameFromHistory();
  while(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d, "GroupProperties");
    dg->setProperties(g);
    Settings::setHistory("GroupNames", g->name());
    if(dg->isMultiGroupKey()) {
      MetaDataIndex key=dg->splitKey();
      SubSignalPool& subPool=_subPoolWin->subPool();
      Signal * sig=subPool.at(0);
      QVariant val=sig->header(key);
      SubSignalPool splitGroupSubPool;
      splitGroupSubPool.addSignal(sig);
      SignalExpressionContext context;
      ExpressionString gName;
      QString log;
      StreamRedirection sr(new StringStream(&log));
      if(!gName.setPattern(g->name(), &context)) {
        Message::warning(MSG_ID, tr("Create new group(s)"), tr("Error(s) while parsing group name pattern:\n\n%1").arg(log));
        continue;
      }
      int gi=1;
      for(int i=1; i<subPool.count(); i++) {
        sig=subPool.at(i);
        if(val!=sig->header(key)) {
          addSplitGroup(splitGroupSubPool, dg->folder(), gName, context, i-splitGroupSubPool.count()+1, gi++);
          val=sig->header(key);
        }
        splitGroupSubPool.addSignal(sig);
      }
      if(!splitGroupSubPool.isEmpty()) {
        addSplitGroup(splitGroupSubPool, dg->folder(), gName, context, subPool.count()-splitGroupSubPool.count()+1, gi);
      }
      delete g;
    } else if(dg->isMultiGroupArray()) {
      QString name=g->name();
      delete g;
      if(_subPoolWin->subPool().associate3Components()) {
        SubSignalPool s=_subPoolWin->subPool();
        g=new SignalGroup;
        g->setSignals(_subPoolWin->subPool());
        g->setName(name+"-3C");
        g->setParent(dg->folder());
        g=new SignalGroup;
        g->setSignals(_subPoolWin->subPool().horizontalComponents());
        g->setName(name+"-H");
        g->setParent(dg->folder());
        g=new SignalGroup;
        g->setSignals(_subPoolWin->subPool().verticalComponent());
        g->setName(name+"-Z");
        g->setParent(dg->folder());
      }
    } else {
      g->setParent(dg->folder());
    }
    delete d;
    return;
  }
  delete g;
  delete d;
}

void MainWindow::addSplitGroup(SubSignalPool& splitGroupSubPool, AbstractSignalGroup * parent,
                                     ExpressionString& gName,SignalExpressionContext& context,
                                     int viewerIndex, int groupIndex)
{
  TRACE;
  context.addVariable("ViewerIndex", viewerIndex);
  context.addVariable("GroupIndex", groupIndex);
  context.setSignal(splitGroupSubPool.first());
  splitGroupSubPool.setName(gName.value());
  SignalGroup * g=new SignalGroup(parent);
  g->setSignals(splitGroupSubPool);
  g->setParent(parent);
  splitGroupSubPool.removeAll();
}

void MainWindow::fftTime()
{
  TRACE;
  if( !_subPoolWin) return ;
  _subPoolWin->fastFourierTransform(DoubleSignal::Waveform);
  _fftTimeAction->setChecked(true);
  _fftAmplitudeAction->setChecked(false);
  _fftPhaseAction->setChecked(false);
}

void MainWindow::fftAmpl()
{
  TRACE;
  if( !_subPoolWin) return ;
  _subPoolWin->fastFourierTransform(DoubleSignal::Spectrum);
  _fftTimeAction->setChecked(false);
  _fftAmplitudeAction->setChecked(true);
  _fftPhaseAction->setChecked(false);
}

void MainWindow::fftPhase()
{
  TRACE;
  if( !_subPoolWin) return ;
  _subPoolWin->fastFourierTransform(DoubleSignal::Phase);
  _fftTimeAction->setChecked(false);
  _fftAmplitudeAction->setChecked(false);
  _fftPhaseAction->setChecked(true);
}

/**************************************************************************
*                                                                         *
*   View actions                                                          *
*                                                                         *
***************************************************************************/

void MainWindow::viewSetDataFields()
{
  TRACE;
  if(_subPoolWin && _subPoolWin->inherits("TableWindow")) {
    static_cast<TableWindow *>(_subPoolWin)->setDataFields();
  }
}

/**************************************************************************
*                                                                         *
*   File actions                                                          *
*                                                                         *
***************************************************************************/

void MainWindow::importTable()
{
  TRACE;
  TableWindow * tableWin=qobject_cast<TableWindow *>(_subPoolWin);
  if(tableWin) {
    tableWin->importTable();
  }
}

void MainWindow::exportSignal()
{
  TRACE;
  MessageContext mc;
  if( !_subPoolWin) return ;
  ExportFileType * d=new ExportFileType(this);
  d->setFormatList(_subPoolWin->inherits( "TableWindow" ));
  Settings::getWidget(d);
  d->updateAllFields();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    SignalFileFormat format=d->format();
    bool useOriginalBaseName=d->useOriginalBaseName();
    int maxSignalsPerFile=d->maximumSignalsPerFile();
    if(format.id()==SignalFileFormat::Temporary) {
      if(strcmp(_subPoolWin->metaObject() ->className(), "TableWindow")==0) {
        static_cast<TableWindow*>(_subPoolWin)->exportTable();
      }
      delete d;
      return;
    }
    if(format.id()==SignalFileFormat::Tomo) {
      ExportPick * d=new ExportPick(this);
      Settings::getWidget(d);
      if(d->exec()==QDialog::Accepted) {
        Settings::setWidget(d);
        QString fileName=Message::getSaveFileName(tr("Export %1").arg(format.caption()),
                                                  tr("%1 (%2)").arg(format.caption())
                                                               .arg(format.filter()));
        if(!fileName.isEmpty()) {
          QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
          if(!_subPoolWin->subPool().save(fileName, useOriginalBaseName, format,
                                          maxSignalsPerFile, d->pickNameCombo->currentText())) {
            Message::warning(MSG_ID, tr("Export %1").arg(format.caption()),
                             tr("Error export file %1, see log for details").arg(fileName));
          }
        }
      }
      delete d;
    } else {
      QString filePath;
      if(useOriginalBaseName) {
        filePath=Message::getExistingDirectory(tr("Export %1").arg(format.caption()));
      } else {
        QString bottomMsg;
        if(maxSignalsPerFile>0) {
          if(maxSignalsPerFile>1) {
            bottomMsg=tr("Signal name is automatically added at the end of file name. For instance, "
                         "if the signal names are 'S01', and if you provide a file name 'Array_large-.mseed', "
                         "exported files will be called 'Array_large-S01.mseed'.");
          } else {
            bottomMsg=tr("Signal name and component are automatically added at the end of file name. For instance, "
                         "if the signal name is 'S01' and is a vertical component, and if you provide a file name "
                         "'Array_large-.mseed', exported files will be called 'Array_large-S01_Z.mseed'.");
          }
        }
        filePath=Message::getSaveFileName(tr("Export %1" ).arg(format.caption()),
                                          tr("%1 (%2)").arg(format.caption()).arg(format.filter()),
                                          QString(), bottomMsg);
      }
      if(!filePath.isEmpty()) {
        QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
        if(!_subPoolWin->subPool().save(filePath, useOriginalBaseName, format,
                                        maxSignalsPerFile, QString())) {
          Message::warning(MSG_ID, tr("Export %1").arg(format.caption()),
                           tr("Error export file %1, see log for details").arg(filePath));
        }
      }
    }
  }
  delete d;
}
