Skip to content
Snippets Groups Projects
moldyn.cpp 32.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Tobias WEBER's avatar
    Tobias WEBER committed
     * atom dynamics
    
     * @author Tobias Weber <tweber@ill.fr>
     * @date Dec-2019
     * @license GPLv3, see 'LICENSE' file
     */
    
    
    #include "moldyn.h"
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    #include <QtWidgets/QApplication>
    
    #include <QtWidgets/QGridLayout>
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    #include <QtWidgets/QFileDialog>
    
    #include <QtWidgets/QSpinBox>
    
    #include <QtWidgets/QComboBox>
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    #include <QtWidgets/QMessageBox>
    
    #include <QtWidgets/QProgressDialog>
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    #include <iostream>
    #include <tuple>
    
    #include <memory>
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    #include "libs/algos.h"
    #include "libs/helper.h"
    
    using namespace m_ops;
    
    constexpr t_real g_eps = 1e-6;
    constexpr int g_prec = 6;
    
    
    
    #define PROG_NAME "Molecular Dynamics Tool"
    
    
    
    
    // ----------------------------------------------------------------------------
    /**
     * File dialog with options
     */
    class MolDynFileDlg : public QFileDialog
    {
    	public:
    		MolDynFileDlg(QWidget *parent, const QString& title, const QString& dir, const QString& filter)
    			: QFileDialog(parent, title, dir, filter)
    		{
    			// options panel with frame skip
    			QLabel *labelFrameSkip = new QLabel("Frame Skip: ", this);
    			m_spinFrameSkip = new QSpinBox(this);
    			m_spinFrameSkip->setValue(10);
    
    			m_spinFrameSkip->setSingleStep(1);
    			m_spinFrameSkip->setRange(0, 9999999);
    
    
    			labelFrameSkip->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Fixed});
    			m_spinFrameSkip->setSizePolicy(QSizePolicy{QSizePolicy::Expanding, QSizePolicy::Fixed});
    
    			QWidget *pPanel = new QWidget();
    			auto pPanelGrid = new QGridLayout(pPanel);
    			pPanelGrid->setSpacing(2);
    			pPanelGrid->setContentsMargins(4,4,4,4);
    
    			pPanelGrid->addWidget(labelFrameSkip, 0,0,1,1);
    			pPanelGrid->addWidget(m_spinFrameSkip, 0,1,1,1);
    
    			// add the options panel to the layout
    			setOptions(QFileDialog::DontUseNativeDialog);
    			QGridLayout *pGrid = reinterpret_cast<QGridLayout*>(layout());
    			if(pGrid)
    				pGrid->addWidget(pPanel, pGrid->rowCount(), 0, 1, pGrid->columnCount());
    		}
    
    
    		int GetFrameSkip() const
    		{
    			return m_spinFrameSkip->value();
    		}
    
    
    	private:
    		QSpinBox *m_spinFrameSkip = nullptr;
    };
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    // ----------------------------------------------------------------------------
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    // ----------------------------------------------------------------------------
    MolDynDlg::MolDynDlg(QWidget* pParent) : QMainWindow{pParent},
    	m_sett{new QSettings{"tobis_stuff", "moldyn"}}
    {
    
    	setWindowTitle(PROG_NAME);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	this->setObjectName("moldyn");
    
    	m_status = new QStatusBar(this);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	m_statusAtoms = new QLabel(m_status);
    	m_status->addPermanentWidget(m_statusAtoms);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	this->setStatusBar(m_status);
    
    
    
    	QWidget *pMainPanel = new QWidget();
    	auto pMainGrid = new QGridLayout(pMainPanel);
    	pMainGrid->setSpacing(2);
    	pMainGrid->setContentsMargins(4,4,4,4);
    	this->setCentralWidget(pMainPanel);
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	// menu bar
    	{
    		m_menu = new QMenuBar(this);
    		m_menu->setNativeMenuBar(m_sett ? m_sett->value("native_gui", false).toBool() : false);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		// File
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		auto menuFile = new QMenu("File", m_menu);
    
    		auto acNew = new QAction("New", menuFile);
    		auto acLoad = new QAction("Load...", menuFile);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		auto acSaveAs = new QAction("Save As...", menuFile);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		auto acExit = new QAction("Exit", menuFile);
    
    		menuFile->addAction(acNew);
    		menuFile->addSeparator();
    		menuFile->addAction(acLoad);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		menuFile->addAction(acSaveAs);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		menuFile->addSeparator();
    		menuFile->addAction(acExit);
    
    		connect(acNew, &QAction::triggered, this, &MolDynDlg::New);
    		connect(acLoad, &QAction::triggered, this, &MolDynDlg::Load);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		connect(acSaveAs, &QAction::triggered, this, &MolDynDlg::SaveAs);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		connect(acExit, &QAction::triggered, this, &QDialog::close);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    		// Edit
    
    		auto menuEdit = new QMenu("Edit", m_menu);
    
    		auto acSelectAll = new QAction("Select All", menuEdit);
    
    		auto acSelectNone = new QAction("Select None", menuEdit);
    
    
    		menuEdit->addAction(acSelectAll);
    
    		menuEdit->addAction(acSelectNone);
    
    
    		connect(acSelectAll, &QAction::triggered, this, &MolDynDlg::SelectAll);
    
    		connect(acSelectNone, &QAction::triggered, this, &MolDynDlg::SelectNone);
    
    
    		// Calculate
    		auto menuCalc = new QMenu("Calculate", m_menu);
    
    		auto acCalcDist = new QAction("Distance Between Selected Atoms...", menuEdit);
    		auto acCalcPos = new QAction("Positions Of Selected Atoms...", menuEdit);
    		auto acCalcDeltaDist = new QAction("Distances to Initial Position of Selected Atoms...", menuEdit);
    
    
    		menuCalc->addAction(acCalcDist);
    
    		menuCalc->addAction(acCalcPos);
    		menuCalc->addAction(acCalcDeltaDist);
    
    
    		connect(acCalcDist, &QAction::triggered, this, &MolDynDlg::CalculateDistanceBetweenAtoms);
    
    		connect(acCalcPos, &QAction::triggered, this, &MolDynDlg::CalculatePositionsOfAtoms);
    		connect(acCalcDeltaDist, &QAction::triggered, this, &MolDynDlg::CalculateDeltaDistancesOfAtoms);
    
    
    		// Help
    		auto menuHelp = new QMenu("Help", m_menu);
    		auto acHelpInfo = new QAction("Infos...", menuHelp);
    
    		menuHelp->addAction(acHelpInfo);
    
    		connect(acHelpInfo, &QAction::triggered, this, [this]()
    		{
    			QString strHelp;
    
    			strHelp = QString{PROG_NAME} + ", part of the Takin 2 package.\n";
    			strHelp += "Written by Tobias Weber <tweber@ill.fr>\nin December 2019.\n\n";
    
    			strHelp += "This program is free software: you can redistribute it and/or modify "
    				"it under the terms of the GNU General Public License as published by "
    				"the Free Software Foundation, version 3 of the License.\n\n"
    				"This program is distributed in the hope that it will be useful, "
    				"but WITHOUT ANY WARRANTY; without even the implied warranty of "
    				"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
    				"GNU General Public License for more details.\n\n"
    				"You should have received a copy of the GNU General Public License "
    				"along with this program.  If not, see <http://www.gnu.org/licenses/>.";
    
    			QMessageBox::information(this, PROG_NAME, strHelp);
    		});
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		m_menu->addMenu(menuFile);
    
    		m_menu->addMenu(menuEdit);
    		m_menu->addMenu(menuCalc);
    
    		m_menu->addMenu(menuHelp);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		this->setMenuBar(m_menu);
    	}
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	// context menus
    	{
    		m_atomContextMenu = new QMenu(this);
    		m_atomContextMenu->setTitle("Atoms");
    		m_atomContextMenu->addAction("Delete Atom", this, &MolDynDlg::DeleteAtomUnderCursor);
    		m_atomContextMenu->addAction("Delete All Atoms Of Selected Type", this, &MolDynDlg::DeleteAllAtomsOfSameType);
    		m_atomContextMenu->addAction("Only Keep Atoms Of Selected Type", this, &MolDynDlg::KeepAtomsOfSameType);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		m_atomContextMenu->addSeparator();
    		m_atomContextMenu->addAction("Select All Atoms of Same Type", this, &MolDynDlg::SelectAtomsOfSameType);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	// plot widget
    	{
    		m_plot = new GlPlot(this);
    		m_plot->setSizePolicy(QSizePolicy{QSizePolicy::Expanding, QSizePolicy::Expanding});
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		m_plot->GetImpl()->EnablePicker(1);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		m_plot->GetImpl()->SetLight(0, m::create<t_vec3_gl>({ 5, 5, 5 }));
    		m_plot->GetImpl()->SetLight(1, m::create<t_vec3_gl>({ -5, -5, -5 }));
    		m_plot->GetImpl()->SetCoordMax(1.);
    		m_plot->GetImpl()->SetCamBase(m::create<t_mat_gl>({1,0,0,0,  0,0,1,0,  0,-1,0,-1.5,  0,0,0,1}),
    			m::create<t_vec_gl>({1,0,0,0}), m::create<t_vec_gl>({0,0,1,0}));
    
    		connect(m_plot, &GlPlot::AfterGLInitialisation, this, &MolDynDlg::AfterGLInitialisation);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		connect(m_plot, &GlPlot::GLInitialisationFailed, this, &MolDynDlg::GLInitialisationFailed);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		connect(m_plot->GetImpl(), &GlPlot_impl::PickerIntersection, this, &MolDynDlg::PickerIntersection);
    		connect(m_plot, &GlPlot::MouseDown, this, &MolDynDlg::PlotMouseDown);
    		connect(m_plot, &GlPlot::MouseUp, this, &MolDynDlg::PlotMouseUp);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		connect(m_plot, &GlPlot::MouseClick, this, &MolDynDlg::PlotMouseClick);
    
    		//this->setCentralWidget(m_plot);
    
    		pMainGrid->addWidget(m_plot, 0,0,1,9);
    
    		auto labCoordSys = new QLabel("Coordinates:", this);
    		auto labFrames = new QLabel("Frames:", this);
    		auto labScale = new QLabel("Scale:", this);
    		labCoordSys->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Fixed});
    		labFrames->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Fixed});
    		labScale->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Fixed});
    
    		auto comboCoordSys = new QComboBox(this);
    		comboCoordSys->addItem("Fractional Units (rlu)");
    		comboCoordSys->addItem("Lab Units (A)");
    		comboCoordSys->setFocusPolicy(Qt::StrongFocus);
    
    		m_spinScale = new QDoubleSpinBox(this);
    		m_spinScale->setDecimals(4);
    		m_spinScale->setRange(1e-4, 1e4);
    		m_spinScale->setSingleStep(0.1);
    		m_spinScale->setValue(0.4);
    		m_spinScale->setFocusPolicy(Qt::StrongFocus);
    
    
    		m_slider = new QSlider(Qt::Horizontal, this);
    		m_slider->setSizePolicy(QSizePolicy{QSizePolicy::Expanding, QSizePolicy::Minimum});
    		m_slider->setMinimum(0);
    
    		m_slider->setSingleStep(1);
    		m_slider->setPageStep(10);
    
    		m_slider->setTracking(1);
    
    		m_slider->setFocusPolicy(Qt::StrongFocus);
    
    
    		connect(m_slider, &QSlider::valueChanged, this, &MolDynDlg::SliderValueChanged);
    
    		connect(comboCoordSys, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int val)
    		{
    			if(this->m_plot)
    				this->m_plot->GetImpl()->SetCoordSys(val);
    		});
    		connect(m_spinScale, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [this](double val)
    		{
    			if(!this->m_plot) return;
    
    			// hack to trigger update
    			SliderValueChanged(m_slider->value());
    		});
    
    		pMainGrid->addWidget(labCoordSys, 1,0,1,1);
    		pMainGrid->addWidget(comboCoordSys, 1,1,1,1);
    		pMainGrid->addWidget(labScale, 1,2,1,1);
    		pMainGrid->addWidget(m_spinScale, 1,3,1,1);
    		pMainGrid->addWidget(labFrames, 1,4,1,1);
    		pMainGrid->addWidget(m_slider, 1,5,1,4);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	}
    
    
    	// restore window size and position
    	if(m_sett && m_sett->contains("geo"))
    		restoreGeometry(m_sett->value("geo").toByteArray());
    	else
    		resize(600, 500);
    
    	m_ignoreChanges = 0;
    }
    
    
    
    // ----------------------------------------------------------------------------
    /**
    
     * add an atom
    
    Tobias WEBER's avatar
    Tobias WEBER committed
     */
    
    std::size_t MolDynDlg::Add3DItem(const t_vec& vec, const t_vec& col, t_real scale, const std::string& label)
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	auto obj = m_plot->GetImpl()->AddLinkedObject(m_sphere, 0,0,0, col[0],col[1],col[2],1);
    
    	Change3DItem(obj, &vec, &col, &scale, &label);
    
    	return obj;
    
    
    
    /**
     * change an atom
     */
    void MolDynDlg::Change3DItem(std::size_t obj, const t_vec *vec, const t_vec *col, const t_real *scale, const std::string *label)
    {
    
    	if(vec)
    	{
    		t_mat_gl mat = m::hom_translation<t_mat_gl>((*vec)[0], (*vec)[1], (*vec)[2]);
    		if(scale) mat *= m::hom_scaling<t_mat_gl>(*scale, *scale, *scale);
    		m_plot->GetImpl()->SetObjectMatrix(obj, mat);
    	}
    
    
    	if(col) m_plot->GetImpl()->SetObjectCol(obj, (*col)[0], (*col)[1], (*col)[2], 1);
    	if(label) m_plot->GetImpl()->SetObjectLabel(obj, *label);
    	if(label) m_plot->GetImpl()->SetObjectDataString(obj, *label);
    }
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    // ----------------------------------------------------------------------------
    
    
    
    
    // ----------------------------------------------------------------------------
    /**
     * calculate the distance between selected atoms
     */
    void MolDynDlg::CalculateDistanceBetweenAtoms()
    {
    
    		// get selected atoms
    		std::vector<std::tuple<std::size_t, std::size_t>> objs;
    
    		for(const auto& obj : m_sphereHandles)
    		{
    			// continue if object isn't selected
    			if(!m_plot->GetImpl()->GetObjectHighlight(obj))
    				continue;
    
    			// get indices for selected atoms
    			const auto [bOk, atomTypeIdx, atomSubTypeIdx, sphereIdx] = GetAtomIndexFromHandle(obj);
    			if(!bOk)
    			{
    				QMessageBox::critical(this, PROG_NAME, "Atom handle not found, data is corrupted.");
    				return;
    			}
    
    			objs.push_back(std::make_tuple(atomTypeIdx, atomSubTypeIdx));
    		}
    
    		if(objs.size() <= 1)
    
    			QMessageBox::critical(this, PROG_NAME, "At least two atoms have to be selected.");
    
    		// create file
    		QString dirLast = m_sett->value("dir", "").toString();
    		QString filename = QFileDialog::getSaveFileName(this, "Save File", dirLast, "Data File (*.dat)");
    		if(filename == "")
    			return;
    
    		std::ofstream ofstr(filename.toStdString());
    		if(!ofstr)
    		{
    			QMessageBox::critical(this, PROG_NAME, "Cannot open file.");
    			return;
    		}
    
    		ofstr.precision(g_prec);
    		m_sett->setValue("dir", QFileInfo(filename).path());
    
    
    		// get coordinates of first atom
    		auto [firstObjTypeIdx, firstObjSubTypeIdx] = objs[0];
    		auto firstObjCoords = m_mol.GetAtomCoords(firstObjTypeIdx, firstObjSubTypeIdx);
    
    
    		ofstr << "# Column 1: Frame\n";
    		ofstr << "# Columns 2, 3, ...: Distances to first atom (A)\n";
    
    		// progress dialog
    		std::shared_ptr<QProgressDialog> dlgProgress = std::make_shared<QProgressDialog>(
    			"Calculating...", "Cancel", 1, objs.size(), this);
    		dlgProgress->setWindowModality(Qt::WindowModal);
    
    		// get distances to other selected atoms
    		for(std::size_t objIdx=1; objIdx<objs.size(); ++objIdx)
    		{
    			auto [objTypeIdx, objSubTypeIdx] = objs[objIdx];
    			const auto objCoords = m_mol.GetAtomCoords(objTypeIdx, objSubTypeIdx);
    
    			for(std::size_t frameidx=0; frameidx<m_mol.GetFrameCount(); ++frameidx)
    			{
    				t_real dist = m::get_dist_uc(m_crystA, firstObjCoords[frameidx], objCoords[frameidx]);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    				ofstr
    
    					<< std::left << std::setw(g_prec*1.5) << frameidx << " "
    					<< std::left << std::setw(g_prec*1.5) << dist << "\n";
    			}
    
    			dlgProgress->setValue(objIdx+1);
    			if(dlgProgress->wasCanceled())
    			{
    				ofstr << "\n# WARNING: Calculation aborted by user.\n";
    				break;
    			}
    		}
    	}
    	catch(const std::exception& ex)
    
    		QMessageBox::critical(this, PROG_NAME, ex.what());
    
    }
    
    
    
    /**
     * calculate positions of selected atoms
     */
    void MolDynDlg::CalculatePositionsOfAtoms()
    {
    	try
    	{
    		// get selected atoms
    		std::vector<std::tuple<std::size_t, std::size_t>> objs;
    
    		for(const auto& obj : m_sphereHandles)
    		{
    			// continue if object isn't selected
    			if(!m_plot->GetImpl()->GetObjectHighlight(obj))
    				continue;
    
    			// get indices for selected atoms
    			const auto [bOk, atomTypeIdx, atomSubTypeIdx, sphereIdx] = GetAtomIndexFromHandle(obj);
    			if(!bOk)
    			{
    				QMessageBox::critical(this, PROG_NAME, "Atom handle not found, data is corrupted.");
    				return;
    			}
    
    			objs.push_back(std::make_tuple(atomTypeIdx, atomSubTypeIdx));
    		}
    
    		if(objs.size() <= 0)
    		{
    			QMessageBox::critical(this, PROG_NAME, "At least one atom has to be selected.");
    			return;
    		}
    
    
    		// create file
    		QString dirLast = m_sett->value("dir", "").toString();
    		QString filename = QFileDialog::getSaveFileName(this, "Save File", dirLast, "Data File (*.dat)");
    		if(filename == "")
    			return;
    
    		std::ofstream ofstr(filename.toStdString());
    		if(!ofstr)
    		{
    			QMessageBox::critical(this, PROG_NAME, "Cannot open file.");
    			return;
    		}
    
    		ofstr.precision(g_prec);
    		m_sett->setValue("dir", QFileInfo(filename).path());
    
    		ofstr << "# Column 1: Frame\n";
    		ofstr << "# Columns 2, 3, 4: x, y, z position of atom  (A)\n";
    
    		// progress dialog
    		std::shared_ptr<QProgressDialog> dlgProgress = std::make_shared<QProgressDialog>(
    			"Calculating...", "Cancel", 0, m_mol.GetFrameCount(), this);
    		dlgProgress->setWindowModality(Qt::WindowModal);
    
    		// iterate all selected atoms
    		for(std::size_t frameidx=0; frameidx<m_mol.GetFrameCount(); ++frameidx)
    		{
    			ofstr << std::left << std::setw(g_prec*1.5) << frameidx << " ";
    
    			for(std::size_t objIdx=0; objIdx<objs.size(); ++objIdx)
    			{
    				auto [objTypeIdx, objSubTypeIdx] = objs[objIdx];
    				const t_vec& coords = m_mol.GetAtomCoords(objTypeIdx, objSubTypeIdx, frameidx);
    
    				ofstr 
    					<< std::left << std::setw(g_prec*1.5) << coords[0] << " "
    					<< std::left << std::setw(g_prec*1.5) << coords[1] << " "
    					<< std::left << std::setw(g_prec*1.5) << coords[2] << "  ";
    			}
    
    			ofstr << "\n";
    
    			dlgProgress->setValue(frameidx+1);
    			if(dlgProgress->wasCanceled())
    			{
    				ofstr << "\n# WARNING: Calculation aborted by user.\n";
    				break;
    			}
    		}
    	}
    	catch(const std::exception& ex)
    
    		QMessageBox::critical(this, PROG_NAME, ex.what());
    	}
    }
    
    
    
    /**
     * calculate distance to initial positions of selected atoms
     */
    void MolDynDlg::CalculateDeltaDistancesOfAtoms()
    {
    	try
    	{
    		// get selected atoms
    		std::vector<std::tuple<std::size_t, std::size_t>> objs;
    
    		for(const auto& obj : m_sphereHandles)
    		{
    			// continue if object isn't selected
    			if(!m_plot->GetImpl()->GetObjectHighlight(obj))
    				continue;
    
    			// get indices for selected atoms
    			const auto [bOk, atomTypeIdx, atomSubTypeIdx, sphereIdx] = GetAtomIndexFromHandle(obj);
    			if(!bOk)
    			{
    				QMessageBox::critical(this, PROG_NAME, "Atom handle not found, data is corrupted.");
    				return;
    			}
    
    			objs.push_back(std::make_tuple(atomTypeIdx, atomSubTypeIdx));
    		}
    
    		if(objs.size() <= 0)
    		{
    			QMessageBox::critical(this, PROG_NAME, "At least one atom has to be selected.");
    			return;
    		}
    
    
    		// create file
    		QString dirLast = m_sett->value("dir", "").toString();
    		QString filename = QFileDialog::getSaveFileName(this, "Save File", dirLast, "Data File (*.dat)");
    		if(filename == "")
    			return;
    
    		std::ofstream ofstr(filename.toStdString());
    		if(!ofstr)
    		{
    			QMessageBox::critical(this, PROG_NAME, "Cannot open file.");
    			return;
    		}
    
    		ofstr.precision(g_prec);
    		m_sett->setValue("dir", QFileInfo(filename).path());
    
    
    		ofstr << "# Column 1: Frame\n";
    		ofstr << "# Column 2, 3, ...: Distance delta (A)\n";
    
    		// progress dialog
    		std::shared_ptr<QProgressDialog> dlgProgress = std::make_shared<QProgressDialog>(
    			"Calculating...", "Cancel", 0, m_mol.GetFrameCount(), this);
    		dlgProgress->setWindowModality(Qt::WindowModal);
    
    		// iterate all selected atoms
    
    		for(std::size_t frameidx=0; frameidx<m_mol.GetFrameCount(); ++frameidx)
    		{
    
    			ofstr << std::left << std::setw(g_prec*1.5) << frameidx << " ";
    
    			for(std::size_t objIdx=0; objIdx<objs.size(); ++objIdx)
    			{
    				auto [objTypeIdx, objSubTypeIdx] = objs[objIdx];
    			 	const t_vec& coords = m_mol.GetAtomCoords(objTypeIdx, objSubTypeIdx, frameidx);
    				const t_vec& coordsInitial = m_mol.GetAtomCoords(objTypeIdx, objSubTypeIdx, 0);
    
    				t_real dist = m::get_dist_uc(m_crystA, coords, coordsInitial);
    
    				ofstr << std::left << std::setw(g_prec*1.5) << dist << " ";
    			}
    
    			ofstr << "\n";
    
    			dlgProgress->setValue(frameidx+1);
    			if(dlgProgress->wasCanceled())
    			{
    				ofstr << "\n# WARNING: Calculation aborted by user.\n";
    				break;
    			}
    
    	catch(const std::exception& ex)
    	{
    		QMessageBox::critical(this, PROG_NAME, ex.what());
    	}
    
    }
    // ----------------------------------------------------------------------------
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    // ----------------------------------------------------------------------------
    void MolDynDlg::New()
    {
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	m_mol.Clear();
    
    
    	for(const auto& obj : m_sphereHandles)
    		m_plot->GetImpl()->RemoveObject(obj);
    
    
    	m_sphereHandles.clear();
    
    	m_slider->setValue(0);
    
    	m_plot->update();
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    }
    
    
    void MolDynDlg::Load()
    {
    
    	if(!m_plot) return;
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	try
    	{
    		QString dirLast = m_sett->value("dir", "").toString();
    
    		auto filedlg = std::make_shared<MolDynFileDlg>(this, "Load File", dirLast, "Molecular Dynamics File (*)");
    		if(!filedlg->exec())
    			return;
    		auto files = filedlg->selectedFiles();
    		if(!files.size())
    			return;
    
    		QString filename = files[0];
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		if(filename == "" || !QFile::exists(filename))
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    			return;
    		m_sett->setValue("dir", QFileInfo(filename).path());
    
    
    		New();
    
    		std::shared_ptr<QProgressDialog> dlgProgress = std::make_shared<QProgressDialog>(
    			"Loading \"" + QFileInfo(filename).fileName() + "\"...", "Cancel", 0, 1000, this);
    		bool bCancelled = 0;
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		auto progressHandler = [dlgProgress, &bCancelled](t_real percentage) -> bool
    
    		{
    			dlgProgress->setValue(int(percentage*10));
    			bCancelled = dlgProgress->wasCanceled();
    			return !bCancelled;
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		};
    		m_mol.SubscribeToLoadProgress(progressHandler);
    
    		dlgProgress->setWindowModality(Qt::WindowModal);
    
    
    		if(!m_mol.LoadFile(filename.toStdString(), filedlg->GetFrameSkip()))
    
    			// only show error if not explicitely cancelled
    			if(!bCancelled)
    
    				QMessageBox::critical(this, PROG_NAME, "Error loading file.");
    
    			return;
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    		m_mol.UnsubscribeFromLoadProgress(&progressHandler);
    
    		m_slider->setMaximum(m_mol.GetFrameCount() - 1);
    
    		// crystal A and B matrices
    		const t_vec& _a = m_mol.GetBaseA();
    		const t_vec& _b = m_mol.GetBaseB();
    		const t_vec& _c = m_mol.GetBaseC();
    
    		m_crystA = m::create<t_mat>({
    			_a[0],	_b[0],	_c[0],
    			_a[1],	_b[1],	_c[1],
    			_a[2], 	_b[2],	_c[2] });
    
    		bool ok = true;
    		std::tie(m_crystB, ok) = m::inv(m_crystA);
    		if(!ok)
    		{
    			m_crystB = m::unit<t_mat>();
    
    			QMessageBox::critical(this, PROG_NAME, "Error: Cannot invert A matrix.");
    
    		}
    
    		m_crystB /= t_real_gl(2)*m::pi<t_real_gl>;
    		t_mat_gl matA{m_crystA};
    		m_plot->GetImpl()->SetBTrafo(m_crystB, &matA);
    
    		std::cout << "A matrix: " << m_crystA << ", \n"
    			<< "B matrix: " << m_crystB << "." << std::endl;
    
    
    
    		// atom colors
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		std::vector<t_vec> cols =
    		{
    			m::create<t_vec>({1, 0, 0}),
    			m::create<t_vec>({0, 0, 1}),
    			m::create<t_vec>({0, 0.5, 0}),
    			m::create<t_vec>({0, 0.5, 0.5}),
    			m::create<t_vec>({0.5, 0.5, 0}),
    			m::create<t_vec>({0, 0, 0}),
    		};
    
    
    		// add atoms to 3d view
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		if(m_mol.GetFrameCount())
    		{
    			const auto& frame = m_mol.GetFrame(0);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    			m_sphereHandles.reserve(frame.GetNumAtomTypes());
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    			for(std::size_t atomidx=0; atomidx<frame.GetNumAtomTypes(); ++atomidx)
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    			{
    				const auto& coords = frame.GetCoords(atomidx);
    				for(const t_vec& vec : coords)
    
    					t_real atomscale = m_spinScale->value();
    					std::size_t handle = Add3DItem(vec, cols[atomidx % cols.size()], atomscale, m_mol.GetAtomName(atomidx));
    
    					m_sphereHandles.push_back(handle);
    				}
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    			}
    		}
    	}
    	catch(const std::exception& ex)
    	{
    
    		QMessageBox::critical(this, PROG_NAME, ex.what());
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	UpdateAtomsStatusMsg();
    
    	m_plot->update();
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    void MolDynDlg::SaveAs()
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	try
    	{
    		QString dirLast = m_sett->value("dir", "").toString();
    		QString filename = QFileDialog::getSaveFileName(this, "Save File", dirLast, "Molecular Dynamics File (*)");
    		if(filename == "")
    			return;
    		m_sett->setValue("dir", QFileInfo(filename).path());
    
    
    		std::shared_ptr<QProgressDialog> dlgProgress = std::make_shared<QProgressDialog>(
    			"Saving \"" + QFileInfo(filename).fileName() + "\"...", "Cancel", 0, 1000, this);
    		bool bCancelled = 0;
    		auto progressHandler = [dlgProgress, &bCancelled](t_real percentage) -> bool
    		{
    			dlgProgress->setValue(int(percentage*10));
    			bCancelled = dlgProgress->wasCanceled();
    			return !bCancelled;
    		};
    		m_mol.SubscribeToSaveProgress(progressHandler);
    		dlgProgress->setWindowModality(Qt::WindowModal);
    
    
    		if(!m_mol.SaveFile(filename.toStdString()))
    		{
    
    			QMessageBox::critical(this, PROG_NAME, "Error saving file.");
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		}
    
    
    		m_mol.UnsubscribeFromSaveProgress(&progressHandler);
    	}
    	catch(const std::exception& ex)
    	{
    
    		QMessageBox::critical(this, PROG_NAME, ex.what());
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	}
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    }
    // ----------------------------------------------------------------------------
    
    
    
    // ----------------------------------------------------------------------------
    /**
     * mouse hovers over 3d object
     */
    void MolDynDlg::PickerIntersection(const t_vec3_gl* pos, std::size_t objIdx, const t_vec3_gl* posSphere)
    {
    
    	if(!m_plot) return;
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	if(pos)
    		m_curPickedObj = long(objIdx);
    	else
    		m_curPickedObj = -1;
    
    
    	if(m_curPickedObj > 0)
    	{
    
    		const std::string& label = m_plot->GetImpl()->GetObjectDataString(m_curPickedObj);
    		SetStatusMsg(label);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		SetStatusMsg("");
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	}
    }
    
    
    
    /**
     * set status label text in 3d dialog
     */
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    void MolDynDlg::SetStatusMsg(const std::string& msg)
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    {
    	if(!m_status) return;
    	m_status->showMessage(msg.c_str());
    }
    
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    void  MolDynDlg::UpdateAtomsStatusMsg()
    {
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	if(!m_statusAtoms || !m_slider) return;
    
    	// Atoms
    	std::string numAtoms = std::to_string(m_mol.GetNumAtomsTotal()) + " atoms.";
    
    	// Selected
    	if(m_plot)
    	{
    		std::size_t numSelected = 0;
    
    		for(auto handle : m_sphereHandles)
    			if(m_plot->GetImpl()->GetObjectHighlight(handle))
    				++numSelected;
    
    		numAtoms += " " + std::to_string(numSelected) + " selected.";
    	}
    
    	// Frames
    	numAtoms += " Frame " + std::to_string(m_slider->value()+1) + " of " + std::to_string(m_mol.GetFrameCount()) + ".";
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    	m_statusAtoms->setText(numAtoms.c_str());
    }
    
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    /**
     * mouse button pressed
     */
    void MolDynDlg::PlotMouseDown(bool left, bool mid, bool right)
    {
    
    	if(!m_plot) return;
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	if(left && m_curPickedObj > 0)
    	{
    
    		m_plot->GetImpl()->SetObjectHighlight(m_curPickedObj, !m_plot->GetImpl()->GetObjectHighlight(m_curPickedObj));
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		UpdateAtomsStatusMsg();
    
    		m_plot->update();
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	}
    }
    
    
    /**
     * mouse button released
     */
    void MolDynDlg::PlotMouseUp(bool left, bool mid, bool right)
    {
    }
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    
    /**
     * mouse button clicked
     */
    void MolDynDlg::PlotMouseClick(bool left, bool mid, bool right)
    {
    	// show context menu
    	if(right && m_curPickedObj > 0)
    	{
    		QString atomLabel = m_plot->GetImpl()->GetObjectDataString(m_curPickedObj).c_str();
    		m_atomContextMenu->actions()[0]->setText("Delete This \"" + atomLabel + "\" Atom");
    		m_atomContextMenu->actions()[1]->setText("Delete All \"" + atomLabel + "\" Atoms");
    		m_atomContextMenu->actions()[2]->setText("Delete All But \"" + atomLabel + "\" Atoms");
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    		m_atomContextMenu->actions()[4]->setText("Select All \"" + atomLabel + "\" Atoms");
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    
    		auto ptGlob = QCursor::pos();
    		ptGlob.setY(ptGlob.y() + 8);
    		m_atomContextMenu->popup(ptGlob);
    	}
    }
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    // ----------------------------------------------------------------------------
    
    
    
    /**
     * select all atoms
     */
    void MolDynDlg::SelectAll()
    {
    	if(!m_plot) return;
    
    	for(auto handle : m_sphereHandles)
    		m_plot->GetImpl()->SetObjectHighlight(handle, 1);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	UpdateAtomsStatusMsg();
    
    	m_plot->update();
    }
    
    
    
    /**
     * unselect all atoms
     */
    void MolDynDlg::SelectNone()
    {
    	if(!m_plot) return;
    
    	for(auto handle : m_sphereHandles)
    		m_plot->GetImpl()->SetObjectHighlight(handle, 0);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	UpdateAtomsStatusMsg();
    
    	m_plot->update();
    }
    
    
    void MolDynDlg::SliderValueChanged(int val)
    {
    
    	if(!m_plot) return;
    
    	if(val < 0 || val >= m_mol.GetFrameCount())
    		return;
    
    
    	// update atom position with selected frame
    	const auto& frame = m_mol.GetFrame(val);
    
    	t_real atomscale = m_spinScale->value();
    
    
    	std::size_t counter = 0;
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	for(std::size_t atomidx=0; atomidx<frame.GetNumAtomTypes(); ++atomidx)
    
    	{
    		const auto& coords = frame.GetCoords(atomidx);
    		for(const t_vec& vec : coords)
    		{
    			std::size_t obj = m_sphereHandles[counter];
    
    			Change3DItem(obj, &vec, nullptr, &atomscale);
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	UpdateAtomsStatusMsg();
    
    	m_plot->update();
    
    // ----------------------------------------------------------------------------
    
    
    
     * get the index of the atom in the m_mol data structure
     * from the handle of the displayed 3d object
    
    std::tuple<bool, std::size_t, std::size_t, std::size_t> 
    MolDynDlg::GetAtomIndexFromHandle(std::size_t handle) const
    
    	// find handle in sphere handle vector
    
    	auto iter = std::find(m_sphereHandles.begin(), m_sphereHandles.end(), handle);
    
    	if(iter == m_sphereHandles.end())
    
    		return std::make_tuple(0, 0, 0, 0);
    
    
    	std::size_t sphereIdx = iter - m_sphereHandles.begin();
    
    	std::size_t atomCountsSoFar = 0;
    	std::size_t atomTypeIdx = 0;
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	for(atomTypeIdx=0; atomTypeIdx<m_mol.GetNumAtomTypes(); ++atomTypeIdx)
    
    	{
    		std::size_t numAtoms = m_mol.GetAtomNum(atomTypeIdx);
    		if(atomCountsSoFar + numAtoms > sphereIdx)
    			break;
    
    		atomCountsSoFar += numAtoms;
    	}
    
    
    	std::size_t atomSubTypeIdx = sphereIdx-atomCountsSoFar;
    
    	return std::make_tuple(1, atomTypeIdx, atomSubTypeIdx, sphereIdx);
    }
    
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    /**
     * select all atoms of the same type as the one under the cursor
     */
    void MolDynDlg::SelectAtomsOfSameType()
    {
    	// nothing under cursor
    	if(m_curPickedObj <= 0)
    		return;
    
    	// atom type to be selected
    	const std::string& atomLabel = m_plot->GetImpl()->GetObjectDataString(m_curPickedObj);
    
    	for(auto handle : m_sphereHandles)
    		if(m_plot->GetImpl()->GetObjectDataString(handle) == atomLabel)
    			m_plot->GetImpl()->SetObjectHighlight(handle, 1);
    
    
    Tobias WEBER's avatar
    Tobias WEBER committed
    	UpdateAtomsStatusMsg();