text:
// Copyright 2020 Dow, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "AnalysisWindow.h"
#include "AppStyle.h"
#include "InputViewer.h"
#include "LogWidget.h"
#include "MainWindow.h"
#include "MeteorologyEditor.h"
#include "ProjectTreeView.h"
#include "ReceptorDialog.h"
#include "ReceptorElevationEditor.h"
//#include "RibbonDefinition.h"
#include "RunModelDialog.h"
#include "ScenarioProperties.h"
#include "SourceGroupProperties.h"
#include "SourceTable.h"

#include "core/Common.h"
#include "core/Scenario.h"
#include "core/Serialization.h"
#include "core/SourceGroup.h"
#include "core/Project.h"
#include "core/Validation.h"

#include "models/MeteorologyModel.h"
#include "models/ProjectModel.h"

#include "widgets/ProgressBar.h"

#include <QAction>
#include <QApplication>
#include <QBoxLayout>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QMimeData>
#include <QScreen>
#include <QSettings>
#include <QStatusBar>
#include <QTabWidget>
#include <QToolBar>
#include <QTreeWidget>
#include <QTreeWidgetItem>

#ifdef Q_OS_WIN
#include <QWinTaskbarButton>
#include <QWinTaskbarProgress>
#endif

#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>

#include <csv/csv.h>

#include <fstream>

MainWindow::MainWindow()
{
    //setWindowIcon(QIcon(":/res/SOFEA_64x.png"));
    setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    int largeIconSize = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize);
    setIconSize(QSize(largeIconSize, largeIconSize));
    setAcceptDrops(true);

    centralTabWidget = new QTabWidget;
    centralTabWidget->setTabsClosable(true);
    centralTabWidget->setDocumentMode(true);

    QVBoxLayout *centralLayout = new QVBoxLayout;
    centralLayout->addSpacing(5);
    centralLayout->addWidget(centralTabWidget);
    centralLayout->setContentsMargins(0, 0, 0, 0);

    QWidget *central = new QWidget;
    central->setLayout(centralLayout);
    setCentralWidget(central);

    createMenus();
    createActions();
    createToolBar();
    createProgressBar();
    createPanels();
    setupConnections();
    setupLogging();
    loadSettings();

    QStringList argv = QCoreApplication::arguments();
    if (argv.size() > 1 && QFile::exists(argv.last())) {
        openProjectFile(argv.last());
    }

    projectModified = false;
}

void MainWindow::createMenus()
{
    fileMenu = menuBar()->addMenu(tr("&File"));
    viewMenu = menuBar()->addMenu(tr("&View"));
    toolsMenu = menuBar()->addMenu(tr("&Tools"));
    helpMenu = menuBar()->addMenu(tr("&Help"));
}

void MainWindow::createActions()
{
    // Resources
    const QIcon newIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_NewFile));
    const QIcon openIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_OpenFile));
    const QIcon saveIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_Save));
    const QIcon saveAsIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_SaveAs));
    const QIcon windRoseIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_WindRose));
    const QIcon validateIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_Checkmark));
    const QIcon runIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_Run));
    const QIcon analyzeIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_Measure));
    const QIcon helpIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_StatusHelp));

    const QIcon cloneIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionClone));
    const QIcon renameIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionRename));
    const QIcon addGroupIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionAddBuildQueue));
    const QIcon importDataIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionImport));
    const QIcon receptorEditorIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionEditorZone));
    const QIcon showTableIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionTable));
    const QIcon showInputFileIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionTextFile));
    const QIcon exportFluxFileIcon = this->style()->standardIcon(static_cast<QStyle::StandardPixmap>(AppStyle::CP_ActionExportFile));

    // File Menu actions
    newAct = new QAction(newIcon, tr("&New Scenario"), this);
    newAct->setStatusTip(tr("Create a new scenario"));
    newAct->setShortcuts(QKeySequence::New);

    openAct = new QAction(openIcon, tr("&Open Project..."), this);
    openAct->setStatusTip(tr("Open an existing project"));
    openAct->setShortcuts(QKeySequence::Open);

    saveAct = new QAction(saveIcon, tr("&Save Project"), this);
    saveAct->setStatusTip(tr("Save the current project"));
    saveAct->setShortcuts(QKeySequence::Save);

    saveAsAct = new QAction(saveAsIcon, tr("&Save Project As..."), this);
    saveAsAct->setStatusTip(tr("Save the current project to a new file"));
    saveAsAct->setShortcuts(QKeySequence::SaveAs);

    closeAct = new QAction(tr("Close Project"), this);
    closeAct->setStatusTip(tr("Close the current project"));

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setStatusTip(tr("Exit the application"));
#ifdef Q_OS_WIN
    exitAct->setShortcut(QKeySequence(Qt::ALT + Qt::Key_F4));
#else
    exitAct->setShortcuts(QKeySequence::Quit);
#endif

    fileMenu->addAction(newAct);
    fileMenu->addAction(openAct);
    fileMenu->addAction(saveAct);
    fileMenu->addAction(saveAsAct);
    fileMenu->addSeparator();
    fileMenu->addAction(closeAct);
    fileMenu->addAction(exitAct);

    // Project actions
    meteorologyAct = new QAction(windRoseIcon, tr("Meteorology"), this);
    meteorologyAct->setStatusTip(tr("Edit meteorological data"));

    // Tools Menu actions
    validateAct = new QAction(validateIcon, tr("Check Project"), this);
    validateAct->setStatusTip(tr("Check project for errors"));

    runAct = new QAction(runIcon, tr("&Run Model..."), this);
    runAct->setStatusTip(tr("Run the current project"));

    analyzeAct = new QAction(analyzeIcon, tr("Analyze Results..."), this);
    analyzeAct->setStatusTip(tr("Analyze results"));

    optionsAct = new QAction(tr("Options..."), this);
    optionsAct->setStatusTip(tr("Edit global options"));
    optionsAct->setDisabled(true); // FIXME

    toolsMenu->addAction(validateAct);
    toolsMenu->addAction(runAct);
    toolsMenu->addAction(analyzeAct);
    toolsMenu->addSeparator();
    toolsMenu->addAction(optionsAct);

    // Help Menu actions
    helpAct = new QAction(helpIcon, tr("Open User's Guide"), this);
    helpAct->setShortcuts(QKeySequence::HelpContents);
    helpAct->setStatusTip(tr("Open User's Guide"));

    aboutAct = new QAction(tr("About SOFEA"), this);
    aboutAct->setStatusTip(tr("About SOFEA"));

    helpMenu->addAction(helpAct);
    helpMenu->addSeparator();
    helpMenu->addAction(aboutAct);

    // Scenario Context Menu actions
    scenarioCloneAct = new QAction(cloneIcon, tr("Clone"), this);
    scenarioRenameAct = new QAction(renameIcon, tr("Rename"), this);
    scenarioRemoveAct = new QAction(tr("Remove"), this);
    scenarioAddSourceGroupAct = new QAction(addGroupIcon, tr("Add Source Group"), this);
    scenarioImportValidationAct = new QAction(importDataIcon, tr("Import Retrospective Data..."), this);
    scenarioPropertiesAct = new QAction(tr("Properties..."), this);

    scenarioInputFileAct = new QAction(showInputFileIcon, tr("Show Input File"), this);
    scenarioFluxFileAct = new QAction(exportFluxFileIcon, tr("Export Flux File..."), this);

    // Source Group Context Menu actions
    sourceGroupCloneAct = new QAction(cloneIcon, tr("Clone"), this);
    sourceGroupRenameAct = new QAction(renameIcon, tr("Rename"), this);
    sourceGroupRemoveAct = new QAction(tr("Remove"), this);
    sourceGroupPropertiesAct = new QAction(tr("Properties..."), this);

    receptorEditorAct = new QAction(receptorEditorIcon, tr("Edit Receptors..."), this);
    elevationEditorAct = new QAction(tr("Edit Elevations..."), this);
    sourceTableAct = new QAction(showTableIcon, tr("Show Source Table"), this);
}

void MainWindow::createToolBar()
{
    toolbar = addToolBar(tr("Standard"));
    toolbar->setObjectName("MainToolbar");
    toolbar->addAction(newAct);
    toolbar->addAction(openAct);
    toolbar->addAction(saveAct);
    toolbar->addSeparator();
    toolbar->addAction(meteorologyAct);
    toolbar->addAction(validateAct);
    toolbar->addAction(runAct);
    toolbar->addAction(analyzeAct);

    // Override drawPrimitive(PE_PanelButtonCommand)?
    //toolbar->setContentsMargins(0, 0, 0, 0);
    //toolbar->layout()->setSpacing(0);
    //toolbar->layout()->setContentsMargins(0, 0, 0, 20);
}

void MainWindow::createProgressBar()
{
    QFontMetrics fm = fontMetrics();

    progressBar = new ProgressBar;
    progressBar->setFixedWidth(fm.averageCharWidth() * 30);
    progressBar->setAutoHide(true);

    statusBar()->addPermanentWidget(progressBar, 0);
}

void MainWindow::createPanels()
{
    setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

    // Project Browser
    dwProjectTree = new QDockWidget(tr("Project Browser"), this);
    dwProjectTree->setObjectName("ProjectBrowser");
    dwProjectTree->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

    //QDockWidget *dwProjectTreeView = new QDockWidget(tr("Project Browser"), this);
    //dwProjectTreeView->setObjectName("ProjectBrowser");
    //dwProjectTreeView->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    //
    //projectTreeView = new ProjectTreeView(dwProjectTreeView);
    //projectModel = new ProjectModel(this);
    //auto project = std::make_shared<Project>();
    //project->scenarios.emplace_back(std::make_shared<Scenario>());
    //projectModel->setProject(project);
    //projectTreeView->setModel(projectModel);
    //
    //dwProjectTreeView->setWidget(projectTreeView);
    //addDockWidget(Qt::LeftDockWidgetArea, dwProjectTreeView);

    projectTree = new QTreeWidget(dwProjectTree);
    QFont font = QApplication::font();
    font.setPointSizeF(font.pointSizeF() + 1);
    projectTree->setFont(font);
    projectTree->setColumnCount(1);
    projectTree->setHeaderHidden(true);
    projectTree->setUniformRowHeights(true);
    projectTree->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
    projectTree->setSelectionBehavior(QAbstractItemView::SelectRows);
    projectTree->setSelectionMode(QAbstractItemView::SingleSelection);
    projectTree->setContextMenuPolicy(Qt::CustomContextMenu);
    //projectTree->setDragDropMode(QAbstractItemView::InternalMove);
    //projectTree->setAcceptDrops(true);
    //projectTree->setDragEnabled(true);

    dwProjectTree->setWidget(projectTree);
    addDockWidget(Qt::LeftDockWidgetArea, dwProjectTree);

    // Validation LogWidget
    dwValidation = new QDockWidget(tr("Validation"), this);
    dwValidation->setObjectName("Validation");
    dwValidation->setAllowedAreas(Qt::BottomDockWidgetArea);
    lwValidation = new LogWidget(dwValidation);
    dwValidation->setWidget(lwValidation);
    addDockWidget(Qt::BottomDockWidgetArea, dwValidation);

    // Model Output LogWidget
    dwOutput = new QDockWidget(tr("Model Output"), this);
    dwValidation->setObjectName("Model Output");
    dwValidation->setAllowedAreas(Qt::BottomDockWidgetArea);
    lwOutput = new LogWidget(dwOutput);
    dwOutput->setWidget(lwOutput);
    addDockWidget(Qt::BottomDockWidgetArea, dwOutput);

    viewMenu->addAction(dwProjectTree->toggleViewAction());
    viewMenu->addAction(dwValidation->toggleViewAction());
    viewMenu->addAction(dwOutput->toggleViewAction());

    // Tabify LogWidgets
    tabifyDockWidget(dwValidation, dwOutput);
    dwValidation->raise();
}

void MainWindow::setupConnections()
{
    connect(centralTabWidget, &QTabWidget::tabCloseRequested,
            this, &MainWindow::deleteTab);

    //connect(this, &MainWindow::commandActivated, this, &MainWindow::onCommandActivated);
    //connect(this, &MainWindow::commandToggled, this, &MainWindow::onCommandToggled);

    connect(this, &MainWindow::scenarioUpdated, this, &MainWindow::onScenarioUpdated);
    connect(this, &MainWindow::sourceGroupUpdated, this, &MainWindow::onSourceGroupUpdated);

    // File Menu
    connect(newAct, &QAction::triggered, this, &MainWindow::newScenario);
    connect(openAct, &QAction::triggered, this, &MainWindow::openProject);
    connect(saveAct,  &QAction::triggered, this, &MainWindow::saveProject);
    connect(saveAsAct,  &QAction::triggered, this, &MainWindow::saveProject);
    connect(closeAct, &QAction::triggered, this, &MainWindow::closeProject);
    connect(exitAct, &QAction::triggered, this, &MainWindow::exitApplication);

    // Model Menu
    connect(meteorologyAct, &QAction::triggered, this, &MainWindow::showMeteorologyEditor);
    connect(validateAct, &QAction::triggered, this, &MainWindow::validate);
    connect(runAct, &QAction::triggered, this, &MainWindow::runModel);
    connect(analyzeAct, &QAction::triggered, this, &MainWindow::showAnalysisWindow);

    // Help Menu
    connect(helpAct, &QAction::triggered, this, &MainWindow::showHelp);
    connect(aboutAct, &QAction::triggered, this, &MainWindow::about);

    // Project Tree
    connect(projectTree, &QTreeWidget::customContextMenuRequested, this, &MainWindow::contextMenuRequested);
    connect(projectTree, &QTreeWidget::itemChanged, this, &MainWindow::onItemChanged);
    //connect(projectTree, &QTreeWidget::currentItemChanged, this, &MainWindow::onCurrentItemChanged);
}

void MainWindow::setupLogging()
{
    // Configure columns and filter menu for each log widget.
    QFontMetrics fm = fontMetrics();
    int maxCharWidth = fm.horizontalAdvance(QChar('W'));
    int avgCharWidth = fm.averageCharWidth();

    lwValidation->setColumn(0, "Message", "", 0);
    lwValidation->setColumn(1, "Source", "Source", avgCharWidth * 50);
    lwValidation->setFilterKeyColumn(1);
    lwValidation->setFilterValues(QStringList{"Distribution", "Geometry", "Projection", "Import", "Export", "Meteorology", "Model", "Analysis", "General"});

    lwOutput->setColumn(0, "Message", "", 0);
    lwOutput->setColumn(1, "PW", "Pathway", maxCharWidth * 2);
    lwOutput->setColumn(2, "Code", "ErrorCode", maxCharWidth * 4);
    lwOutput->setColumn(3, "Line", "Line", maxCharWidth * 8);
    lwOutput->setColumn(4, "Module", "Module", maxCharWidth * 12);
    lwOutput->setColumn(5, "Directory", "Dir", avgCharWidth * 50);
    lwOutput->setFilterKeyColumn(1);
    lwOutput->setFilterValues(QStringList{"CO", "SO", "RE", "ME", "EV", "OU", "MX", "CN"});
    lwOutput->setUniformRowHeights(true);

    // Get a pointer to the global logging core.
    auto core = boost::log::core::get();

    // Set a global severity filter.
#ifdef SOFEA_DEBUG
    core->set_filter(boost::log::trivial::severity >= boost::log::trivial::trace);
#else
    core->set_filter(boost::log::trivial::severity >= boost::log::trivial::info);
#endif

    // Register custom sinks, with attribute presence filter on "Dir".
    // The Dir attribute should be present for all model output messages.
    typedef boost::log::sinks::synchronous_sink<LogWidgetBackend> sink_t;

    auto messageBackend = boost::make_shared<LogWidgetBackend>(lwValidation);
    messageBackend->setKeywords(QStringList{"Source"});
    auto messageSink = boost::make_shared<sink_t>(messageBackend);
    messageSink->set_filter(!boost::log::expressions::has_attr("Dir"));
    core->add_sink(messageSink);

    auto outputBackend = boost::make_shared<LogWidgetBackend>(lwOutput);
    outputBackend->setKeywords(QStringList{"Dir", "Pathway", "ErrorCode", "Line", "Module"});
    auto outputSink = boost::make_shared<sink_t>(outputBackend);
    outputSink->set_filter(boost::log::expressions::has_attr("Dir"));
    core->add_sink(outputSink);
}

void MainWindow::loadSettings()
{
    QSettings settings;
    settings.beginGroup("MainWindow");

    // Default main window size is 75% main screen size
    QScreen *primaryScreen = QGuiApplication::primaryScreen();
    QSize defaultSize = primaryScreen->availableGeometry().size() * 0.75;

    // Load settings
    QByteArray state = settings.value("State", QByteArray()).toByteArray();
    QByteArray geometry = settings.value("Geometry", QByteArray()).toByteArray();
    bool maximized = settings.value("Maximized", false).toBool();
    QSize size = settings.value("Size", defaultSize).toSize();

    // Restore size/state of main window
    this->restoreState(state);
    this->restoreGeometry(geometry);
    this->resize(size);
    if (maximized)
        this->showMaximized();

    settings.endGroup();
}

void MainWindow::saveSettings()
{
    QSettings settings;
    settings.beginGroup("MainWindow");
    settings.remove("");

    // Save size/state of main window
    settings.setValue("State", this->saveState());
    settings.setValue("Geometry", this->saveGeometry());
    bool maximized = this->isMaximized();
    settings.setValue("Maximized", maximized);
    if (!maximized)
        settings.setValue("Size", this->size());

    settings.endGroup();
}

/****************************************************************************
** Slots
****************************************************************************/
/*
void MainWindow::onCommandActivated(int commandId)
{
    switch (commandId) {
    case IDC_RIBBONHELP:
        showHelp();
        break;
    case IDC_OPEN:
    case IDC_OPENPROJECT:
        openProject();
        break;
    case IDC_VALIDATE:
        validate();
        break;
    case IDC_RUN:
        runModel();
        break;
    case IDC_ANALYZE:
        showAnalysisWindow();
        break;
    case IDC_INPUTFILE:
    case IDC_SOURCETABLE:
    case IDC_RECEPTOREDITOR:
        break;
    case IDC_SELECTALL:
        emit selectAllClicked();
        break;
    case IDC_SELECTNONE:
        emit selectNoneClicked();
        break;
    case IDC_SELECTINVERSE:
        emit selectInverseClicked();
        break;
    default:
        break;
    }
}

void MainWindow::onCommandToggled(int commandId, bool checked)
{
    switch (commandId) {
    case IDC_SHOWPROJECTBROWSER:
        dwProjectTree->setVisible(checked);
        break;
    case IDC_SHOWVALIDATION:
        dwValidation->setVisible(checked);
        break;
    case IDC_SHOWMODELOUTPUT:
        dwOutput->setVisible(checked);
        break;
    default:
        break;
    }
}

void MainWindow::onCurrentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
    Q_UNUSED(previous)

    if (current && treeWidgetToScenario.count(current) > 0) {
        //Scenario *s = treeWidgetToScenario[current];
        setCommandEnabled(IDC_PROPERTIES, true);
        setCommandEnabled(IDC_INPUTFILE, true);
        setCommandEnabled(IDC_SOURCETABLE, false);
        setCommandEnabled(IDC_RECEPTOREDITOR, true);
        setCommandEnabled(IDC_WINDROSE, true);
    }
    else if (current && treeWidgetToSourceGroup.count(current) > 0) {
        //SourceGroup *sg = treeWidgetToSourceGroup[current];
        setCommandEnabled(IDC_PROPERTIES, true);
        setCommandEnabled(IDC_INPUTFILE, false);
        setCommandEnabled(IDC_SOURCETABLE, true);
        setCommandEnabled(IDC_RECEPTOREDITOR, false);
        setCommandEnabled(IDC_WINDROSE, false);
    }
    else {
        setCommandEnabled(IDC_PROPERTIES, false);
        setCommandEnabled(IDC_INPUTFILE, false);
        setCommandEnabled(IDC_SOURCETABLE, false);
        setCommandEnabled(IDC_RECEPTOREDITOR, false);
        setCommandEnabled(IDC_WINDROSE, false);
    }
}
*/
void MainWindow::onItemChanged(QTreeWidgetItem *item, int)
{
    // Handle rename, making sure name is valid.
    QString text = item->text(0); // new text

    // Remove any non-alphanumeric characters and truncate to max length (100)
    text.remove(QRegExp("[^A-Za-z0-9_]+"));
    text.truncate(100);
    item->setText(0, text);

    if (treeWidgetToScenario.count(item) > 0) {
        Scenario *s = treeWidgetToScenario[item];
        s->name = text.toStdString();
        emit scenarioUpdated(s);
        return;
    }

    if (treeWidgetToSourceGroup.count(item) > 0) {
        SourceGroup *sg = treeWidgetToSourceGroup[item];
        sg->grpid = text.toStdString();
        emit sourceGroupUpdated(sg);
        return;
    }
}

void MainWindow::contextMenuRequested(const QPoint& pos)
{
    QPoint globalPos = projectTree->viewport()->mapToGlobal(pos);
    QMenu contextMenu;

    QTreeWidgetItem *item = projectTree->itemAt(pos);
    QAction *selectedItem;

    // Scenario Context Menu
    // TODO: Import Sources
    if (item && treeWidgetToScenario.count(item) > 0)
    {
        Scenario *s = treeWidgetToScenario[item];
        contextMenu.addAction(scenarioCloneAct);
        contextMenu.addAction(scenarioRenameAct);
        contextMenu.addAction(scenarioRemoveAct);
        contextMenu.addSeparator();
        contextMenu.addAction(scenarioAddSourceGroupAct);
        contextMenu.addAction(scenarioImportValidationAct);
        contextMenu.addAction(scenarioFluxFileAct);
        contextMenu.addSeparator();
        contextMenu.addAction(scenarioPropertiesAct);
        contextMenu.addSeparator();
        contextMenu.addAction(receptorEditorAct);
        contextMenu.addAction(elevationEditorAct);
        contextMenu.addSeparator();
        contextMenu.addAction(scenarioInputFileAct);

        selectedItem = contextMenu.exec(globalPos);
        if (selectedItem && selectedItem == scenarioCloneAct) {
            cloneScenario(s);
        }
        else if (selectedItem && selectedItem == scenarioRenameAct) {
            projectTree->editItem(item);
        }
        else if (selectedItem && selectedItem == scenarioRemoveAct) {
            removeScenario(s);
        }
        else if (selectedItem && selectedItem == scenarioAddSourceGroupAct) {
            newSourceGroup(s);
        }
        else if (selectedItem && selectedItem == scenarioImportValidationAct) {
            importValidationData(s);
        }
        else if (selectedItem && selectedItem == scenarioPropertiesAct) {
            showScenarioProperties(s);
        }
        else if (selectedItem && selectedItem == receptorEditorAct) {
            showReceptorEditor(s);
        }
        else if (selectedItem && selectedItem == elevationEditorAct) {
            showElevationEditor(s);
        }
        else if (selectedItem && selectedItem == scenarioInputFileAct) {
            showInputViewer(s);
        }
        else if (selectedItem && selectedItem == scenarioFluxFileAct) {
            exportFluxFile(s);
        }
        return;
    }

    // Source Group Context Menu
    if (item && treeWidgetToSourceGroup.count(item) > 0)
    {
        SourceGroup *sg = treeWidgetToSourceGroup[item];
        contextMenu.addAction(sourceGroupCloneAct);
        contextMenu.addAction(sourceGroupRenameAct);
        contextMenu.addAction(sourceGroupRemoveAct);
        contextMenu.addSeparator();
        contextMenu.addAction(sourceGroupPropertiesAct);
        contextMenu.addSeparator();
        contextMenu.addAction(sourceTableAct);

        selectedItem = contextMenu.exec(globalPos);
        if (selectedItem && selectedItem == sourceGroupCloneAct) {
            cloneSourceGroup(sg);
        }
        else if (selectedItem && selectedItem == sourceGroupRenameAct) {
            projectTree->editItem(item);
        }
        else if (selectedItem && selectedItem == sourceGroupRemoveAct) {
            removeSourceGroup(sg);
        }
        else if (selectedItem && selectedItem == sourceGroupPropertiesAct) {
            showSourceGroupProperties(sg);
        }
        else if (selectedItem && selectedItem == sourceTableAct) {
            showSourceTable(sg);
        }
        return;
    }
}

void MainWindow::onScenarioUpdated(Scenario *s)
{
    projectModified = true;
    if (scenarioToInputViewer.count(s) > 0) {
        InputViewer *viewer = scenarioToInputViewer[s];
        int viewerIndex = centralTabWidget->indexOf(viewer);
        if (viewerIndex >= 0) {
            viewer->refresh();
            QString viewerTitle = QString::fromStdString(s->name);
            centralTabWidget->setTabText(viewerIndex, viewerTitle);
        }
    }
    for (const auto &item : sourceTableToSourceGroup) {
        SourceTable *table = item.first;
        SourceGroup *sg = item.second;
        if (sourceGroupToScenario[sg] == s) {
            table->refresh();
        }
    }
}

void MainWindow::onSourceGroupUpdated(SourceGroup *sg)
{
    projectModified = true;
    if (sourceGroupToSourceTable.count(sg) > 0) {
        SourceTable *table = sourceGroupToSourceTable[sg];
        int tableIndex = centralTabWidget->indexOf(table);
        if (tableIndex >= 0) {
            table->refresh();
            QString tableTitle = QString::fromStdString(sg->grpid);
            centralTabWidget->setTabText(tableIndex, tableTitle);
        }
    }
}

void MainWindow::openProject()
{
    QSettings settings;
    QString openFile;
    QString examplesDir = QDir::cleanPath(qApp->applicationDirPath() + QDir::separator() + "examples");
    QString currentDir = settings.value("DefaultDirectory", examplesDir).toString();
    openFile = QFileDialog::getOpenFileName(this,
                                            tr("Open Project"),
                                            currentDir,
                                            tr("SOFEA Project (*.sofea)"));

    if (openFile.isEmpty())
        return;

    openProjectFile(openFile);
}

void MainWindow::openProjectFile(const QString& openFile)
{
    QFileInfo fi(openFile);
    if (fi.suffix().toLower() != "sofea")
        return;

    // Close current project.
    closeProject();

    // Update the current working directory.
    QString projectDir = fi.canonicalPath();
    QString prevCurrentPath = QDir::currentPath();
    QDir::setCurrent(projectDir);

    // Read the input archive.
    std::ifstream ifs(openFile.toStdString());

    try {
        // Simple check for JSON: first character is '{'
        if (ifs.peek() == 0x7b) {
            cereal::JSONInputArchive ia(ifs);
            ia(scenarios);
        }
        else {
            //cereal::PortableBinaryInputArchive ia(ifs);
            //ia(scenarios);
        }
    } catch (const cereal::Exception &e) {
        QMessageBox::critical(this, "Parse Error", QString::fromLocal8Bit(e.what()));
        QDir::setCurrent(prevCurrentPath);
        return;
    }

    // Update containers and tree.
    for (Scenario &s : scenarios) {
        addScenarioToTree(&s);
        for (auto sgptr : s.sourceGroups) {
            sourceGroupToScenario[sgptr.get()] = &s;
            addSourceGroupToTree(sgptr.get(), &s);
        }
    }

    // Update project file and default directory.
    projectFile = openFile;
    QSettings settings;
    settings.setValue("DefaultDirectory", projectDir);

    projectModified = false;
}

void MainWindow::saveProject()
{
    if (scenarios.size() == 0)
        return;

    // saveAct or saveAsAct
    QAction *sender = qobject_cast<QAction *>(QObject::sender());

    if (projectFile.isEmpty() || sender == saveAsAct) {
        QSettings settings;
        QString saveFile;
        saveFile = QFileDialog::getSaveFileName(this,
                                                tr("Save Project"),
                                                QDir::currentPath(),
                                                tr("SOFEA Project (*.sofea)"));

        if (saveFile.isEmpty())
            return;
    }

    saveProjectFile(projectFile);
}

void MainWindow::saveProjectFile(const QString& saveFile)
{
    if (scenarios.size() == 0)
        return;

    // Update the current working directory.
    QFileInfo fi(saveFile);
    QString projectDir = fi.canonicalPath();
    QString prevCurrentPath = QDir::currentPath();
    QDir::setCurrent(projectDir);

    // Create the output archive.
    std::ofstream ofs(saveFile.toStdString());

    try {
        cereal::JSONOutputArchive oa(ofs, cereal::JSONOutputArchive::Options::NoIndent());
        oa(scenarios);
    } catch (const cereal::Exception &e) {
        QMessageBox::critical(this, "Parse Error", QString::fromLocal8Bit(e.what()));
        QDir::setCurrent(prevCurrentPath);
        return;
    }

    // Update default directory.
    QSettings settings;
    settings.setValue("DefaultDirectory", projectDir);

    projectModified = false;
}

void MainWindow::closeProject()
{
    if (scenarios.size() == 0)
        return;

    if (projectModified) {
        // Get user confirmation.
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Question);
        msgBox.setText("The current project has unsaved changes. Are you sure you want to close?");
        msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgBox.setDefaultButton(QMessageBox::No);

        int rc = msgBox.exec();
        if (rc == QMessageBox::No)
            return;
    }

    // Clear messages.
    lwValidation->clear();
    lwOutput->clear();

    // Remove all scenarios.
    for (auto it = scenarios.rbegin(); it != scenarios.rend(); ++it)
        removeScenario(&(*it));

    // Clear project data.
    projectFile.clear();

    projectModified = false;
}

void MainWindow::exitApplication()
{
    QApplication::closeAllWindows();
}

void MainWindow::newScenario()
{
    Scenario *s = new Scenario();
    scenarios.push_back(s);
    addScenarioToTree(s);
}

void MainWindow::newSourceGroup(Scenario *s)
{
    auto sgptr = std::make_shared<SourceGroup>();
    s->sourceGroups.push_back(sgptr);
    sourceGroupToScenario[sgptr.get()] = s;
    addSourceGroupToTree(sgptr.get(), s);
}

void MainWindow::importValidationData(Scenario *s)
{
    BOOST_LOG_SCOPED_THREAD_TAG("Source", "Import");

    QSettings settings;
    QString csvfile;
    QString examplesDir = QDir::cleanPath(qApp->applicationDirPath() + QDir::separator() + "examples");
    QString currentDir = settings.value("DefaultDirectory", examplesDir).toString();
    csvfile = QFileDialog::getOpenFileName(this,
                                           tr("Import Retrospective Data"),
                                           currentDir,
                                           tr("Retrospective Data (*.csv)"));

    if (csvfile.isEmpty())
        return;

    // Create a new SourceGroup with area sources
    auto sgptr = std::make_shared<SourceGroup>();
    s->sourceGroups.push_back(sgptr);
    sourceGroupToScenario[sgptr.get()] = s;

    // Set validation mode by default
    sgptr->validationMode = true;

    // Read the CSV file
    std::string srcid;
    double xs, ys, xinit, yinit, apprate;
    std::string start;
    double incdepth;

    try {
        io::CSVReader<8> in(csvfile.toStdString());
        in.read_header(io::ignore_extra_column, "srcid", "xs", "ys", "xinit", "yinit", "apprate", "start", "incdepth");
        while (true) {
            bool ok = in.read_row(srcid, xs, ys, xinit, yinit, apprate, start, incdepth);
            if (!ok)
                break;
            AreaSource *as = new AreaSource;
            as->srcid = srcid;
            as->xs = xs;
            as->ys = ys;
            as->xinit = xinit;
            as->yinit = yinit;
            as->appRate = apprate;
            as->appStart = QDateTime::fromString(QString::fromStdString(start), Qt::ISODate);
            as->appStart.setTimeSpec(Qt::UTC);
            as->incorpDepth = incdepth;
            as->setGeometry();
            sgptr->sources.push_back(as);
        }
    }
    catch (const std::exception &e) {
        QMessageBox::critical(this, "Import Failed", QString::fromLocal8Bit(e.what()));
        return;
    }

    addSourceGroupToTree(sgptr.get(), s);
}

void MainWindow::addScenarioToTree(Scenario *s)
{
    QTreeWidgetItem *item = new QTreeWidgetItem;

    scenarioToTreeWidget[s] = item;
    treeWidgetToScenario[item] = s;

    item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
    item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless);
    item->setText(0, QString::fromStdString(s->name));

    QFont rootFont = item->font(0);
    rootFont.setBold(true);
    item->setFont(0, rootFont);

    projectTree->addTopLevelItem(item);

    projectModified = true;
}

void MainWindow::addSourceGroupToTree(SourceGroup *sg, Scenario *s)
{
    QTreeWidgetItem *parent = scenarioToTreeWidget[s];
    QTreeWidgetItem *item = new QTreeWidgetItem;

    sourceGroupToTreeWidget[sg] = item;
    treeWidgetToSourceGroup[item] = sg;

    item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
    item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
    item->setText(0, QString::fromStdString(sg->grpid));

    parent->addChild(item);
    parent->setExpanded(true);

    projectModified = true;
}

void MainWindow::removeScenario(Scenario *s)
{
    if (runModelDialog)
        runModelDialog->removeScenario(s);

    // Remove scenario tabs.
    if (scenarioToInputViewer.count(s) > 0) {
        InputViewer *viewer = scenarioToInputViewer[s];
        int index = centralTabWidget->indexOf(viewer);
        deleteTab(index);
    }

    // Remove all source groups.
    for (auto it = s->sourceGroups.rbegin(); it != s->sourceGroups.rend(); ++it)
        removeSourceGroup((*it).get());

    // Remove from tree.
    QTreeWidgetItem *item = scenarioToTreeWidget[s];
    scenarioToTreeWidget.erase(s);
    treeWidgetToScenario.erase(item);
    delete item;

    // Remove from ptr_vector using erase–remove idiom.
    // Object will be automatically deallocated.
    for (auto it = scenarios.begin(); it != scenarios.end(); ) {
        if (&(*it) == s)
            it = scenarios.erase(it);
        else
            ++it;
    }

    projectModified = true;
}

void MainWindow::removeSourceGroup(SourceGroup *sg)
{
    // Remove tabs.
    if (sourceGroupToSourceTable.count(sg) > 0) {
        SourceTable *table = sourceGroupToSourceTable[sg];
        int index = centralTabWidget->indexOf(table);
        deleteTab(index);
    }

    // Find parent.
    Scenario *s = sourceGroupToScenario.find(sg)->second;

    // Remove from tree.
    QTreeWidgetItem *item = sourceGroupToTreeWidget[sg];
    sourceGroupToTreeWidget.erase(sg);
    treeWidgetToSourceGroup.erase(item);
    delete item;

    // Remove from maps.
    auto it = sourceGroupToScenario.find(sg);
    if (it != sourceGroupToScenario.end())
        sourceGroupToScenario.erase(it);

    // Remove from ptr_vector using erase–remove idiom.
    // Object will be automatically deallocated.
    for (auto it = s->sourceGroups.begin(); it != s->sourceGroups.end(); ) {
        if ((*it).get() == sg)
            it = s->sourceGroups.erase(it);
        else
            ++it;
    }

    projectModified = true;
}

void MainWindow::cloneScenario(Scenario *s)
{
    Scenario *clone = new Scenario(*s); // copy constructor
    scenarios.push_back(clone);
    addScenarioToTree(clone);
    for (auto sgptr : clone->sourceGroups) {
        sourceGroupToScenario[sgptr.get()] = clone;
        addSourceGroupToTree(sgptr.get(), clone);
    }
}

void MainWindow::cloneSourceGroup(SourceGroup *sg)
{
    Scenario *s = sourceGroupToScenario.find(sg)->second;
    auto clone = std::make_shared<SourceGroup>(*sg); // copy constructor
    s->sourceGroups.push_back(clone);
    sourceGroupToScenario[clone.get()] = s;
    addSourceGroupToTree(clone.get(), s);
}

void MainWindow::showScenarioProperties(Scenario *s)
{
    ScenarioProperties *dialog = new ScenarioProperties(s, this);
    connect(dialog, &ScenarioProperties::saved, [&]() {
        emit scenarioUpdated(s);
    });
    dialog->exec();
}

void MainWindow::showSourceGroupProperties(SourceGroup *sg)
{
    Scenario *s = sourceGroupToScenario[sg];
    SourceGroupProperties *dialog = new SourceGroupProperties(s, sg, this);
    connect(dialog, &SourceGroupProperties::saved, [&]() {
        emit scenarioUpdated(s);
        emit sourceGroupUpdated(sg);
    });
    dialog->exec();
}

void MainWindow::showReceptorEditor(Scenario *s)
{
    ReceptorDialog *dialog = new ReceptorDialog(s, this);
    int rc = dialog->exec();
    if (rc == QDialog::Accepted) {
        emit scenarioUpdated(s);
    }
}

void MainWindow::showElevationEditor(Scenario *s)
{
    using namespace Projection;

    if (s->receptors.empty() && s->sourceGroups.empty()) {
        QMessageBox::critical(this, "Error", "No receptors or source groups have been defined.");
        return;
    }

    if (s->conversionCode.empty() ||
        s->hUnitsCode.empty() ||
        s->hDatumCode.empty() ||
        s->vUnitsCode.empty() ||
        s->vDatumCode.empty()) {
        QMessageBox::critical(this, "Error", "Projection is not set.");
        return;
    }

    auto conv = createConversion(s->conversionCode);
    auto gcrs = createGeodeticCRS(s->hDatumCode);
    auto pcrs = createProjectedCRS(gcrs, conv, s->hUnitsCode);
    auto vcrs = createVerticalCRS(s->vDatumCode, s->vUnitsCode);
    auto ccrs = createCompoundCRS(pcrs, vcrs);
    auto area = getObjectAreaOfUse(conv);
    auto pipeline = Pipeline(ccrs, gcrs, area);

    if (!pipeline.valid()) {
        QMessageBox::critical(this, "Error", "Invalid projection.");
        return;
    }

    ReceptorElevationEditor *editor = new ReceptorElevationEditor;
    editor->setCompoundCRS(ccrs);
    editor->setAreaOfUse(area);
    editor->setReceptors(s->receptors);

    editor->show();
}

void MainWindow::showMeteorologyEditor()
{
    if (meteorologyEditor == nullptr) {
        MeteorologyModel *model = new MeteorologyModel(this);
        meteorologyEditor = new MeteorologyEditor(model, this);
    }

    int index = centralTabWidget->indexOf(meteorologyEditor);
    if (index >= 0) {
        centralTabWidget->setCurrentIndex(index);
    }
    else {
        centralTabWidget->addTab(meteorologyEditor, tr("Meteorology"));
        centralTabWidget->setCurrentIndex(centralTabWidget->count() - 1);
    }
}

void MainWindow::showInputViewer(Scenario *s)
{
    if (scenarioToInputViewer.count(s) > 0) {
        InputViewer *viewer = scenarioToInputViewer[s];
        viewer->refresh();
        int index = centralTabWidget->indexOf(viewer);
        if (index >= 0)
            centralTabWidget->setCurrentIndex(index);
    }
    else {
        InputViewer *viewer = new InputViewer(s);
        viewer->refresh();

        QString title = QString::fromStdString(s->name);
        centralTabWidget->addTab(viewer, title);
        centralTabWidget->setCurrentIndex(centralTabWidget->count() - 1);

        scenarioToInputViewer[s] = viewer;
        inputViewerToScenario[viewer] = s;
    }
}

void MainWindow::showSourceTable(SourceGroup *sg)
{
    if (sourceGroupToSourceTable.count(sg) > 0) {
        SourceTable *table = sourceGroupToSourceTable[sg];
        int index = centralTabWidget->indexOf(table);
        if (index >= 0)
            centralTabWidget->setCurrentIndex(index);
    }
    else {
        Scenario *s = sourceGroupToScenario[sg];
        SourceTable *table = new SourceTable(s, sg);

        QString title = QString::fromStdString(sg->grpid);
        centralTabWidget->addTab(table, title);
        centralTabWidget->setCurrentIndex(centralTabWidget->count() - 1);

        sourceGroupToSourceTable[sg] = table;
        sourceTableToSourceGroup[table] = sg;
    }

    connect(sourceGroupToSourceTable[sg], &SourceTable::dataChanged, [=]() {
        projectModified = true;
    });
}

void MainWindow::exportFluxFile(Scenario *s)
{
    QSettings settings;
    QString saveFile;
    saveFile = QFileDialog::getSaveFileName(this,
                                            tr("Export Flux File"),
                                            QDir::currentPath(),
                                            tr("Flux File (*.dat)"));

    if (!saveFile.isEmpty()) {
        s->writeFluxFile(saveFile.toStdString());
    }
}

void MainWindow::deleteTab(int index)
{
    // Remove tab from containers, and schedule the widget for deletion.
    // This will also remove the tab.

    if (index < 0)
        return;

    QWidget *widget = centralTabWidget->widget(index);

    InputViewer *viewer = qobject_cast<InputViewer *>(widget);
    if (viewer != nullptr) {
        Scenario *s = inputViewerToScenario[viewer];
        scenarioToInputViewer.erase(s);
        inputViewerToScenario.erase(viewer);
        widget->deleteLater();
        return;
    }

    SourceTable *table = qobject_cast<SourceTable *>(widget);
    if (table != nullptr) {
        SourceGroup *sg = sourceTableToSourceGroup[table];
        sourceGroupToSourceTable.erase(sg);
        sourceTableToSourceGroup.erase(table);
        widget->deleteLater();
        return;
    }

    centralTabWidget->removeTab(index);
    return;
}

void MainWindow::validate()
{
    lwValidation->clear();
    dwValidation->raise();

    for (Scenario &s : scenarios) {
        auto res = Validation::ValidateScenario(s);
    }
}

void MainWindow::runModel()
{
    if (runModelDialog == nullptr)
        runModelDialog = new RunModelDialog(this);

    for (Scenario &s : scenarios)
        runModelDialog->addScenario(&s);

    connect(runModelDialog, &RunModelDialog::progressValueChanged, progressBar, &ProgressBar::setValue);

    dwOutput->raise();
    runModelDialog->show();
    runModelDialog->raise();
    runModelDialog->activateWindow();
}

void MainWindow::showAnalysisWindow()
{
    if (analysisWindow == nullptr)
        analysisWindow = new AnalysisWindow(this);

    analysisWindow->show();

    // Centering
    analysisWindow->move(window()->frameGeometry().topLeft() +
                         window()->rect().center() -
                         analysisWindow->rect().center());
}

void MainWindow::showHelp()
{
    QDesktopServices::openUrl(QUrl(SOFEA_DOCUMENTATION_URL));
}

void MainWindow::about()
{
    QString aboutText;
    aboutText += "<h3>Soil Fumigant Exposure Assessment (SOFEA) Modeling System</h3>";
    aboutText += QString("<h4>Version %1 <i>ALPHA</i>").arg(SOFEA_VERSION_STRING);
#if _WIN64
    aboutText += " 64-bit</h4>";
#elif _WIN32
    aboutText += " 32-bit</h4>";
#else
    aboutText += "</h4>";
#endif
    aboutText += "<p>© 2004-2020 Dow Inc. All rights reserved.</p>";

    // Compiler Info
    aboutText += QString("Built with MSVC %1").arg(_MSC_VER) +
                 QString(" and Qt %1").arg(QT_VERSION_STR) +
                 QString(" on %1").arg(__DATE__);

    // Dispersion Model Info
    aboutText += "<h4>Model Version Information:</h4>";
    aboutText += QString("AERMOD %1").arg(AERMOD_VERSION_STRING);

    // Contributor Info
    aboutText += "<h4>Developers:</h4>";
    aboutText += "John Buonagurio (jbuonagurio@exponent.com)<br>";
    aboutText += "Steven A. Cryer (steven.cryer@corteva.com)<br>";
    aboutText += "Ian van Wesenbeeck (ian@illahe-environmental.com)";

    QMessageBox::about(this, tr("About SOFEA"), aboutText);
}

/****************************************************************************
** Events
****************************************************************************/

void MainWindow::showEvent(QShowEvent *event)
{
    //setCommandEnabled(IDC_PROPERTIES, false);
    //setCommandEnabled(IDC_INPUTFILE, false);
    //setCommandEnabled(IDC_SOURCETABLE, false);
    //setCommandEnabled(IDC_RECEPTOREDITOR, false);
    //setCommandEnabled(IDC_WINDROSE, false);

    //setCommandChecked(IDC_SHOWPROJECTBROWSER, true);
    //setCommandChecked(IDC_SHOWVALIDATION, true);
    //setCommandChecked(IDC_SHOWMODELOUTPUT, true);

    //setTabGroupContextAvailability(IDC_TABGROUP_SOURCETOOLS, ContextAvailability::Available);

#ifdef Q_OS_WIN
    taskbarButton = new QWinTaskbarButton(this);
    taskbarButton->setWindow(windowHandle());
    taskbarProgress = taskbarButton->progress();
    progressBar->setTaskbarProgress(taskbarProgress);
#endif

    QMainWindow::showEvent(event);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (projectModified) {
        // Get user confirmation.
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Question);
        msgBox.setText("The current project has unsaved changes. Are you sure you want to exit?");
        msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgBox.setDefaultButton(QMessageBox::No);

        int rc = msgBox.exec();
        if (rc == QMessageBox::No) {
            event->ignore();
            return;
        }
    }

    event->accept();
    QMainWindow::closeEvent(event);
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    event->acceptProposedAction();
    QMainWindow::dragEnterEvent(event);
}

void MainWindow::dragMoveEvent(QDragMoveEvent *event)
{
    event->acceptProposedAction();
    QMainWindow::dragMoveEvent(event);
}

void MainWindow::dragLeaveEvent(QDragLeaveEvent *event)
{
    event->accept();
    QMainWindow::dragLeaveEvent(event);
}

void MainWindow::dropEvent(QDropEvent *event)
{
    const QMimeData* mimeData = event->mimeData();

    if (mimeData->hasUrls()) {
        QList<QUrl> urlList = mimeData->urls();
        QString openFile = urlList.first().toLocalFile();
        openProjectFile(openFile);
    }

    event->acceptProposedAction();
    QMainWindow::dropEvent(event);
}

max_stars_repo_name: sofea-model/sofea

max_issues_repo_name: sofea-model/sofea

max_forks_repo_name: sofea-model/sofea


text:
//==================================================================================================
/*!
  @file

  @copyright 2012-2015 NumScale SAS
  @copyright 2015 J.T.Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_SCALAR_IS_NOT_IMAG_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SCALAR_IS_NOT_IMAG_HPP_INCLUDED

#include <boost/simd/function/definition/is_not_imag.hpp>
#include <boost/simd/arch/common/scalar/function/is_not_imag.hpp>
#include <boost/simd/arch/common/generic/function/is_not_imag.hpp>

#endif

ext: hpp


text:
/*
 * StreamSubscriber.cpp
 *
 *  Created on: Dec 6, 2011
 *      Author: ddaeschler
 */

#include "StreamSubscriber.h"

#include <boost/bind.hpp>
#include <iostream>

namespace audioreflector
{
	const int StreamSubscriber::SUBSCRIPTION_TIMEOUT = 30;

	StreamSubscriber::StreamSubscriber(boost::asio::ip::udp::endpoint endPoint,
				boost::asio::ip::udp::socket& socket,
				boost::function<void()> sendCompleteCallback)
	:	_ep(endPoint), _socket(socket),
	 	_lastResubscribe(boost::posix_time::second_clock::local_time()),
	 	_sendCompleteCallback(sendCompleteCallback)
	{

	}

	StreamSubscriber::~StreamSubscriber()
	{

	}

	void StreamSubscriber::updateSubscription()
	{
		_lastResubscribe = boost::posix_time::second_clock::local_time();
	}

	const boost::asio::ip::udp::endpoint& StreamSubscriber::getEndpoint() const
	{
		return	_ep;
	}

	bool StreamSubscriber::isExpired()
	{
		boost::posix_time::time_duration diff = boost::posix_time::second_clock::local_time() - _lastResubscribe;
		return diff.seconds() >= SUBSCRIPTION_TIMEOUT;
	}

	void StreamSubscriber::sendData(packet_buffer_ptr buffer, int amtToCopy)
	{
		_socket.async_send_to(
				boost::asio::buffer(buffer->contents, amtToCopy), _ep,
				boost::bind(&StreamSubscriber::handleSend, this,
				            boost::asio::placeholders::error,
				            boost::asio::placeholders::bytes_transferred,
				            buffer));
	}

	void StreamSubscriber::handleSend(
		      const boost::system::error_code& error,
		      std::size_t /*bytes_transferred*/,
		      packet_buffer_ptr buffer)
	{
		_sendCompleteCallback();
	}
}

ext: cpp

max_stars_repo_name: ddaeschler/audio-reflector

max_issues_repo_name: ddaeschler/audio-reflector

max_forks_repo_name: ddaeschler/audio-reflector


text:
/* Copyright (c) 2019 Xieyuanli Chen, Andres Milioto, Cyrill Stachniss, University of Bonn.
 *
 *  This file is part of rangenet_lib, and covered by the provided LICENSE file.
 *
 */

// opencv stuff
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/viz.hpp>

// c++ stuff
#include <chrono>
#include <iomanip>  // for setfill
#include <iostream>
#include <string>

// net stuff
#include <selector.hpp>
namespace cl = rangenet::segmentation;

// standalone lib h
#include "infer.hpp"

// boost
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;

typedef std::tuple< u_char, u_char, u_char> color;

int main(int argc, const char *argv[]) {
  // define options
  std::string scan;
  std::string path;
  std::string backend = "tensorrt";
  bool verbose = false;

  // Parse options
  try {
    po::options_description desc{"Options"};
    desc.add_options()("help,h", "Help screen")(
        "scan,s", po::value<std::string>(&scan),
        "LiDAR scan to infer. No Default")(
        "path,p", po::value<std::string>(),
        "Directory to get the inference model from. No default")(
        "verbose,v", po::bool_switch(),
        "Verbose mode. Calculates profile (time to run)");

    po::variables_map vm;
    po::store(parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    std::cout << std::setfill('=') << std::setw(80) << "" << std::endl;

    if (vm.count("help")) {
      std::cout << desc << std::endl;
      return 0;
    }

    if (!vm["scan"].empty()) {
      std::cout << "scan: " << scan << std::endl;
    } else {
      std::cerr << "No scan! See --help (-h) for help. Exiting" << std::endl;
      return 1;
    }

    // make defaults count, parameter check, and print
    if (vm.count("path")) {
      path = vm["path"].as<std::string>() + "/";  // make sure path is valid
      std::cout << "path: " << path << std::endl;
    } else {
      std::cerr << "No path! See --help (-h) for help. Exiting" << std::endl;
      return 1;
    }
    if (vm.count("verbose")) {
      verbose = vm["verbose"].as<bool>();
      std::cout << "verbose: " << verbose << std::endl;
    } else {
      std::cout << "verbose: " << verbose << ". Using default!" << std::endl;
    }

    std::cout << std::setfill('=') << std::setw(80) << "" << std::endl;
  } catch (const po::error &ex) {
    std::cerr << ex.what() << std::endl;
    return 1;
  }

  // create a network
  std::unique_ptr<cl::Net> net = cl::make_net(path, backend);

  // set verbosity
  net->verbosity(verbose);

  // predict each image
  std::cout << std::setfill('=') << std::setw(80) << "" << std::endl;
  std::cout << "Predicting image: " << scan << std::endl;

  // Open a scan
  std::ifstream in(scan.c_str(), std::ios::binary);
  if (!in.is_open()) {
      std::cerr << "Could not open the scan!" << std::endl;
      return 1;
  }

  in.seekg(0, std::ios::end);
  uint32_t num_points = in.tellg() / (4 * sizeof(float));
  in.seekg(0, std::ios::beg);

  std::vector<float> values(4 * num_points);
  in.read((char*)&values[0], 4 * num_points * sizeof(float));

  // predict
  std::vector<std::vector<float>> semantic_scan = net->infer(values, num_points);

  // get point cloud
  std::vector<cv::Vec3f> points = net->getPoints(values, num_points);

  // get color mask
  std::vector<cv::Vec3b> color_mask = net->getLabels(semantic_scan, num_points);

  // print the output
  if (verbose) {
    cv::viz::Viz3d window("semantic scan");
    cv::viz::WCloud cloudWidget(points, color_mask);
    while (!window.wasStopped()) {
      window.showWidget("cloud", cloudWidget);
      window.spinOnce(30, true);
    }
  }
  std::cout << std::setfill('=') << std::setw(80) << "" << std::endl;

  std::cout << "Example finished! "<< std::endl;

  return 0;
}


text:
// author chenlu
// version 2020-0115
#include <nodelet/nodelet.h>
#include <pluginlib/class_list_macros.h>

#include <ros/ros.h>
#include <std_msgs/Float32MultiArray.h>
#include <sensor_msgs/PointCloud2.h>
#include <message_filters/subscriber.h>
#include <message_filters/synchronizer.h>
#include <message_filters/sync_policies/approximate_time.h>
#include <std_msgs/Bool.h>
#include <std_msgs/ColorRGBA.h>
#include <geometry_msgs/PoseWithCovarianceStamped.h>
#include <geometry_msgs/Vector3.h>
#include <geometry_msgs/Point.h>
#include <geometry_msgs/Pose.h>
#include <geometry_msgs/PoseStamped.h>
#include <nav_msgs/Odometry.h>
#include <nav_msgs/Path.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>
#include <tf/tf.h>
#include <tf/transform_broadcaster.h>
#include <tf/transform_listener.h>
#include <jsk_recognition_msgs/BoundingBoxArray.h>
#include <jsk_recognition_msgs/BoundingBox.h>

#include <pcl/point_types.h>
#include <pcl/common/common_headers.h>
#include <pcl/impl/point_types.hpp>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_parallel_plane.h>
#include <pcl/sample_consensus/sac_model_line.h>
#include <pcl/sample_consensus/sac_model_parallel_line.h>
#include <pcl/common/intersections.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/visualization/cloud_viewer.h>
// filters
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/statistical_outlier_removal.h>
// features
#include <pcl/features/feature.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/boundary.h>
#include <pcl/search/kdtree.h>
// segment
#include <pcl/PointIndices.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/range_image/range_image.h>
#include <pcl/range_image/range_image_planar.h>
#include <pcl/visualization/range_image_visualizer.h>
#include <pcl/registration/gicp.h>
#include <pcl/registration/icp.h>
#include <pcl/filters/filter.h>

#include <iostream>
#include <Eigen/Dense>

#define PI 3.1415926
//#define DEBUG

namespace pcl
{
  class Pcl : public nodelet::Nodelet{
      public:
          Pcl(){}

      private:
          virtual void onInit(){
              ros::NodeHandle& node = getPrivateNodelHandle();

              readParameters();

              // publisher
              cloud_pub = node.advertise<sensor_msgs::PointCloud2>("cloud", 5);
              odom_pub = node.advertise<nav_msgs::Odometry>("odom", 5);
              passthroughfilter_points_pub = node.advertise<sensor_msgs::PointCloud2>("passthroughfilter_points", 1);
              downsample_points_pub = node.advertise<sensor_msgs::PointCloud2>("downsample_points", 1);
              radiusOutlierRemovalPoints_pub  = node.advertise<sensor_msgs::PointCloud2>("radius_outlier_removal_points", 1);
              radiusOutlierRemovalPoints2_pub  = node.advertise<sensor_msgs::PointCloud2>("radius_outlier_removal_points2", 1);
              referenceCloud_pub = node.advertise<sensor_msgs::PointCloud2>("reference_cloud", 10);
              readingCloud_pub = node.advertise<sensor_msgs::PointCloud2>("reading_cloud", 10);
              alignedCloud_pub = node.advertise<sensor_msgs::PointCloud2>("aligned_cloud", 10);

              boundingBoxCentroidMarker_pub = node.advertise<visualization_msgs::MarkerArray>("bounding_box_centroid", 10);
              boundingBoxCenterMarker_pub = node.advertise<visualization_msgs::MarkerArray>("bounding_box_center", 10);
              boundingBox_pub = node.advertise<jsk_recognition_msgs::BoundingBoxArray>("bounding_box", 10);
              odomVariation_pub = node.advertise<std_msgs::Float32MultiArray>("odom_variation", 10);
              icpPoseVariation_pub = node.advertise<std_msgs::Float32MultiArray>("icp_pose_variation", 10);
              bbxVariation_pub = node.advertise<std_msgs::Float32MultiArray>("bbx_variation", 10);
              fusedVariation_pub = node.advertise<std_msgs::Float32MultiArray>("fused_variation", 10);
              poseVariationDiff_pub = node.advertise<std_msgs::Float32MultiArray>("pose_variation_diff", 10);
              pk2p0_pub = node.advertise<std_msgs::Float32MultiArray>("pk2p0", 20);
              //pk2p0_pub = node.advertise<geometry_msgs::PoseStamped>("pk2p0", 20);

              // subscriber
              cloud_sub = node.subscribe("/bluerov2h_follower/depth/points2", 100, &Pcl::callback, this);

          }

          // ========================================= function
          bool readparameters()
          {
            // 从参数服务器获取参数
            // passthrough filter parameters
            if(!ros::param::get("~passthrough_filter/field_limit_x1", field_limit_x1))
              ROS_WARN_STREAM("could not get field_limit_x1 from parameterserver!");

            if(!ros::param::get("~passthrough_filter/field_limit_x2", field_limit_x2))
              ROS_WARN_STREAM("could not get field_limit_x2 from parameterserver!");

            if(!ros::param::get("~passthrough_filter/field_limit_y1", field_limit_y1))
              ROS_WARN_STREAM("could not get field_limit_y1 from parameterserver!");

            if(!ros::param::get("~passthrough_filter/field_limit_y2", field_limit_y2))
              ROS_WARN_STREAM("could not get field_limit_y2 from parameterserver!");

            if(!ros::param::get("~passthrough_filter/field_limit_z1", field_limit_z1))
              ROS_WARN_STREAM("could not get field_limit_z1 from parameterserver!");

            if(!ros::param::get("~passthrough_filter/field_limit_z2", field_limit_z2))
              ROS_WARN_STREAM("could not get field_limit_z2 from parameterserver!");

            // downsample filter parameters
            if(!ros::param::get("~downsample/leaf_size", leaf_size))
              ROS_WARN_STREAM("could not get leaf_size from parameterserver!");

            // SACSegmentation
            if(!ros::param::get("~SACSegmentation/setOptimizeCoefficients", setOptimizeCoefficients))
              ROS_WARN_STREAM("could not get setOptimizeCoefficients from parameterserver!");

            // RadiusOutlierRemoval
            if(!ros::param::get("~RadiusOutlierRemoval/setRadiusSearch", setRadiusSearch))
              ROS_WARN_STREAM("could not get setRadiusSearch from parameterserver!");

            if(!ros::param::get("~RadiusOutlierRemoval/setMinNeighborsInRadius", setMinNeighborsInRadius))
              ROS_WARN_STREAM("could not get setMinNeighborsInRadius from parameterserver!");

            if(!ros::param::get("~RadiusOutlierRemoval/setRadiusSearch2", setRadiusSearch2))
              ROS_WARN_STREAM("could not get setRadiusSearch2 from parameterserver!");

            if(!ros::param::get("~RadiusOutlierRemoval/setMinNeighborsInRadius2", setMinNeighborsInRadius2))
              ROS_WARN_STREAM("could not get setMinNeighborsInRadius2 from parameterserver!");

            // topic name
            if(!ros::param::get("~Topics/cloudTopic", cloudTopic))
              ROS_WARN_STREAM("could not get cloudTopic from parameterserver!");
            //ROS_INFO_STREAM("cloudTopic: " << cloudTopic);

            // cluster
            if(!ros::param::get("~cluster/clusterTolerance", clusterTolerance))
              ROS_WARN_STREAM("could not get clusterTolerance from parameterserver!");
            if(!ros::param::get("~cluster/clusterMinSize", clusterMinSize))
              ROS_WARN_STREAM("could not get clusterMinSize from parameterserver!");
            if(!ros::param::get("~cluster/clusterMaxSize", clusterMaxSize))
              ROS_WARN_STREAM("could not get clusterMaxSize from parameterserver!");

            //ROS_WARN_STREAM("normal_kdtree_searchradius: " << normal_kdtree_searchradius << "  normal_kdtree_searchnum: " << normal_kdtree_searchnum);

            return true;
          }

          void passthrough_filter(pcl::PointCloud<pcl::PointXYZ>::Ptr &points,
              double field_limit_x1, double field_limit_x2, double field_limit_y1, double field_limit_y2, double field_limit_z1, double field_limit_z2,
              pcl::PointCloud<pcl::PointXYZ>::Ptr &passthrough_filter_out)
          {
            pcl::PassThrough<pcl::PointXYZ> pass_filter;

            // X方向滤波
            // 首先，在机器人前进方向（x方向）进行滤波，设置一个ROI区域,距离机器人太远的和太近的点云都将被滤除掉
            if(field_limit_x1 != 0.0 && field_limit_x2 != 0.0)
            {
              pass_filter.setInputCloud(points);

              pass_filter.setFilterFieldName("x");
              pass_filter.setFilterLimits(field_limit_x1, field_limit_x2); // ！根据真实场景调整参数

              pass_filter.filter(*passthrough_filter_out);
            }

            // Y方向滤波
            if(field_limit_y1 != 0.0 && field_limit_y2 != 0.0)
            {
              if(field_limit_x1 != 0.0 && field_limit_x2 != 0.0)
                pass_filter.setInputCloud(passthrough_filter_out);
              else
                pass_filter.setInputCloud(points);

              pass_filter.setFilterFieldName("y");
              pass_filter.setFilterLimits(field_limit_y1, field_limit_y2); // ！根据真实场景调整参数

              pass_filter.filter(*passthrough_filter_out);
            }

            // Z方向滤波
            // 然后，在高度方向（z方向）进行滤波，将地面点云滤除掉
            if(field_limit_z1 != 0.0 && field_limit_z2 != 0.0)
            {
              if( (field_limit_x1 != 0.0 && field_limit_x2 != 0.0) || (field_limit_y1 != 0.0 && field_limit_y2 != 0.0) )
                pass_filter.setInputCloud(passthrough_filter_out);
              else
                pass_filter.setInputCloud(points);

              pass_filter.setFilterFieldName("z");
              pass_filter.setFilterLimits(field_limit_z1, field_limit_z2); // ！根据真实场景调整参数

              pass_filter.filter(*passthrough_filter_out);
            }

          }

          void downsample(pcl::PointCloud<pcl::PointXYZ>::Ptr &points,
              double leaf_size,
              pcl::PointCloud<pcl::PointXYZ>::Ptr &downsample_out)
          {
            pcl::VoxelGrid<pcl::PointXYZ> voxcelgrid_filter;

            voxcelgrid_filter.setInputCloud(points);

            voxcelgrid_filter.setLeafSize(leaf_size, leaf_size, leaf_size); // ！根据真实场景调整参数

            voxcelgrid_filter.filter(*downsample_out);
          }

        // ========================================= struct
        struct Dectected_Obj
        {
          jsk_recognition_msgs::BoundingBox boundingBox_;
          pcl::PointXYZ minPoint_;
          pcl::PointXYZ maxPoint_;
          pcl::PointXYZ centroid_;
          //pcl::PointXYZ center_;
          Eigen::Vector3f center_;
        };
          std::vector<Dectected_Obj> clusterSegmentBoundingBox(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,
              double clusterTolerance, double clusterMinSize, double clusterMaxSize)
          {
              std::cout<<"\ncluster segment Bounding Box......"<<std::endl;
              pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
              std::vector<pcl::PointIndices> clusterIndices;
              pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;

              ros::Time startTime = ros::Time::now();
              ec.setClusterTolerance(clusterTolerance);
              ec.setMinClusterSize(clusterMinSize);
              ec.setMaxClusterSize(clusterMaxSize);
              ec.setSearchMethod(tree);
              ec.setInputCloud(cloud);
              ec.extract(clusterIndices);
              ROS_INFO_STREAM("cluster numbers: " << clusterIndices.size());
              ros::Time endTime1 = ros::Time::now();
              ROS_INFO_STREAM("time1 = " << endTime1 - startTime);

              std::vector<Dectected_Obj> objList;
              for(size_t i=0; i<clusterIndices.size(); i++)
              {
                  Dectected_Obj detectedObj;

                  float minX = std::numeric_limits<float>::max();
                  float maxX = -std::numeric_limits<float>::max();
                  float minY = std::numeric_limits<float>::max();
                  float maxY = -std::numeric_limits<float>::max();
                  float minZ = std::numeric_limits<float>::max();
                  float maxZ = -std::numeric_limits<float>::max();

                  // for(std::vector<int>::const_iterator pit = it)
                  for(auto pit = clusterIndices[i].indices.begin(); pit != clusterIndices[i].indices.end(); ++pit)
                  {
                    pcl::PointXYZ p;
                    p.x = cloud->points[*pit].x;
                    p.y = cloud->points[*pit].y;
                    p.z = cloud->points[*pit].z;

                    detectedObj.centroid_.x += p.x;
                    detectedObj.centroid_.y += p.y;
                    detectedObj.centroid_.z += p.z;

                    if(p.x < minX) minX = p.x;
                    if(p.y < minY) minY = p.y;
                    if(p.z < minZ) minZ = p.z;
                    if(p.x > maxX) maxX = p.x;
                    if(p.y > maxY) maxY = p.y;
                    if(p.z > maxZ) maxZ = p.z;
                  }

                  // fill detected object information
                  // detectedObj.boundingBox_.header = cloud->header;
                  detectedObj.boundingBox_.header.seq = cloud->header.seq;
                  // detectedObj.boundingBox_.header.stamp = cloud->header.stamp;
                  detectedObj.boundingBox_.header.frame_id = cloud->header.frame_id;

                  detectedObj.minPoint_.x = minX;
                  detectedObj.minPoint_.y = minY;
                  detectedObj.minPoint_.z = minZ;
                  detectedObj.maxPoint_.x = maxX;
                  detectedObj.maxPoint_.y = maxY;
                  detectedObj.maxPoint_.z = maxZ;

                  // mass centroid
                  if(clusterIndices[i].indices.size() > 0)
                  {
                    detectedObj.centroid_.x /= clusterIndices[i].indices.size();
                    detectedObj.centroid_.y /= clusterIndices[i].indices.size();
                    detectedObj.centroid_.z /= clusterIndices[i].indices.size();
                  }

                  // shape center
                  Eigen::Vector4f minP1, maxP1;
                  pcl::PointXYZ minP1_1, maxP1_1;
                  pcl::getMinMax3D(*cloud, clusterIndices[i], minP1, maxP1);
                  minP1_1.x = minP1[0];
                  minP1_1.y = minP1[1];
                  minP1_1.z = minP1[2];
                  maxP1_1.x = maxP1[0];
                  maxP1_1.y = maxP1[1];
                  maxP1_1.z = maxP1[2];
                  detectedObj.center_ = 0.5f * ( minP1_1.getVector3fMap() + maxP1_1.getVector3fMap() );

                  // bounding box
                  double length = detectedObj.maxPoint_.x - detectedObj.minPoint_.x;
                  double width = detectedObj.maxPoint_.y - detectedObj.minPoint_.y;
                  double height = detectedObj.maxPoint_.z - detectedObj.minPoint_.z;

                  detectedObj.boundingBox_.pose.position.x = detectedObj.minPoint_.x + length/2;
                  detectedObj.boundingBox_.pose.position.y = detectedObj.minPoint_.y + width/2;
                  detectedObj.boundingBox_.pose.position.z = detectedObj.minPoint_.z + height/2;

                  detectedObj.boundingBox_.dimensions.x = ((length<0) ? -1*length : length);
                  detectedObj.boundingBox_.dimensions.y = ((width<0) ? -1*width : width);
                  detectedObj.boundingBox_.dimensions.z = ((height<0) ? -1*height : height);

                  objList.push_back(detectedObj);
                  ros::Time endTime2 = ros::Time::now();
                  ROS_INFO_STREAM("time2 = " << endTime2 - startTime);
              }
              std::cout<<"cluster segment Bounding Box end.\n"<<std::endl;

              return(objList);
          }

          Eigen::Matrix4f updateICP()
          {
              // G-ICP
              pcl::GeneralizedIterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
              icp.setTransformationEpsilon(0.01);
              icp.setMaxCorrespondenceDistance(0.3);
              icp.setMaximumIterations(100);
              icp.setRANSACIterations(0);

              // ICP
              // pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;

              icp.setInputSource(source);
              icp.setInputTarget(reference);
              //pcl::PointCloud<pcl::PointXYZ> alignedCloud;
              icp.align(*alignedCloud);

              if(icp.hasConverged())
              {
                std::cout << "\nICP has converged, score= " << icp.getFitnessScore() << std::endl;
              }

              Eigen::Matrix4f transM = icp.getFinalTransformation();
              //std::cout << "icp transformation = " << std::endl << icp.getFinalTransformation() << std::endl;
              return transM;
          }

          void odomCallback(const nav_msgs::OdometryConstPtr& odomIn)
          {
            if(odomStart==true || cloudDone==true){

              odomTime = odomIn->header.stamp.toSec();
              odomX = odomIn->pose.pose.position.x;
              odomY = odomIn->pose.pose.position.y;
              odomZ = odomIn->pose.pose.position.z;
            }
            odomStart = false;
            cloudDone = false;

          /*   odomCloud.data.push_back(time);
            odomCloud.data.push_back(odomIn->pose.pose.position.x);
            odomCloud.data.push_back(odomIn->pose.pose.position.y);
            odomCloud.data.push_back(odomIn->pose.pose.position.z); */
          }

          /*
            get a stablized object's initial pose in point_cloud frame.
          */
          bool initialization(std_msgs::Float32MultiArray delta_P, Eigen::Vector3f p_k)
          {

              float delta_X = 0.0, delta_Y = 0.0, delta_Z=0.0;
              delta_X = delta_P.data[0];
              delta_Y = delta_P.data[1];
              delta_Z = delta_P.data[2];

              bool if_X = 0, if_Y = 0, if_Z = 0;
              if(delta_X<threshold_X)
                if_X = 1;
              if(delta_Y<threshold_Y)
                if_Y = 1;
              if(delta_Z<threshold_Z)
                if_Z = 1;

              if(if_X==1 && if_Y==1 && if_Z==1)
              {
                ++frameNum;
                pSum[0] = pSum[0] + p_k[0];
                pSum[1] = pSum[1] + p_k[1];
                pSum[2] = pSum[2] + p_k[2];
              }
              else
              {
                frameNum = 0;
                pSum[0] = 0.0;
                pSum[1] = 0.0;
                pSum[2] = 0.0;
              }

              if(frameNum > frameNumSet)
              {
                pMean[0] = pSum[0]/frameNum;
                pMean[1] = pSum[1]/frameNum;
                pMean[2] = pSum[2]/frameNum;

                return 1;
              }

              return 0;
          }

          // void callback(const sensor_msgs::PointCloud2ConstPtr& cloudIn, const nav_msgs::OdometryConstPtr& odomIn)
          void callback(const sensor_msgs::PointCloud2ConstPtr& cloudIn)
          {
              std::cout << "" << std::endl;
              std::cout << "************************************" << std::endl;

              //std::cout.precision(3);
              std::cout<<std::fixed<<std::setprecision(3)<<std::endl;

          /*     std::cout << "cloud timestamp=" << cloudIn->header.stamp << "  odom timestamp=" << odomIn->header.stamp << std::endl;
              std::cout << "time diff=" << cloudIn->header.stamp - odomIn->header.stamp << std::endl; */

              sensor_msgs::PointCloud2 cloudOut;
          /*     nav_msgs::Odometry odomOut;

              odomOut.header = odomIn->header;
              odomOut.child_frame_id = odomIn->child_frame_id;
              odomOut.pose = odomIn->pose;
              odomOut.twist = odomIn->twist;
              odom_pub.publish(odomOut); */

            pcl::fromROSMsg(*cloudIn, *cloud);     // sensor_msgs::PointCloud2类型转换为pcl::PointCloud<pcl::PointXYZ>类型
            //ROS_INFO_STREAM("initial cloud information    points size: " << cloud->points.size() << "  frame id: " << cloud->header.frame_id << "  sensor acquisition pose: " << cloud->sensor_origin[0] << " " << cloud->sensor_origin[1] << " " << cloud->sensor_origin[2] << " " << cloud->sensor_origin[3]);
            cloudFilterSucceed = true;
            if(0 == cloud->points.size())
            {
              ROS_ERROR("ERROR! input cloud size is 0 !");
              cloudFilterSucceed = false;
              //return;
            }
            ROS_INFO_STREAM("initial cloud information    points size: " << cloud->points.size() << "  frame id: " << cloud->header.frame_id);

            ros::Time beginTime = ros::Time::now();
            // ============================================== pass filter and downsample
            if((field_limit_x1 != 0.0 && field_limit_x2 != 0.0) || (field_limit_y1 != 0.0 && field_limit_y2 != 0.0) || (field_limit_z1 != 0.0 && field_limit_z2 != 0.0))  // passfilter before downsample
            {

                  field_limit_x1 = field_limit_x1 - odomPoseChanged.position.x;
                  field_limit_x2 = field_limit_x2 - odomPoseChanged.position.x;
                //ROS_INFO_STREAM("passthrough filter limits x: " << field_limit_x1 << " " << field_limit_x2 << " y: " << field_limit_y1 << " " << field_limit_y2 << " z: " << field_limit_z1 << " " << field_limit_z2);
                //delta_x_filter = 0.0;

                // pass filter
                //ROS_INFO_STREAM("------- passthrough filter start.");
                passthrough_filter(cloud, field_limit_x1, field_limit_x2, field_limit_y1, field_limit_y2, field_limit_z1, field_limit_z2, passthoughfilter_points);
                if(0 == passthoughfilter_points->points.size()) // 直接跳出回调函数
                {
                  ROS_ERROR_STREAM("ERROR! passthoughfilter points size is 0!");
                  cloudFilterSucceed = false;
                  //return;
                }
                //ROS_INFO_STREAM("passthoughfilter points size: " << passthoughfilter_points->points.size());
                //ROS_INFO_STREAM("------- passthrough filter finished.");

                sensor_msgs::PointCloud2 passthroughfilter_points_rosmsg;
                pcl::toROSMsg(*passthoughfilter_points, passthroughfilter_points_rosmsg);
                passthroughfilter_points_pub.publish(passthroughfilter_points_rosmsg);

                // voxelgrid downsample
                //ROS_INFO_STREAM("------- downsample start.");
                downsample(passthoughfilter_points, leaf_size, downsample_points);
            }
            else  // downsample without passfilter
            {
                //ROS_INFO_STREAM("------- downsample start.");
                downsample(cloud, leaf_size, downsample_points);
            }
            //ROS_INFO_STREAM("downsample points size: " << downsample_points->points.size());
            //ROS_INFO_STREAM("------- downsample finished.");

            // publish downsample pointcloud
            sensor_msgs::PointCloud2 downsample_points_rosmsg;
            pcl::toROSMsg(*downsample_points, downsample_points_rosmsg);
            downsample_points_pub.publish(downsample_points_rosmsg);

            // ============================================== outlier radius_outlier_removal
            sensor_msgs::PointCloud2 radiusOutlierRemovalPointsRosMsg;

            pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;
              // first filter
            outrem.setInputCloud(downsample_points);
            outrem.setRadiusSearch(setRadiusSearch);
            outrem.setMinNeighborsInRadius(setMinNeighborsInRadius);
            outrem.filter(*radiusOutlierRemovalPoints);
            if(0 == radiusOutlierRemovalPoints->points.size())
            {
              ROS_ERROR("ERROR! radiusOutlierRemovalPoints1 size is 0!");
              cloudFilterSucceed = false;
              //return;
            }
            pcl::toROSMsg(*radiusOutlierRemovalPoints, radiusOutlierRemovalPointsRosMsg);
            radiusOutlierRemovalPoints_pub.publish(radiusOutlierRemovalPointsRosMsg);

              // second filter
            outrem.setInputCloud(radiusOutlierRemovalPoints);
            outrem.setRadiusSearch(setRadiusSearch2);
            outrem.setMinNeighborsInRadius(setMinNeighborsInRadius2);
            outrem.filter(*radiusOutlierRemovalPoints2);
            if(0 == radiusOutlierRemovalPoints2->points.size())
            {
              ROS_ERROR("ERROR! radiusOutlierRemovalPoints2 size is 0!");
              cloudFilterSucceed = false;
              //return;
            }
            pcl::toROSMsg(*radiusOutlierRemovalPoints2, radiusOutlierRemovalPointsRosMsg);
            radiusOutlierRemovalPoints2_pub.publish(radiusOutlierRemovalPointsRosMsg);
              ROS_INFO_STREAM("cloudFilterSucceed: " << cloudFilterSucceed);

            ros::Time endTime1 = ros::Time::now();
            ROS_INFO_STREAM("time1 = " << endTime1 - beginTime);

            // ============================================== cluster and bounding box
              std::vector<Dectected_Obj> objList;
              objList = clusterSegmentBoundingBox(radiusOutlierRemovalPoints2, clusterTolerance, clusterMinSize, clusterMaxSize);
              ROS_WARN_STREAM("object number: " << objList.size());

              ros::Time endTime2 = ros::Time::now();
              ROS_INFO_STREAM("time2 = " << endTime2 - beginTime);

              jsk_recognition_msgs::BoundingBoxArray boundingBoxArray;
              for(size_t i=0; i<objList.size(); i++)
              {
                  boundingBoxArray.boxes.push_back(objList[i].boundingBox_);
              }
              // boundingBoxArray.header = cloud->header;
              boundingBoxArray.header.seq = cloud->header.seq;
              boundingBoxArray.header.frame_id = cloud->header.frame_id;
              boundingBox_pub.publish(boundingBoxArray);

              // pub markers:  centroid of clustered cloud(equal to the "centroid of cloud" computed from compute3DCentroid().)
              visualization_msgs::MarkerArray boundingBoxCentroidArray, boundingBoxCenterArray;
              visualization_msgs::Marker boundingBoxCentroid, boundingBoxCenter;
              // 1. centroid
              boundingBoxCentroid.type = visualization_msgs::Marker::SPHERE;
              boundingBoxCentroid.action = visualization_msgs::Marker::ADD;
              boundingBoxCentroid.lifetime = ros::Duration(0);
              boundingBoxCentroid.header = boundingBoxArray.header;
              // boundingBoxCentroid.header.frame_id = cloud->header.frame_id; //"/map";
              // boundingBoxCentroid.header.stamp = cloud->header.stamp; //ros::Time::now();
              boundingBoxCentroid.scale.x = boundingBoxCentroid.scale.y = boundingBoxCentroid.scale.z = 0.05;
              boundingBoxCentroid.color.r = 1.0;
              boundingBoxCentroid.color.g = 1.0;
              boundingBoxCentroid.color.a = 0.8;
              boundingBoxCentroid.pose.orientation.w = 1.0;
              // 2. center
              boundingBoxCenter.type = visualization_msgs::Marker::SPHERE;
              boundingBoxCenter.action = visualization_msgs::Marker::ADD;
              boundingBoxCenter.lifetime = ros::Duration(0);
              boundingBoxCenter.header = boundingBoxArray.header;
              // boundingBoxCenter.header.frame_id = cloud->header.frame_id; //"/map";
              // boundingBoxCenter.header.stamp = cloud->header.stamp; //ros::Time::now();
              boundingBoxCenter.scale.x = boundingBoxCenter.scale.y = boundingBoxCenter.scale.z = 0.05;
              boundingBoxCenter.color.g = 1.0;
              boundingBoxCenter.color.a = 0.8;
              boundingBoxCenter.pose.orientation.w = 1.0;

              int markerID = 0;
              boundingBoxCentroidArray.markers.clear();
              boundingBoxCenterArray.markers.clear();
              for(size_t i=0; i<objList.size(); i++)
              {
                  boundingBoxCentroid.id = markerID;
                  boundingBoxCentroid.pose.position.x = objList[i].centroid_.x; // in point cloud(camera) frame.
                  boundingBoxCentroid.pose.position.y = objList[i].centroid_.y;
                  boundingBoxCentroid.pose.position.z = objList[i].centroid_.z;
                  boundingBoxCentroidArray.markers.push_back(boundingBoxCentroid);
                  boundingBoxCentroid.pose.position.x = objList[i].centroid_.z;  // in base_link frame.
                  boundingBoxCentroid.pose.position.y = -objList[i].centroid_.x;
                  boundingBoxCentroid.pose.position.z = -objList[i].centroid_.y;
                  boundingBoxCenter.id = markerID;
                  boundingBoxCenter.pose.position.x = objList[i].center_.x(); // in point cloud(camera) frame.
                  boundingBoxCenter.pose.position.y = objList[i].center_.y();
                  boundingBoxCenter.pose.position.z = objList[i].center_.z();
                  boundingBoxCenterArray.markers.push_back(boundingBoxCenter);
                  boundingBoxCenter.pose.position.x = objList[i].center_.z(); // in base_link frame.
                  boundingBoxCenter.pose.position.y = -objList[i].center_.x();
                  boundingBoxCenter.pose.position.z = -objList[i].center_.y();
                  // std::cout<<"\nBBX center(cloud_frame)=\n"<<objList[i].center_<<std::endl;
                  // std::cout<<"BBX centroid(cloud_frame)=\n"<<objList[i].centroid_<<std::endl;
                  // std::cout<<"BBX center(base_frame)=\n"<<boundingBoxCenter.pose.position<<std::endl;
                  // std::cout<<"BBX centroid(base_frame)=\n"<<boundingBoxCentroid.pose.position<<std::endl;

                  ++markerID;
              }
              boundingBoxCentroidMarker_pub.publish(boundingBoxCentroidArray);
              boundingBoxCenterMarker_pub.publish(boundingBoxCenterArray);

              ros::Time endTime3 = ros::Time::now();
              ROS_INFO_STREAM("time3 = " << endTime3 - beginTime);

            // ============================================== icp
              *incloud = *radiusOutlierRemovalPoints2;
              if(!incloud->is_dense)
              {
                  ROS_WARN("input cloud is_dense=false!");
                  std::vector<int> indices;
                  pcl::removeNaNFromPointCloud(*incloud, *incloudFiltered, indices);
              }
              else
              {
                  *incloudFiltered = *incloud;
              }

              // K1 & K2
              if(!isInitialized)
              {
                  pcl::copyPointCloud(*incloudFiltered, *source);

                  odomTK2 = odomTCurrent;
                  odomRK2 = odomRCurrent;

                  bbxTK2[0] = boundingBoxCenter.pose.position.x;
                  bbxTK2[1] = boundingBoxCenter.pose.position.y;
                  bbxTK2[2] = boundingBoxCenter.pose.position.z;

                  isInitialized = true;
                  return;
              }

              bbxTK1 = bbxTK2;
              bbxTK2[0] = boundingBoxCenter.pose.position.x;
              bbxTK2[1] = boundingBoxCenter.pose.position.y;
              bbxTK2[2] = boundingBoxCenter.pose.position.z;

              // bbx variation
              std_msgs::Float32MultiArray bbxVariation;
              bbxVariation.data.push_back(bbxTK2[0] - bbxTK1[0]);
              bbxVariation.data.push_back(bbxTK2[1] - bbxTK1[1]);
              bbxVariation.data.push_back(bbxTK2[2] - bbxTK1[2]);
              bbxVariation_pub.publish(bbxVariation);
              std::cout << "\nBBX position variation=\n" << bbxVariation.data[0] << "  " << bbxVariation.data[1] << "  " << bbxVariation.data[2] << std::endl;

              // pose initialization
              if(initialized == false)
              {
                  std::cout<<"\npose initialization......"<<std::endl;
                  if(initialization(bbxVariation, bbxTK2))
                  {
                    initialized = true;
                    initialPose = pMean;
                    ROS_WARN_STREAM("initial pose =\n" << initialPose);
                    std::cout<<"pose initialized succeed!\n"<<std::endl;
                  }
                  else
                  {
                    return;
                  }
              }

              //std_msgs::Float32MultiArray pk2p0_Msg;
              geometry_msgs::PoseStamped pk2p0_Msg;
              Eigen::Vector3f pk2p0;
              pk2p0[0] = boundingBoxCenter.pose.position.x - initialPose[0];
              pk2p0[1] = boundingBoxCenter.pose.position.y - initialPose[1];
              pk2p0[2] = boundingBoxCenter.pose.position.z - initialPose[2];
              /*
              pk2p0_Msg.data.push_back(pk2p0[0]);
              pk2p0_Msg.data.push_back(pk2p0[1]);
              pk2p0_Msg.data.push_back(pk2p0[2]);
              pk2p0_Msg.data.push_back(ros::Time::now().toSec());
              */
          /*     pk2p0_Msg.pose.position.x = pk2p0[0];
              pk2p0_Msg.pose.position.y = pk2p0[1];
              pk2p0_Msg.pose.position.z = pk2p0[2]; */
              std_msgs::Float32MultiArray odomCloud;
              odomCloud.data.push_back(odomTime);
              odomCloud.data.push_back(odomX);
              odomCloud.data.push_back(odomY);
              odomCloud.data.push_back(odomZ);
              odomCloud.data.push_back(cloudIn->header.stamp.toSec());
              odomCloud.data.push_back(pk2p0[0]);
              odomCloud.data.push_back(pk2p0[1]);
              odomCloud.data.push_back(pk2p0[2]);
              pk2p0_pub.publish(odomCloud);
              cloudDone = true;
              //pk2p0_pub.publish(pk2p0_Msg);

              ROS_ERROR_STREAM("cloud time = "<<ros::Time::now().toSec()-cloudIn->header.stamp.toSec());
              ROS_ERROR_STREAM("total time = "<<ros::Time::now().toSec()-beginTime.toSec());

              // time
              ros::Time endTime = ros::Time::now();
              ROS_INFO_STREAM("total time = " << endTime - beginTime);
              std::cout<<"ROS time now ="<<endTime<<std::endl;
              std::cout << "\n" << std::endl;
          }

        // ========================================= publisher
        ros::Publisher passthroughfilter_points_pub;
        ros::Publisher downsample_points_pub;
        ros::Publisher radiusOutlierRemovalPoints_pub, radiusOutlierRemovalPoints2_pub;
        ros::Publisher boundingBox_pub, boundingBoxCentroidMarker_pub, boundingBoxCenterMarker_pub;
        ros::Publisher cloud_pub, odom_pub;
        ros::Publisher referenceCloud_pub, readingCloud_pub, alignedCloud_pub;
        ros::Publisher fusedVariation_pub, bbxVariation_pub, icpPoseVariation_pub, odomVariation_pub, poseVariationDiff_pub, pk2p0_pub;
        // ========================================= subscriber
        ros::Subscriber cloud_sub;

        // ========================================= global variables
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>), reference(new pcl::PointCloud<pcl::PointXYZ>), source(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr passthoughfilter_points(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr downsample_points(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr radiusOutlierRemovalPoints (new pcl::PointCloud<pcl::PointXYZ>), radiusOutlierRemovalPoints2 (new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr incloud(new pcl::PointCloud<pcl::PointXYZ>), incloudFiltered(new pcl::PointCloud<pcl::PointXYZ>);
        //pcl::PointCloud<pcl::PointXYZ>::Ptr source(new pcl::PointCloud<pcl::PointXYZ>), reference(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr alignedCloud(new pcl::PointCloud<pcl::PointXYZ>);

        static geometry_msgs::Point odomInitialPose, odomCurrentCalledPose, odomPreviousCalledPose;
        static geometry_msgs::Point pointcloud_initial_pose, cloud_current_pose, cloud_previous_pose;
        geometry_msgs::Pose odomPoseChanged, pointcloudPoseChanged, poseChangedBias, poseChangedBias_current, poseChangedBias_previous;
        geometry_msgs::Pose odomPoseDelta, odomPoseCurrent, odomPosePrevious, odomPoseAccumulate;
        std::string odomPoseTopic, cloudTopic;
        bool cloudFilterSucceed = false;
        int pointcloudPoseChangedMsgID = 0;
        bool isInitialized = false;

        Eigen::Vector3d odomTCurrent;
        Eigen::Vector4d odomRCurrent;
        Eigen::Vector4d odomRK1, odomRK2; // quatertion
        Eigen::Vector3d odomTK1, odomTK2; // translation
        Eigen::Vector3d odomREK1, odomREK2; // euler angle
        Eigen::Vector3f bbxTK1, bbxTK2;

        float threshold_X=0.03, threshold_Y=0.03, threshold_Z = 0.03;
        int frameNumSet = 10;
        int frameNum = 0;
        Eigen::Vector3f pSum, pMean; // x,y,z
        bool initialized = false;
        Eigen::Vector3f initialPose;

        float odomTime, odomX, odomY, odomZ;
        bool odomStart = true, cloudDone = false;
        // ========================================= parameters
        double field_limit_x1, field_limit_x2, field_limit_y1, field_limit_y2, field_limit_z1, field_limit_z2;
        // downsample
        double leaf_size;
        // SACSegmentation
        int model_type;
        int method_type;
        int horizontalplane_method_type, horizontalplane_model_type, verticalplane_method_type, verticalplane_model_type;
        bool setOptimizeCoefficients;
        double horizontalplane_eps_angle_in_degreer, horizontalplane_setDistanceThreshold, verticalplane_eps_angle_in_degreer, verticalplane_setDistanceThreshold;
        double setRadiusSearch, setMinNeighborsInRadius, setRadiusSearch2, setMinNeighborsInRadius2;
        // cluster
        double clusterTolerance, clusterMinSize, clusterMaxSize;

  }

  PLUGINLIB_EXPORT_CLASS(pcl_nodelet::Pcl, nodelet::Nodelet)
}