<commit_before>/*
	Copyright (c) 2016 Xavier Leclercq

	Permission is hereby granted, free of charge, to any person obtaining a
	copy of this software and associated documentation files (the "Software"),
	to deal in the Software without restriction, including without limitation
	the rights to use, copy, modify, merge, publish, distribute, sublicense,
	and/or sell copies of the Software, and to permit persons to whom the
	Software is furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
	THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
	IN THE SOFTWARE.
*/

/*
	Part of this file were copied from the Chart.js project (http://chartjs.org/)
	and translated into C++.

	The files of the Chart.js project have the following copyright and license.

	Copyright (c) 2013-2016 Nick Downie
	Released under the MIT license
	https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
*/

#include "wxlinechartctrl.h"
#include <wx/dcbuffer.h>
#include <wx/graphics.h>
#include <wx/filedlg.h>
#include <sstream>

wxLineChartDataset::wxLineChartDataset(const wxString &label,
									   const wxColor &dotColor,
									   const wxColor &dotStrokeColor,
									   const wxColor &fillColor,
									   const wxVector<wxDouble> &data)
	: m_label(label), m_showDots(true), m_dotColor(dotColor),
	m_dotStrokeColor(dotStrokeColor), m_showLine(true),
	m_lineColor(dotColor), m_fill(true), m_fillColor(fillColor),
	m_data(data)
{
}

const wxString& wxLineChartDataset::GetLabel() const
{
	return m_label;
}

bool wxLineChartDataset::ShowDots() const
{
	return m_showDots;
}

const wxColor& wxLineChartDataset::GetDotColor() const
{
	return m_dotColor;
}

const wxColor& wxLineChartDataset::GetDotStrokeColor() const
{
	return m_dotStrokeColor;
}

bool wxLineChartDataset::ShowLine() const
{
	return m_showLine;
}

const wxColor& wxLineChartDataset::GetLineColor() const
{
	return m_lineColor;
}

bool wxLineChartDataset::Fill() const
{
	return m_fill;
}

const wxColor& wxLineChartDataset::GetFillColor() const
{
	return m_fillColor;
}

const wxVector<double>& wxLineChartDataset::GetData() const
{
	return m_data;
}

wxLineChartData::wxLineChartData(const wxVector<wxString> &labels)
	: m_labels(labels)
{
}

void wxLineChartData::AddDataset(wxLineChartDataset::ptr dataset)
{
	m_datasets.push_back(dataset);
}

const wxVector<wxString>& wxLineChartData::GetLabels() const
{
	return m_labels;
}

const wxVector<wxLineChartDataset::ptr>& wxLineChartData::GetDatasets() const
{
	return m_datasets;
}

wxLineChartCtrl::Point::Point(wxDouble value,
							  const wxChartTooltipProvider::ptr tooltipProvider,
							  wxDouble x,
							  wxDouble y,
							  wxDouble radius,
							  unsigned int strokeWidth,
							  const wxColor &strokeColor,
							  const wxColor &fillColor,
							  wxDouble hitDetectionRange)
	: wxChartPoint(x, y, radius, tooltipProvider, wxChartPointOptions(strokeWidth, strokeColor, fillColor)),
	m_value(value), m_hitDetectionRange(hitDetectionRange)
{
}

bool wxLineChartCtrl::Point::HitTest(const wxPoint &point) const
{
	wxDouble distance = (point.x - GetPosition().m_x);
	if (distance < 0)
	{
		distance = -distance;
	}
	return (distance < m_hitDetectionRange);
}

wxDouble wxLineChartCtrl::Point::GetValue() const
{
	return m_value;
}

wxLineChartCtrl::Dataset::Dataset(bool showDots,
								  bool showLine,
								  const wxColor &lineColor,
								  bool fill,
								  const wxColor &fillColor)
	: m_showDots(showDots), m_showLine(showLine),
	m_lineColor(lineColor), m_fill(fill), m_fillColor(fillColor)
{
}

bool wxLineChartCtrl::Dataset::ShowDots() const
{
	return m_showDots;
}

bool wxLineChartCtrl::Dataset::ShowLine() const
{
	return m_showLine;
}

const wxColor& wxLineChartCtrl::Dataset::GetLineColor() const
{
	return m_lineColor;
}

bool wxLineChartCtrl::Dataset::Fill() const
{
	return m_fill;
}

const wxColor& wxLineChartCtrl::Dataset::GetFillColor() const
{
	return m_fillColor;
}

const wxVector<wxLineChartCtrl::Point::ptr>& wxLineChartCtrl::Dataset::GetPoints() const
{
	return m_points;
}

void wxLineChartCtrl::Dataset::AppendPoint(Point::ptr point)
{
	m_points.push_back(point);
}

wxLineChartCtrl::wxLineChartCtrl(wxWindow *parent,
								 wxWindowID id,
								 const wxLineChartData &data,
								 const wxPoint &pos,
								 const wxSize &size,
								 long style)
	: wxChartCtrl(parent, id, pos, size, style),
	m_grid(
		wxPoint2DDouble(m_options.GetPadding().GetLeft(), m_options.GetPadding().GetTop()),
		size, data.GetLabels(), GetMinValue(data.GetDatasets()),
		GetMaxValue(data.GetDatasets()), m_options.GetGridOptions()
		)
{
	Initialize(data);
}

wxLineChartCtrl::wxLineChartCtrl(wxWindow *parent,
								 wxWindowID id,
								 const wxLineChartData &data,
								 const wxLineChartOptions &options,
								 const wxPoint &pos,
								 const wxSize &size,
								 long style)
	: wxChartCtrl(parent, id, pos, size, style), m_options(options),
	m_grid(
		wxPoint2DDouble(m_options.GetPadding().GetLeft(), m_options.GetPadding().GetTop()),
		size, data.GetLabels(), GetMinValue(data.GetDatasets()),
		GetMaxValue(data.GetDatasets()), m_options.GetGridOptions()
		)
{
    Initialize(data);
}

const wxLineChartOptions& wxLineChartCtrl::GetOptions() const
{
	return m_options;
}

void wxLineChartCtrl::Initialize(const wxLineChartData &data)
{
    CreateContextMenu();

	const wxVector<wxLineChartDataset::ptr>& datasets = data.GetDatasets();
	for (size_t i = 0; i < datasets.size(); ++i)
	{
		Dataset::ptr newDataset(new Dataset(datasets[i]->ShowDots(),
			datasets[i]->ShowLine(), datasets[i]->GetLineColor(),
			datasets[i]->Fill(), datasets[i]->GetFillColor()));

		const wxVector<wxDouble>& datasetData = datasets[i]->GetData();
		for (size_t j = 0; j < datasetData.size(); ++j)
		{
			std::stringstream tooltip;
			tooltip << datasetData[j];
			wxChartTooltipProvider::ptr tooltipProvider(
				new wxChartTooltipProviderStatic(data.GetLabels()[j], tooltip.str(), datasets[i]->GetLineColor())
				);

			Point::ptr point(
				new Point(datasetData[j], tooltipProvider, 20 + j * 10, 0,
					m_options.GetDotRadius(), m_options.GetDotStrokeWidth(),
					datasets[i]->GetDotStrokeColor(), datasets[i]->GetDotColor(),
					m_options.GetHitDetectionRange())
				);

			newDataset->AppendPoint(point);
		}

		m_datasets.push_back(newDataset);
	}
}

void wxLineChartCtrl::CreateContextMenu()
{
    m_contextMenu.Append(wxID_SAVEAS, wxString("Save as"));

    Bind(wxEVT_CONTEXT_MENU,
        [this](wxContextMenuEvent& evt)
        {
            PopupMenu(&m_contextMenu, ScreenToClient(evt.GetPosition()));
        }
        );

    m_contextMenu.Bind(wxEVT_MENU,
        [this](wxCommandEvent &)
        {
            wxFileDialog saveFileDialog(this, _("Save file"), "", "",
                "PNG files (*.png)|*.png|JPEG files (*.jpeg)|*.jpeg",
                wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
            if (saveFileDialog.ShowModal() == wxID_CANCEL)
                return;

            wxString filename = saveFileDialog.GetPath();

            switch (saveFileDialog.GetFilterIndex())
            {
            case 0:
                Save(filename, wxBitmapType::wxBITMAP_TYPE_PNG);
                break;

            case 1:
                Save(filename, wxBitmapType::wxBITMAP_TYPE_JPEG);
                break;
            }
		},
        wxID_SAVEAS
        );
}

wxDouble wxLineChartCtrl::GetMinValue(const wxVector<wxLineChartDataset::ptr>& datasets)
{
	wxDouble result = 0;
	bool foundValue = false;

	for (size_t i = 0; i < datasets.size(); ++i)
	{
		const wxVector<wxDouble>& values = datasets[i]->GetData();
		for (size_t j = 0; j < values.size(); ++j)
		{
			if (!foundValue)
			{
				result = values[j];
				foundValue = true;
			}
			else if (result > values[j])
			{
				result = values[j];
			}
		}
	}

	return result;
}

wxDouble wxLineChartCtrl::GetMaxValue(const wxVector<wxLineChartDataset::ptr>& datasets)
{
	wxDouble result = 0;
	bool foundValue = false;

	for (size_t i = 0; i < datasets.size(); ++i)
	{
		const wxVector<wxDouble>& values = datasets[i]->GetData();
		for (size_t j = 0; j < values.size(); ++j)
		{
			if (!foundValue)
			{
				result = values[j];
				foundValue = true;
			}
			else if (result < values[j])
			{
				result = values[j];
			}
		}
	}

	return result;
}

void wxLineChartCtrl::Resize(const wxSize &size)
{
	wxSize newSize(
		size.GetWidth() - m_options.GetPadding().GetTotalHorizontalPadding(),
		size.GetHeight() - m_options.GetPadding().GetTotalVerticalPadding()
		);
	m_grid.Resize(newSize);
}

wxSharedPtr<wxVector<const wxChartElement*> > wxLineChartCtrl::GetActiveElements(const wxPoint &point)
{
	wxSharedPtr<wxVector<const wxChartElement*> > activeElements(new wxVector<const wxChartElement*>());
	for (size_t i = 0; i < m_datasets.size(); ++i)
	{
		const wxVector<Point::ptr>& points = m_datasets[i]->GetPoints();
		for (size_t j = 0; j < points.size(); ++j)
		{
			if (points[j]->HitTest(point))
			{
				activeElements->push_back(points[j].get());
			}
		}
	}
	return activeElements;
}

void wxLineChartCtrl::Draw(wxGraphicsContext &gc)
{
    m_grid.Draw(gc);

    for (size_t i = 0; i < m_datasets.size(); ++i)
    {
        const wxVector<Point::ptr>& points = m_datasets[i]->GetPoints();

		wxGraphicsPath path = gc.CreatePath();

		if (points.size() > 0)
		{
			const Point::ptr& point = points[0];
			wxPoint2DDouble firstPosition = m_grid.GetMapping().GetWindowPositionAtTickMark(0, point->GetValue());
			path.MoveToPoint(firstPosition);

            wxPoint2DDouble lastPosition;
			for (size_t j = 1; j < points.size(); ++j)
			{
				const Point::ptr& point = points[j];
				lastPosition = m_grid.GetMapping().GetWindowPositionAtTickMark(j, point->GetValue());
				path.AddLineToPoint(lastPosition);
			}

			if (m_datasets[i]->ShowLine())
			{
				wxPen pen(m_datasets[i]->GetLineColor(), m_options.GetLineWidth());
				gc.SetPen(pen);
			}
			else
			{
				// TODO : transparent pen
			}

			gc.StrokePath(path);

			wxPoint2DDouble yPos = m_grid.GetMapping().GetXAxis().GetTickMarkPosition(0);

			path.AddLineToPoint(lastPosition.m_x, yPos.m_y);
			path.AddLineToPoint(firstPosition.m_x, yPos.m_y);
			path.CloseSubpath();

			wxBrush brush(m_datasets[i]->GetFillColor());
			gc.SetBrush(brush);
			gc.FillPath(path);
		}

		if (m_datasets[i]->ShowDots())
		{
			for (size_t j = 0; j < points.size(); ++j)
			{
				const Point::ptr& point = points[j];
				point->SetPosition(m_grid.GetMapping().GetWindowPositionAtTickMark(j, point->GetValue()));
				point->Draw(gc);
			}
		}
	}

	DrawTooltips(gc);
}

void wxLineChartCtrl::Save(const wxString &filename, const wxBitmapType &type)
{
    int w,h;
    GetSize(&w,&h);
    wxBitmap bmp(w,h);
    wxMemoryDC mdc(bmp);
    mdc.Clear();
    wxGraphicsContext* gc = wxGraphicsContext::Create(mdc);
    Draw(*gc);
    bmp.SaveFile(filename,type);
    delete gc;
}

void wxLineChartCtrl::OnPaint(wxPaintEvent &evt)
{
	wxAutoBufferedPaintDC dc(this);
	dc.Clear();
	wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
	Draw(*gc);
    delete gc;
}

BEGIN_EVENT_TABLE(wxLineChartCtrl, wxChartCtrl)
	EVT_PAINT(wxLineChartCtrl::OnPaint)
END_EVENT_TABLE()
<commit_msg>Add image handler if needed<commit_after>/*
	Copyright (c) 2016 Xavier Leclercq

	Permission is hereby granted, free of charge, to any person obtaining a
	copy of this software and associated documentation files (the "Software"),
	to deal in the Software without restriction, including without limitation
	the rights to use, copy, modify, merge, publish, distribute, sublicense,
	and/or sell copies of the Software, and to permit persons to whom the
	Software is furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
	THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
	IN THE SOFTWARE.
*/

/*
	Part of this file were copied from the Chart.js project (http://chartjs.org/)
	and translated into C++.

	The files of the Chart.js project have the following copyright and license.

	Copyright (c) 2013-2016 Nick Downie
	Released under the MIT license
	https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
*/

#include "wxlinechartctrl.h"
#include <wx/dcbuffer.h>
#include <wx/graphics.h>
#include <wx/filedlg.h>
#include <sstream>

wxLineChartDataset::wxLineChartDataset(const wxString &label,
									   const wxColor &dotColor,
									   const wxColor &dotStrokeColor,
									   const wxColor &fillColor,
									   const wxVector<wxDouble> &data)
	: m_label(label), m_showDots(true), m_dotColor(dotColor),
	m_dotStrokeColor(dotStrokeColor), m_showLine(true),
	m_lineColor(dotColor), m_fill(true), m_fillColor(fillColor),
	m_data(data)
{
}

const wxString& wxLineChartDataset::GetLabel() const
{
	return m_label;
}

bool wxLineChartDataset::ShowDots() const
{
	return m_showDots;
}

const wxColor& wxLineChartDataset::GetDotColor() const
{
	return m_dotColor;
}

const wxColor& wxLineChartDataset::GetDotStrokeColor() const
{
	return m_dotStrokeColor;
}

bool wxLineChartDataset::ShowLine() const
{
	return m_showLine;
}

const wxColor& wxLineChartDataset::GetLineColor() const
{
	return m_lineColor;
}

bool wxLineChartDataset::Fill() const
{
	return m_fill;
}

const wxColor& wxLineChartDataset::GetFillColor() const
{
	return m_fillColor;
}

const wxVector<double>& wxLineChartDataset::GetData() const
{
	return m_data;
}

wxLineChartData::wxLineChartData(const wxVector<wxString> &labels)
	: m_labels(labels)
{
}

void wxLineChartData::AddDataset(wxLineChartDataset::ptr dataset)
{
	m_datasets.push_back(dataset);
}

const wxVector<wxString>& wxLineChartData::GetLabels() const
{
	return m_labels;
}

const wxVector<wxLineChartDataset::ptr>& wxLineChartData::GetDatasets() const
{
	return m_datasets;
}

wxLineChartCtrl::Point::Point(wxDouble value,
							  const wxChartTooltipProvider::ptr tooltipProvider,
							  wxDouble x,
							  wxDouble y,
							  wxDouble radius,
							  unsigned int strokeWidth,
							  const wxColor &strokeColor,
							  const wxColor &fillColor,
							  wxDouble hitDetectionRange)
	: wxChartPoint(x, y, radius, tooltipProvider, wxChartPointOptions(strokeWidth, strokeColor, fillColor)),
	m_value(value), m_hitDetectionRange(hitDetectionRange)
{
}

bool wxLineChartCtrl::Point::HitTest(const wxPoint &point) const
{
	wxDouble distance = (point.x - GetPosition().m_x);
	if (distance < 0)
	{
		distance = -distance;
	}
	return (distance < m_hitDetectionRange);
}

wxDouble wxLineChartCtrl::Point::GetValue() const
{
	return m_value;
}

wxLineChartCtrl::Dataset::Dataset(bool showDots,
								  bool showLine,
								  const wxColor &lineColor,
								  bool fill,
								  const wxColor &fillColor)
	: m_showDots(showDots), m_showLine(showLine),
	m_lineColor(lineColor), m_fill(fill), m_fillColor(fillColor)
{
}

bool wxLineChartCtrl::Dataset::ShowDots() const
{
	return m_showDots;
}

bool wxLineChartCtrl::Dataset::ShowLine() const
{
	return m_showLine;
}

const wxColor& wxLineChartCtrl::Dataset::GetLineColor() const
{
	return m_lineColor;
}

bool wxLineChartCtrl::Dataset::Fill() const
{
	return m_fill;
}

const wxColor& wxLineChartCtrl::Dataset::GetFillColor() const
{
	return m_fillColor;
}

const wxVector<wxLineChartCtrl::Point::ptr>& wxLineChartCtrl::Dataset::GetPoints() const
{
	return m_points;
}

void wxLineChartCtrl::Dataset::AppendPoint(Point::ptr point)
{
	m_points.push_back(point);
}

wxLineChartCtrl::wxLineChartCtrl(wxWindow *parent,
								 wxWindowID id,
								 const wxLineChartData &data,
								 const wxPoint &pos,
								 const wxSize &size,
								 long style)
	: wxChartCtrl(parent, id, pos, size, style),
	m_grid(
		wxPoint2DDouble(m_options.GetPadding().GetLeft(), m_options.GetPadding().GetTop()),
		size, data.GetLabels(), GetMinValue(data.GetDatasets()),
		GetMaxValue(data.GetDatasets()), m_options.GetGridOptions()
		)
{
	Initialize(data);
}

wxLineChartCtrl::wxLineChartCtrl(wxWindow *parent,
								 wxWindowID id,
								 const wxLineChartData &data,
								 const wxLineChartOptions &options,
								 const wxPoint &pos,
								 const wxSize &size,
								 long style)
	: wxChartCtrl(parent, id, pos, size, style), m_options(options),
	m_grid(
		wxPoint2DDouble(m_options.GetPadding().GetLeft(), m_options.GetPadding().GetTop()),
		size, data.GetLabels(), GetMinValue(data.GetDatasets()),
		GetMaxValue(data.GetDatasets()), m_options.GetGridOptions()
		)
{
    Initialize(data);
}

const wxLineChartOptions& wxLineChartCtrl::GetOptions() const
{
	return m_options;
}

void wxLineChartCtrl::Initialize(const wxLineChartData &data)
{
    CreateContextMenu();

	const wxVector<wxLineChartDataset::ptr>& datasets = data.GetDatasets();
	for (size_t i = 0; i < datasets.size(); ++i)
	{
		Dataset::ptr newDataset(new Dataset(datasets[i]->ShowDots(),
			datasets[i]->ShowLine(), datasets[i]->GetLineColor(),
			datasets[i]->Fill(), datasets[i]->GetFillColor()));

		const wxVector<wxDouble>& datasetData = datasets[i]->GetData();
		for (size_t j = 0; j < datasetData.size(); ++j)
		{
			std::stringstream tooltip;
			tooltip << datasetData[j];
			wxChartTooltipProvider::ptr tooltipProvider(
				new wxChartTooltipProviderStatic(data.GetLabels()[j], tooltip.str(), datasets[i]->GetLineColor())
				);

			Point::ptr point(
				new Point(datasetData[j], tooltipProvider, 20 + j * 10, 0,
					m_options.GetDotRadius(), m_options.GetDotStrokeWidth(),
					datasets[i]->GetDotStrokeColor(), datasets[i]->GetDotColor(),
					m_options.GetHitDetectionRange())
				);

			newDataset->AppendPoint(point);
		}

		m_datasets.push_back(newDataset);
	}
}

void wxLineChartCtrl::CreateContextMenu()
{
    m_contextMenu.Append(wxID_SAVEAS, wxString("Save as"));

    Bind(wxEVT_CONTEXT_MENU,
        [this](wxContextMenuEvent& evt)
        {
            PopupMenu(&m_contextMenu, ScreenToClient(evt.GetPosition()));
        }
        );

    m_contextMenu.Bind(wxEVT_MENU,
        [this](wxCommandEvent &)
        {
            wxFileDialog saveFileDialog(this, _("Save file"), "", "",
                "JPEG files (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG files (*.png)|*.png",
                wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
            if (saveFileDialog.ShowModal() == wxID_CANCEL)
                return;

            wxString filename = saveFileDialog.GetPath();

            wxBitmapType type = wxBitmapType::wxBITMAP_TYPE_INVALID;
            switch (saveFileDialog.GetFilterIndex())
            {
            case 0:
                type = wxBitmapType::wxBITMAP_TYPE_JPEG;
                if (wxImage::FindHandler(wxBitmapType::wxBITMAP_TYPE_JPEG) == 0)
                {
                    wxImage::AddHandler(new wxJPEGHandler());
                }
                break;

            case 1:
                type = wxBitmapType::wxBITMAP_TYPE_PNG;
                if (wxImage::FindHandler(wxBitmapType::wxBITMAP_TYPE_PNG) == 0)
                {
                    wxImage::AddHandler(new wxPNGHandler());
                }
                break;
            }

            Save(filename, type);
		},
        wxID_SAVEAS
        );
}

wxDouble wxLineChartCtrl::GetMinValue(const wxVector<wxLineChartDataset::ptr>& datasets)
{
	wxDouble result = 0;
	bool foundValue = false;

	for (size_t i = 0; i < datasets.size(); ++i)
	{
		const wxVector<wxDouble>& values = datasets[i]->GetData();
		for (size_t j = 0; j < values.size(); ++j)
		{
			if (!foundValue)
			{
				result = values[j];
				foundValue = true;
			}
			else if (result > values[j])
			{
				result = values[j];
			}
		}
	}

	return result;
}

wxDouble wxLineChartCtrl::GetMaxValue(const wxVector<wxLineChartDataset::ptr>& datasets)
{
	wxDouble result = 0;
	bool foundValue = false;

	for (size_t i = 0; i < datasets.size(); ++i)
	{
		const wxVector<wxDouble>& values = datasets[i]->GetData();
		for (size_t j = 0; j < values.size(); ++j)
		{
			if (!foundValue)
			{
				result = values[j];
				foundValue = true;
			}
			else if (result < values[j])
			{
				result = values[j];
			}
		}
	}

	return result;
}

void wxLineChartCtrl::Resize(const wxSize &size)
{
	wxSize newSize(
		size.GetWidth() - m_options.GetPadding().GetTotalHorizontalPadding(),
		size.GetHeight() - m_options.GetPadding().GetTotalVerticalPadding()
		);
	m_grid.Resize(newSize);
}

wxSharedPtr<wxVector<const wxChartElement*> > wxLineChartCtrl::GetActiveElements(const wxPoint &point)
{
	wxSharedPtr<wxVector<const wxChartElement*> > activeElements(new wxVector<const wxChartElement*>());
	for (size_t i = 0; i < m_datasets.size(); ++i)
	{
		const wxVector<Point::ptr>& points = m_datasets[i]->GetPoints();
		for (size_t j = 0; j < points.size(); ++j)
		{
			if (points[j]->HitTest(point))
			{
				activeElements->push_back(points[j].get());
			}
		}
	}
	return activeElements;
}

void wxLineChartCtrl::Draw(wxGraphicsContext &gc)
{
    m_grid.Draw(gc);

    for (size_t i = 0; i < m_datasets.size(); ++i)
    {
        const wxVector<Point::ptr>& points = m_datasets[i]->GetPoints();

		wxGraphicsPath path = gc.CreatePath();

		if (points.size() > 0)
		{
			const Point::ptr& point = points[0];
			wxPoint2DDouble firstPosition = m_grid.GetMapping().GetWindowPositionAtTickMark(0, point->GetValue());
			path.MoveToPoint(firstPosition);

            wxPoint2DDouble lastPosition;
			for (size_t j = 1; j < points.size(); ++j)
			{
				const Point::ptr& point = points[j];
				lastPosition = m_grid.GetMapping().GetWindowPositionAtTickMark(j, point->GetValue());
				path.AddLineToPoint(lastPosition);
			}

			if (m_datasets[i]->ShowLine())
			{
				wxPen pen(m_datasets[i]->GetLineColor(), m_options.GetLineWidth());
				gc.SetPen(pen);
			}
			else
			{
				// TODO : transparent pen
			}

			gc.StrokePath(path);

			wxPoint2DDouble yPos = m_grid.GetMapping().GetXAxis().GetTickMarkPosition(0);

			path.AddLineToPoint(lastPosition.m_x, yPos.m_y);
			path.AddLineToPoint(firstPosition.m_x, yPos.m_y);
			path.CloseSubpath();

			wxBrush brush(m_datasets[i]->GetFillColor());
			gc.SetBrush(brush);
			gc.FillPath(path);
		}

		if (m_datasets[i]->ShowDots())
		{
			for (size_t j = 0; j < points.size(); ++j)
			{
				const Point::ptr& point = points[j];
				point->SetPosition(m_grid.GetMapping().GetWindowPositionAtTickMark(j, point->GetValue()));
				point->Draw(gc);
			}
		}
	}

	DrawTooltips(gc);
}

void wxLineChartCtrl::Save(const wxString &filename, const wxBitmapType &type)
{
    int w,h;
    GetSize(&w,&h);
    wxBitmap bmp(w,h);
    wxMemoryDC mdc(bmp);
    mdc.Clear();
    wxGraphicsContext* gc = wxGraphicsContext::Create(mdc);
    Draw(*gc);
    bmp.SaveFile(filename,type);
    delete gc;
}

void wxLineChartCtrl::OnPaint(wxPaintEvent &evt)
{
	wxAutoBufferedPaintDC dc(this);
	dc.Clear();
	wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
	Draw(*gc);
    delete gc;
}

BEGIN_EVENT_TABLE(wxLineChartCtrl, wxChartCtrl)
	EVT_PAINT(wxLineChartCtrl::OnPaint)
END_EVENT_TABLE()
<|endoftext|>


<commit_before>//
//  Operator.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 15/04/2020.
//  Copyright © 2020 Thomas Harte. All rights reserved.
//

#include "Operator.hpp"

#include <algorithm>
#include <cassert>

using namespace Yamaha::OPL;

int OperatorState::level() {
	return power_two(attenuation);
}

void Operator::set_attack_decay(uint8_t value) {
	attack_rate_ = (value & 0xf0) >> 2;
	decay_rate_ = (value & 0x0f) << 2;
}

void Operator::set_sustain_release(uint8_t value) {
	sustain_level_ = (value & 0xf0) >> 4;
	release_rate_ = (value & 0x0f) << 2;
}

void Operator::set_scaling_output(uint8_t value) {
	key_level_scaling_ = value >> 6;
	attenuation_ = value & 0x3f;
}

void Operator::set_waveform(uint8_t value) {
	waveform_ = Operator::Waveform(value & 3);
}

void Operator::set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value) {
	apply_amplitude_modulation_ = value & 0x80;
	apply_vibrato_ = value & 0x40;
	use_sustain_level_ = value & 0x20;
	key_rate_scaling_shift_ = (value & 0x10) ? 0 : 2;
	frequency_multiple_ = value & 0xf;
}

bool Operator::is_audible(OperatorState &state, OperatorOverrides *overrides) {
	if(state.adsr_phase_ == OperatorState::ADSRPhase::Release) {
		if(overrides) {
			if(overrides->attenuation == 0xf) return false;
		} else {
			if(attenuation_ == 0x3f) return false;
		}
	}
	return state.adsr_attenuation_ != 511;
}

void Operator::update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorState *phase_offset, OperatorOverrides *overrides) {
	// Per the documentation:
	//
	// Delta phase = ( [desired freq] * 2^19 / [input clock / 72] ) / 2 ^ (b - 1)
	//
	// After experimentation, I think this gives rate calculation as formulated below.

	// This encodes the MUL -> multiple table given on page 12,
	// multiplied by two.
	constexpr int multipliers[] = {
		1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
	};

	// Update the raw phase.
	state.raw_phase_ += multipliers[frequency_multiple_] * channel_period << channel_octave;

	// Hence calculate phase (TODO: by also taking account of vibrato).
	constexpr int waveforms[4][4] = {
		{1023, 1023, 1023, 1023},	// Sine: don't mask in any quadrant.
		{511, 511, 0, 0},			// Half sine: keep the first half in tact, lock to 0 in the second half.
		{511, 511, 511, 511},		// AbsSine: endlessly repeat the first half of the sine wave.
		{255, 0, 255, 0},			// PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
	};
	const int scaled_phase_offset = phase_offset ? power_two(phase_offset->attenuation, 11) : 0;
	const int phase = (state.raw_phase_ + scaled_phase_offset) >> 11;
	state.attenuation = negative_log_sin(phase & waveforms[int(waveform_)][(phase >> 8) & 3]);

	// Key-on logic: any time it is false, be in the release state.
	// On the leading edge of it becoming true, enter the attack state.
	if(!key_on) {
		state.adsr_phase_ = OperatorState::ADSRPhase::Release;
		state.time_in_phase_ = 0;
	} else if(!state.last_key_on_) {
		state.adsr_phase_ = OperatorState::ADSRPhase::Attack;
		state.time_in_phase_ = 0;
	}
	state.last_key_on_ = key_on;

	// Adjust the ADSR attenuation appropriately;
	// cf. http://forums.submarine.org.uk/phpBB/viewtopic.php?f=9&t=16 (primarily) for the source of the maths below.

	// "An attack rate value of 52 (AR = 13) has 32 samples in the attack phase, an attack rate value of 48 (AR = 12)
	// has 64 samples in the attack phase, but pairs of samples show the same envelope attenuation. I am however struggling to find a plausible algorithm to match the experimental results.

	const int key_scaling_rate = ((channel_octave << 1) | (channel_period >> 9)) >> key_rate_scaling_shift_;
	assert(key_scaling_rate < 16);
	assert((channel_period >> 9) < 2);

	const auto current_phase = state.adsr_phase_;
	switch(current_phase) {
		case OperatorState::ADSRPhase::Attack: {
			const int attack_rate = attack_rate_ + key_scaling_rate;

			// Rules:
			//
			// An attack rate of '13' has 32 samples in the attack phase; a rate of '12' has the same 32 steps, but spread out over 64 samples, etc.
			// An attack rate of '14' uses a divide by four instead of two.
			// 15 is instantaneous.

			if(attack_rate >= 56) {
				state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 2) - 1;
			} else {
				const int sample_length = 1 << (14 - (attack_rate >> 2));	// TODO: don't throw away KSR bits.
				if(!(state.time_in_phase_ & (sample_length - 1))) {
					state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 3) - 1;
				}
			}

			// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
			if(attack_rate > 60 || state.adsr_attenuation_ <= 0) {
				state.adsr_attenuation_ = 0;
				state.adsr_phase_ = OperatorState::ADSRPhase::Decay;
			}
		} break;

		case OperatorState::ADSRPhase::Release:
		case OperatorState::ADSRPhase::Decay:
		{
			// Rules:
			//
			// (relative to a 511 scale)
			//
			// A rate of 0 is no decay at all.
			// A rate of 1 means increase 4 per cycle.
			// A rate of 2 means increase 2 per cycle.
			// A rate of 3 means increase 1 per cycle.
			// A rate of 4 means increase 1 every other cycle.
			// (etc)
			const int decrease_rate = key_scaling_rate + ((state.adsr_phase_ == OperatorState::ADSRPhase::Decay) ? decay_rate_ : release_rate_);

			if(decrease_rate) {
				// TODO: don't throw away KSR bits.
				switch(decrease_rate >> 2) {
					case 1: state.adsr_attenuation_ += 32;	break;
					case 2: state.adsr_attenuation_ += 16;	break;
					default: {
						const int sample_length = 1 << ((decrease_rate >> 2) - 4);
						if(!(state.time_in_phase_ & (sample_length - 1))) {
							state.adsr_attenuation_ += 8;
						}
					} break;
				}
			}

			// Clamp to the proper range.
			state.adsr_attenuation_ = std::min(state.adsr_attenuation_, 511);

			// Check for the decay exit condition.
			if(state.adsr_phase_ == OperatorState::ADSRPhase::Decay && state.adsr_attenuation_ >= (sustain_level_ << 3)) {
				state.adsr_attenuation_ = sustain_level_ << 3;
				state.adsr_phase_ = ((overrides && overrides->use_sustain_level) || use_sustain_level_) ? OperatorState::ADSRPhase::Sustain : OperatorState::ADSRPhase::Release;
			}
		} break;

		case OperatorState::ADSRPhase::Sustain:
			// Nothing to do.
		break;
	}
	if(state.adsr_phase_ == current_phase) {
		++state.time_in_phase_;
	} else {
		state.time_in_phase_ = 0;
	}

	// Calculate key-level scaling. Table is as per p14 of the YM3812 application manual,
	// converted into a fixed-point scheme. Compare with https://www.smspower.org/Development/RE12
	// and apologies for the highly ad hoc indentation.
	constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};	// '7' is just a number large enough to render all the numbers below as 0.
	constexpr int key_level_scales[8][16] = {
#define _ 0
		// 6 db attenuations.
		{_,	  _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _},
		{_,   _,   _,   _,   _,   _,   _,   _,   _,   4,   6,   8,  10,  12,  14,  16},
		{_,   _,   _,   _,   _,   6,  10,  14,  16,  20,  22,  24,  26,  28,  30,  32},
		{_,   _,   _,  10,  16,  22,  26,  30,  32,  36,  38,  40,  42,  44,  46,  48},
		{_,	  _,  16,  26,  32,  38,  42,  46,  48,  52,  54,  56,  58,  60,  62,  64},
		{_,	 16,  32,  42,  48,  54,  58,  62,  64,  68,  70,  72,  74,  76,  78,  80},
		{_,	 32,  48,  58,  64,  70,  74,  78,  80,  84,  86,  88,  90,  92,  94,  96},
		{_,	 48,  64,  74,  80,  86,  90,  94,  96, 100, 102, 104, 106, 108, 110, 112},
#undef _
	};
	assert((channel_period >> 6) < 16);
	assert(channel_octave < 8);
	state.attenuation.log += (key_level_scales[channel_octave][channel_period >> 6] >> key_level_scale_shifts[key_level_scaling_]) << 7;

	// Combine the ADSR attenuation and overall channel attenuation.
	if(overrides) {
		// Overrides here represent per-channel volume on an OPLL. The bits are defined to represent
		// attenuations of 24db to 3db; the main envelope generator is stated to have a resolution of
		// 0.325db (which I've assumed is supposed to say 0.375db).
		state.attenuation.log += (state.adsr_attenuation_ << 3) + (overrides->attenuation << 7);
	} else {
		// Overrides here represent per-channel volume on an OPLL. The bits are defined to represent
		// attenuations of 24db to 0.75db.
		state.attenuation.log += (state.adsr_attenuation_ << 3) + (attenuation_ << 5);
	}
}
<commit_msg>Minor layout and commenting improvements.<commit_after>//
//  Operator.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 15/04/2020.
//  Copyright © 2020 Thomas Harte. All rights reserved.
//

#include "Operator.hpp"

#include <algorithm>
#include <cassert>

using namespace Yamaha::OPL;

// MARK: - Setters

int OperatorState::level() {
	return power_two(attenuation);
}

void Operator::set_attack_decay(uint8_t value) {
	attack_rate_ = (value & 0xf0) >> 2;
	decay_rate_ = (value & 0x0f) << 2;
}

void Operator::set_sustain_release(uint8_t value) {
	sustain_level_ = (value & 0xf0) >> 4;
	release_rate_ = (value & 0x0f) << 2;
}

void Operator::set_scaling_output(uint8_t value) {
	key_level_scaling_ = value >> 6;
	attenuation_ = value & 0x3f;
}

void Operator::set_waveform(uint8_t value) {
	waveform_ = Operator::Waveform(value & 3);
}

void Operator::set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value) {
	apply_amplitude_modulation_ = value & 0x80;
	apply_vibrato_ = value & 0x40;
	use_sustain_level_ = value & 0x20;
	key_rate_scaling_shift_ = (value & 0x10) ? 0 : 2;
	frequency_multiple_ = value & 0xf;
}

// MARK: - Getter

bool Operator::is_audible(OperatorState &state, OperatorOverrides *overrides) {
	// TODO: (i) do I actually want to support this functionality? (ii) if so, fix below.
	if(state.adsr_phase_ == OperatorState::ADSRPhase::Release) {
		if(overrides) {
			if(overrides->attenuation == 0xf) return false;
		} else {
			if(attenuation_ == 0x3f) return false;
		}
	}
	return state.adsr_attenuation_ != 511;
}

// MARK: - Update logic.

void Operator::update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorState *phase_offset, OperatorOverrides *overrides) {
	// Per the documentation:
	//
	// Delta phase = ( [desired freq] * 2^19 / [input clock / 72] ) / 2 ^ (b - 1)
	//
	// After experimentation, I think this gives rate calculation as formulated below.

	// This encodes the MUL -> multiple table given on page 12,
	// multiplied by two.
	constexpr int multipliers[] = {
		1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
	};

	// Update the raw phase.
	state.raw_phase_ += multipliers[frequency_multiple_] * channel_period << channel_octave;

	// Hence calculate phase (TODO: by also taking account of vibrato).
	constexpr int waveforms[4][4] = {
		{1023, 1023, 1023, 1023},	// Sine: don't mask in any quadrant.
		{511, 511, 0, 0},			// Half sine: keep the first half intact, lock to 0 in the second half.
		{511, 511, 511, 511},		// AbsSine: endlessly repeat the first half of the sine wave.
		{255, 0, 255, 0},			// PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0.
	};
	const int scaled_phase_offset = phase_offset ? power_two(phase_offset->attenuation, 11) : 0;
	const int phase = (state.raw_phase_ + scaled_phase_offset) >> 11;
	state.attenuation = negative_log_sin(phase & waveforms[int(waveform_)][(phase >> 8) & 3]);

	// Key-on logic: any time it is false, be in the release state.
	// On the leading edge of it becoming true, enter the attack state.
	if(!key_on) {
		state.adsr_phase_ = OperatorState::ADSRPhase::Release;
		state.time_in_phase_ = 0;
	} else if(!state.last_key_on_) {
		state.adsr_phase_ = OperatorState::ADSRPhase::Attack;
		state.time_in_phase_ = 0;
	}
	state.last_key_on_ = key_on;

	// Adjust the ADSR attenuation appropriately;
	// cf. http://forums.submarine.org.uk/phpBB/viewtopic.php?f=9&t=16 (primarily) for the source of the maths below.

	// "An attack rate value of 52 (AR = 13) has 32 samples in the attack phase, an attack rate value of 48 (AR = 12)
	// has 64 samples in the attack phase, but pairs of samples show the same envelope attenuation. I am however struggling to find a plausible algorithm to match the experimental results.

	const int key_scaling_rate = ((channel_octave << 1) | (channel_period >> 9)) >> key_rate_scaling_shift_;
	assert(key_scaling_rate < 16);
	assert((channel_period >> 9) < 2);

	const auto current_phase = state.adsr_phase_;
	switch(current_phase) {
		case OperatorState::ADSRPhase::Attack: {
			const int attack_rate = attack_rate_ + key_scaling_rate;

			// Rules:
			//
			// An attack rate of '13' has 32 samples in the attack phase; a rate of '12' has the same 32 steps, but spread out over 64 samples, etc.
			// An attack rate of '14' uses a divide by four instead of two.
			// 15 is instantaneous.

			if(attack_rate >= 56) {
				state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 2) - 1;
			} else {
				const int sample_length = 1 << (14 - (attack_rate >> 2));	// TODO: don't throw away KSR bits.
				if(!(state.time_in_phase_ & (sample_length - 1))) {
					state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 3) - 1;
				}
			}

			// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
			if(attack_rate > 60 || state.adsr_attenuation_ <= 0) {
				state.adsr_attenuation_ = 0;
				state.adsr_phase_ = OperatorState::ADSRPhase::Decay;
			}
		} break;

		case OperatorState::ADSRPhase::Release:
		case OperatorState::ADSRPhase::Decay:
		{
			// Rules:
			//
			// (relative to a 511 scale)
			//
			// A rate of 0 is no decay at all.
			// A rate of 1 means increase 4 per cycle.
			// A rate of 2 means increase 2 per cycle.
			// A rate of 3 means increase 1 per cycle.
			// A rate of 4 means increase 1 every other cycle.
			// A rate of 5 means increase once every fourth cycle.
			// etc.
			// eighth, sixteenth, 32nd, 64th, 128th, 256th, 512th, 1024th, 2048th, 4096th, 8192th
			const int decrease_rate = key_scaling_rate + ((state.adsr_phase_ == OperatorState::ADSRPhase::Decay) ? decay_rate_ : release_rate_);

			if(decrease_rate) {
				// TODO: don't throw away KSR bits.
				switch(decrease_rate >> 2) {
					case 1: state.adsr_attenuation_ += 32;	break;
					case 2: state.adsr_attenuation_ += 16;	break;
					default: {
						const int sample_length = 1 << ((decrease_rate >> 2) - 4);
						if(!(state.time_in_phase_ & (sample_length - 1))) {
							state.adsr_attenuation_ += 8;
						}
					} break;
				}
			}

			// Clamp to the proper range.
			state.adsr_attenuation_ = std::min(state.adsr_attenuation_, 511);

			// Check for the decay exit condition.
			if(state.adsr_phase_ == OperatorState::ADSRPhase::Decay && state.adsr_attenuation_ >= (sustain_level_ << 3)) {
				state.adsr_attenuation_ = sustain_level_ << 3;
				state.adsr_phase_ = ((overrides && overrides->use_sustain_level) || use_sustain_level_) ? OperatorState::ADSRPhase::Sustain : OperatorState::ADSRPhase::Release;
			}
		} break;

		case OperatorState::ADSRPhase::Sustain:
			// Nothing to do.
		break;
	}
	if(state.adsr_phase_ == current_phase) {
		++state.time_in_phase_;
	} else {
		state.time_in_phase_ = 0;
	}

	// Calculate key-level scaling. Table is as per p14 of the YM3812 application manual,
	// converted into a fixed-point scheme. Compare with https://www.smspower.org/Development/RE12
	// and apologies for the highly ad hoc indentation.
	constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};	// '7' is just a number large enough to render all the numbers below as 0.
	constexpr int key_level_scales[8][16] = {
#define _ 0
		// 6 db attenuations.
		{_,	  _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _,   _},
		{_,   _,   _,   _,   _,   _,   _,   _,   _,   4,   6,   8,  10,  12,  14,  16},
		{_,   _,   _,   _,   _,   6,  10,  14,  16,  20,  22,  24,  26,  28,  30,  32},
		{_,   _,   _,  10,  16,  22,  26,  30,  32,  36,  38,  40,  42,  44,  46,  48},
		{_,	  _,  16,  26,  32,  38,  42,  46,  48,  52,  54,  56,  58,  60,  62,  64},
		{_,	 16,  32,  42,  48,  54,  58,  62,  64,  68,  70,  72,  74,  76,  78,  80},
		{_,	 32,  48,  58,  64,  70,  74,  78,  80,  84,  86,  88,  90,  92,  94,  96},
		{_,	 48,  64,  74,  80,  86,  90,  94,  96, 100, 102, 104, 106, 108, 110, 112},
#undef _
	};
	assert((channel_period >> 6) < 16);
	assert(channel_octave < 8);
	state.attenuation.log += (key_level_scales[channel_octave][channel_period >> 6] >> key_level_scale_shifts[key_level_scaling_]) << 7;

	// Combine the ADSR attenuation and overall channel attenuation.
	if(overrides) {
		// Overrides here represent per-channel volume on an OPLL. The bits are defined to represent
		// attenuations of 24db to 3db; the main envelope generator is stated to have a resolution of
		// 0.325db (which I've assumed is supposed to say 0.375db).
		state.attenuation.log += (state.adsr_attenuation_ << 3) + (overrides->attenuation << 7);
	} else {
		// Overrides here represent per-channel volume on an OPLL. The bits are defined to represent
		// attenuations of 24db to 0.75db.
		state.attenuation.log += (state.adsr_attenuation_ << 3) + (attenuation_ << 5);
	}
}
<|endoftext|>


<commit_before>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    TestPointLocators.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

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

=========================================================================*/
#include "vtkIdList.h"
#include "vtkKdTreePointLocator.h"
#include "vtkMath.h"
#include "vtkPointLocator.h"
#include "vtkPoints.h"
#include "vtkStructuredGrid.h"

#include <vtkstd/vector>

// returns true if 2 points are equidistant from x, within a tolerance
bool ArePointsEquidistant(double x[3], vtkIdType id1, vtkIdType id2,
                          vtkPointSet* grid)
{
  if(id1 == id2)
    {
    return true;
    }
  float firstDist2 = vtkMath::Distance2BetweenPoints(
    x, grid->GetPoint(id1));
  float secondDist2 = vtkMath::Distance2BetweenPoints(
    x, grid->GetPoint(id2));

  float differenceDist2 = firstDist2 - secondDist2;
  if(differenceDist2 < 0)
    {
    differenceDist2 = -differenceDist2;
    }

  if(differenceDist2/(firstDist2+secondDist2) >
     .00001)
    {
    cerr << "Results do not match (first dist2="
         << firstDist2 << " , second dist2=" << secondDist2 << ") ";
    return false;
    }
  return true;
}

// checks that every point in firstList has a matching point (based
// on distance) in secondList
bool DoesListHaveProperPoints(double x[3], vtkIdList* firstList,
                              vtkIdList* secondList,
                              vtkPointSet* grid)
{
  for(vtkIdType uid=0;uid<firstList->GetNumberOfIds();uid++)
    {
    int found = 0;
    for(vtkIdType kid=0;kid<secondList->GetNumberOfIds();kid++)
      {
      if(firstList->GetId(uid) == secondList->GetId(kid))
        {
        found = 1;
        break;
        }
      }
    if(!found)
      {
      for(vtkIdType kid=0;kid<secondList->GetNumberOfIds();kid++)
        {
        if(ArePointsEquidistant(x, firstList->GetId(uid),
                                secondList->GetId(kid), grid))
          {
          found = 1;
          break;
          }
        }
      }
    if(!found)
      {
      return 0;
      }
    }
  return 1;

}
int TestPointLocators(int , char *[])
{
  int rval = 0;

  int i, j, k, kOffset, jOffset, offset;
  float x[3];
  static int dims[3]={39,31,31};

  // Create the structured grid.
  vtkStructuredGrid *sgrid = vtkStructuredGrid::New();
    sgrid->SetDimensions(dims);

  // We also create the points.
  vtkPoints *points = vtkPoints::New();
    points->Allocate(dims[0]*dims[1]*dims[2]);

  for ( k=0; k<dims[2]; k++)
    {
    x[2] = 1.0 + k*1.2;
    kOffset = k * dims[0] * dims[1];
    for (j=0; j<dims[1]; j++)
      {
      x[1] = sqrt(10.+j*2.);
      jOffset = j * dims[0];
      for (i=0; i<dims[0]; i++)
        {
        x[0] = 1+i*i*.5;
        offset = i + jOffset + kOffset;
        points->InsertPoint(offset,x);
        }
      }
    }
  sgrid->SetPoints(points);
  sgrid->Update();
  points->Delete();

  vtkPointLocator* uniformLocator = vtkPointLocator::New();
  uniformLocator->SetDataSet(sgrid);
  vtkKdTreePointLocator* kdtree = vtkKdTreePointLocator::New();
  kdtree->SetDataSet(sgrid);

  double bounds[6];
  sgrid->GetBounds(bounds);
  for(i=0;i<3;i++)
    {
    //expand the search so we are looking for points inside and outside the BB
    bounds[i*2] *= .5;
    bounds[i*2+1] *= 1.2;
    }
  int numSearchPoints = 20;
  vtkIdList* uniformList = vtkIdList::New();
  vtkIdList* kdtreeList = vtkIdList::New();
  for(i=0;i<numSearchPoints;i++)
    {
    double point[3] = {(bounds[0]+(bounds[1]-bounds[0])*i/numSearchPoints),
                       (bounds[2]+(bounds[3]-bounds[2])*i/numSearchPoints),
                       (bounds[4]+(bounds[5]-bounds[4])*i/numSearchPoints)};
    vtkIdType uniformPt = uniformLocator->FindClosestPoint(point);
    vtkIdType kdtreePt = kdtree->FindClosestPoint(point);
    if(!ArePointsEquidistant(point, uniformPt, kdtreePt, sgrid))
      {
      cerr << " from FindClosestPoint.\n";
      rval++;
      }
    int N = 1+i*250/numSearchPoints; // test different amounts of points to search for
    uniformLocator->FindClosestNPoints(N, point, uniformList);
    kdtree->FindClosestNPoints(N, point, kdtreeList);
    if(!ArePointsEquidistant(point, uniformPt, uniformList->GetId(0), sgrid))
      {
      cerr << "for comparing FindClosestPoint and first result of FindClosestNPoints for uniform locator.\n";
      rval++;
      }
    if(!ArePointsEquidistant(point, kdtreePt, kdtreeList->GetId(0), sgrid))
      {
      cerr << "for comparing FindClosestPoint and first result of FindClosestNPoints for kdtree locator.\n";
      rval++;
      }

    for(j=0;j<N;j++)
      {
      if(!ArePointsEquidistant(point, kdtreeList->GetId(j), uniformList->GetId(j), sgrid))
        {
        cerr << "for point " << j << " for ClosestNPoints search.\n";
        rval++;
        }
      }
    double radius = 10;
    uniformLocator->FindPointsWithinRadius(radius, point, uniformList);
    kdtree->FindPointsWithinRadius(radius, point, kdtreeList);
    if(!DoesListHaveProperPoints(point, uniformList, kdtreeList, sgrid) ||
       !DoesListHaveProperPoints(point, kdtreeList, uniformList, sgrid))
      {
      cerr << "Problem with FindPointsWithinRadius\n";
      rval++;
      }

    double dist2;
    uniformPt = uniformLocator->FindClosestPointWithinRadius(radius, point, dist2);
    kdtreePt = kdtree->FindClosestPointWithinRadius(radius, point, dist2);
    if(uniformPt < 0 || kdtreePt < 0)
      {
      if(uniformPt >=0 || kdtreePt >= 0)
        {
        cerr << "Inconsistent results for FindClosestPointWithinRadius\n";
        rval++;
        }
      }
    else if(!ArePointsEquidistant(point, uniformPt, kdtreePt, sgrid))
      {
      cerr << "Incorrect result for FindClosestPointWithinRadius.\n";
      rval++;
      }
    if(uniformPt >= 0)
      {
      uniformList->Reset();
      uniformList->InsertNextId(uniformPt);
      if(!DoesListHaveProperPoints(point, uniformList, kdtreeList, sgrid))
        {
        cerr << "Inconsistent results for FindClosestPointWithinRadius and FindPointsWithRadius\n";
        rval++;
        }
      }
    }

  uniformList->Delete();
  kdtreeList->Delete();

  kdtree->Delete();
  uniformLocator->Delete();
  sgrid->Delete();

  return rval; // returns 0 if all tests passes
}

<commit_msg>ENH: Added a test that found a bug that wasn't found by any previous test for the point locators.<commit_after>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    TestPointLocators.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

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

=========================================================================*/
#include "vtkIdList.h"
#include "vtkKdTree.h"
#include "vtkKdTreePointLocator.h"
#include "vtkMath.h"
#include "vtkPointLocator.h"
#include "vtkPoints.h"
#include "vtkStructuredGrid.h"

#include <vtkstd/vector>

// returns true if 2 points are equidistant from x, within a tolerance
bool ArePointsEquidistant(double x[3], vtkIdType id1, vtkIdType id2,
                          vtkPointSet* grid)
{
  if(id1 == id2)
    {
    return true;
    }
  float firstDist2 = vtkMath::Distance2BetweenPoints(
    x, grid->GetPoint(id1));
  float secondDist2 = vtkMath::Distance2BetweenPoints(
    x, grid->GetPoint(id2));

  float differenceDist2 = firstDist2 - secondDist2;
  if(differenceDist2 < 0)
    {
    differenceDist2 = -differenceDist2;
    }

  if(differenceDist2/(firstDist2+secondDist2) >
     .00001)
    {
    cerr << "Results do not match (first dist2="
         << firstDist2 << " , second dist2=" << secondDist2 << ") ";
    return false;
    }
  return true;
}

// checks that every point in firstList has a matching point (based
// on distance) in secondList
bool DoesListHaveProperPoints(double x[3], vtkIdList* firstList,
                              vtkIdList* secondList,
                              vtkPointSet* grid)
{
  for(vtkIdType uid=0;uid<firstList->GetNumberOfIds();uid++)
    {
    int found = 0;
    for(vtkIdType kid=0;kid<secondList->GetNumberOfIds();kid++)
      {
      if(firstList->GetId(uid) == secondList->GetId(kid))
        {
        found = 1;
        break;
        }
      }
    if(!found)
      {
      for(vtkIdType kid=0;kid<secondList->GetNumberOfIds();kid++)
        {
        if(ArePointsEquidistant(x, firstList->GetId(uid),
                                secondList->GetId(kid), grid))
          {
          found = 1;
          break;
          }
        }
      }
    if(!found)
      {
      return 0;
      }
    }
  return 1;

}

// This test compares results for different point locators since they should
// all return the same results (within a tolerance)
int ComparePointLocators()
{
  int rval = 0;
  int i, j, k, kOffset, jOffset, offset;
  float x[3];
  static int dims[3]={39,31,31};

  // Create the structured grid.
  vtkStructuredGrid *sgrid = vtkStructuredGrid::New();
    sgrid->SetDimensions(dims);

  // We also create the points.
  vtkPoints *points = vtkPoints::New();
    points->Allocate(dims[0]*dims[1]*dims[2]);

  for ( k=0; k<dims[2]; k++)
    {
    x[2] = 1.0 + k*1.2;
    kOffset = k * dims[0] * dims[1];
    for (j=0; j<dims[1]; j++)
      {
      x[1] = sqrt(10.+j*2.);
      jOffset = j * dims[0];
      for (i=0; i<dims[0]; i++)
        {
        x[0] = 1+i*i*.5;
        offset = i + jOffset + kOffset;
        points->InsertPoint(offset,x);
        }
      }
    }
  sgrid->SetPoints(points);
  sgrid->Update();
  points->Delete();

  vtkPointLocator* uniformLocator = vtkPointLocator::New();
  uniformLocator->SetDataSet(sgrid);
  vtkKdTreePointLocator* kdtree = vtkKdTreePointLocator::New();
  kdtree->SetDataSet(sgrid);

  double bounds[6];
  sgrid->GetBounds(bounds);
  for(i=0;i<3;i++)
    {
    //expand the search so we are looking for points inside and outside the BB
    bounds[i*2] *= .5;
    bounds[i*2+1] *= 1.2;
    }
  int numSearchPoints = 20;
  vtkIdList* uniformList = vtkIdList::New();
  vtkIdList* kdtreeList = vtkIdList::New();
  for(i=0;i<numSearchPoints;i++)
    {
    double point[3] = {(bounds[0]+(bounds[1]-bounds[0])*i/numSearchPoints),
                       (bounds[2]+(bounds[3]-bounds[2])*i/numSearchPoints),
                       (bounds[4]+(bounds[5]-bounds[4])*i/numSearchPoints)};
    vtkIdType uniformPt = uniformLocator->FindClosestPoint(point);
    vtkIdType kdtreePt = kdtree->FindClosestPoint(point);
    if(!ArePointsEquidistant(point, uniformPt, kdtreePt, sgrid))
      {
      cerr << " from FindClosestPoint.\n";
      rval++;
      }
    int N = 1+i*250/numSearchPoints; // test different amounts of points to search for
    uniformLocator->FindClosestNPoints(N, point, uniformList);
    kdtree->FindClosestNPoints(N, point, kdtreeList);
    if(!ArePointsEquidistant(point, uniformPt, uniformList->GetId(0), sgrid))
      {
      cerr << "for comparing FindClosestPoint and first result of FindClosestNPoints for uniform locator.\n";
      rval++;
      }
    if(!ArePointsEquidistant(point, kdtreePt, kdtreeList->GetId(0), sgrid))
      {
      cerr << "for comparing FindClosestPoint and first result of FindClosestNPoints for kdtree locator.\n";
      rval++;
      }

    for(j=0;j<N;j++)
      {
      if(!ArePointsEquidistant(point, kdtreeList->GetId(j), uniformList->GetId(j), sgrid))
        {
        cerr << "for point " << j << " for ClosestNPoints search.\n";
        rval++;
        }
      }
    double radius = 10;
    uniformLocator->FindPointsWithinRadius(radius, point, uniformList);
    kdtree->FindPointsWithinRadius(radius, point, kdtreeList);
    if(!DoesListHaveProperPoints(point, uniformList, kdtreeList, sgrid) ||
       !DoesListHaveProperPoints(point, kdtreeList, uniformList, sgrid))
      {
      cerr << "Problem with FindPointsWithinRadius\n";
      rval++;
      }

    double dist2;
    uniformPt = uniformLocator->FindClosestPointWithinRadius(radius, point, dist2);
    kdtreePt = kdtree->FindClosestPointWithinRadius(radius, point, dist2);
    if(uniformPt < 0 || kdtreePt < 0)
      {
      if(uniformPt >=0 || kdtreePt >= 0)
        {
        cerr << "Inconsistent results for FindClosestPointWithinRadius\n";
        rval++;
        }
      }
    else if(!ArePointsEquidistant(point, uniformPt, kdtreePt, sgrid))
      {
      cerr << "Incorrect result for FindClosestPointWithinRadius.\n";
      rval++;
      }
    if(uniformPt >= 0)
      {
      uniformList->Reset();
      uniformList->InsertNextId(uniformPt);
      if(!DoesListHaveProperPoints(point, uniformList, kdtreeList, sgrid))
        {
        cerr << "Inconsistent results for FindClosestPointWithinRadius and FindPointsWithRadius\n";
        rval++;
        }
      }
    }

  uniformList->Delete();
  kdtreeList->Delete();

  kdtree->Delete();
  uniformLocator->Delete();
  sgrid->Delete();

  return rval; // returns 0 if all tests passes
}

// This test does a brute force test on the KdTree point locator
// to make sure that at least one of the point locators used
// above gives a correct result for FindClosestPoint().
int TestKdTreePointLocator()
{
  int rval = 0;
  vtkIdType num_points = 1000;
  vtkIdType num_test_points = 100;

  vtkIdType idA;
  vtkIdType closest_id;
  vtkIdType point;
  vtkIdType test_point;

  double pointA[3];
  double pointB[3];

  vtkPoints * A = vtkPoints::New();
  A->SetDataTypeToDouble();
  A->SetNumberOfPoints( num_points );
  for ( point = 0; point < num_points; ++point )
    {
    pointA[0] = ((double) rand()) / RAND_MAX;
    pointA[1] = ((double) rand()) / RAND_MAX;
    pointA[2] = ((double) rand()) / RAND_MAX;
    A->SetPoint( point, pointA );
    }

  vtkKdTree * kd = vtkKdTree::New();
  kd->BuildLocatorFromPoints( A );

  for ( test_point = 0; test_point < num_test_points; ++test_point )
    {
    double min_dist2 = 10.0;
    pointB[0] = ((double) rand()) / RAND_MAX;
    pointB[1] = ((double) rand()) / RAND_MAX;
    pointB[2] = ((double) rand()) / RAND_MAX;
    for ( point = 0; point < num_points; ++point )
      {
      double dist2;
      double dx, dy, dz;
      A->GetPoint( point, pointA );
      dx = pointA[0] - pointB[0];
      dy = pointA[1] - pointB[1];
      dz = pointA[2] - pointB[2];
      dist2 = dx * dx + dy * dy + dz * dz;
      if ( dist2 < min_dist2 )
        {
        closest_id = point;
        min_dist2 = dist2;
        }
      }
    double ld2;
    idA = kd->FindClosestPoint( pointB, ld2 );
    double diff = ld2 - min_dist2;
    if(diff < 0)
      {
      diff = -diff;
      }
    if ( (idA != closest_id ) && ( diff/ld2 > .000001 ))
      {
      cerr << "KdTree found the closest point to be " << ld2
           << " away but a brute force method returned a closer distance of "
                << min_dist2 << endl;
      rval++;
      }
    }

  return rval;
}

int TestPointLocators(int , char *[])
{
  int rval = ComparePointLocators();
  rval += TestKdTreePointLocator();

  return rval;
}
<|endoftext|>


<commit_before>/***********************************************************************************************************************
**
** Copyright (c) 2011, 2015 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
**    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
**      disclaimer.
**    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
**      following disclaimer in the documentation and/or other materials provided with the distribution.
**    * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
**      derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
***********************************************************************************************************************/

#include "Filter.h"

#include "../query_framework/QueryRegistry.h"
#include "../query_framework/QueryParsingException.h"

#include "ModelBase/src/util/SymbolMatcher.h"

namespace InformationScripting {

Optional<TupleSet> Filter::executeLinear(TupleSet input)
{
	auto filterTag = arguments_.positionalArgument(0);

	if (!filterTag.contains("."))
		return {"filter: requires tagged value to filter by, i.e. tag.value."};

	auto tagParts = filterTag.split(".");
	QString tupleTag = tagParts[0];

	if (tagParts.size() < 2)
		return TupleSet(input.take(tupleTag).toList());

	if (arguments_.numPositionalArguments() < 2)
		return {"filter: when filtering by value, a value is required."};

	auto filterBy = removeOuterQuotes(arguments_.positionalArgument(1));
	QString tupleValueTag = tagParts[1];
	TupleSet result;

	for (const auto& candidate : input.take(tupleTag))
	{
		auto it = candidate.find(tupleValueTag);
		if (it == candidate.end())
			return {QString("Tuple %1 does not contain %2").arg(tupleTag, tupleValueTag)};
		Property value = it->second;
		if (!value.isConvertibleTo<QString>())
			return {"filter only works on string values"};
		QString valueString = value;
		auto matcher = Model::SymbolMatcher::guessMatcher(filterBy);
		if (matcher.matches(valueString))
			result.add(candidate);
	}
	return result;
}

void Filter::registerDefaultQueries()
{
	QueryRegistry::registerQuery<Filter>("filter");
}

Filter::Filter(Model::Node* target, QStringList args)
	: LinearQuery{target}, arguments_{{
			PositionalArgument{"tag", "The tag to filter on"},
			PositionalArgument{"by", "The value by which we should filter"}
	}, args}
{
	if (arguments_.numPositionalArguments() < 1)
		throw QueryParsingException(arguments_.queryName() + " Requires at least one arguments");
}

QString Filter::removeOuterQuotes(const QString& from)
{
	QString result = from;
	if (result.startsWith("\""))
		result = result.mid(1);
	if (result.endsWith("\""))
		result = result.left(result.size() - 1);
	return result;
}

} /* namespace InformationScripting */
<commit_msg>Remove a wrong return statement in the filter query<commit_after>/***********************************************************************************************************************
**
** Copyright (c) 2011, 2015 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
**    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
**      disclaimer.
**    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
**      following disclaimer in the documentation and/or other materials provided with the distribution.
**    * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
**      derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
***********************************************************************************************************************/

#include "Filter.h"

#include "../query_framework/QueryRegistry.h"
#include "../query_framework/QueryParsingException.h"

#include "ModelBase/src/util/SymbolMatcher.h"

namespace InformationScripting {

Optional<TupleSet> Filter::executeLinear(TupleSet input)
{
	auto filterTag = arguments_.positionalArgument(0);

	auto tagParts = filterTag.split(".");
	QString tupleTag = tagParts[0];

	if (tagParts.size() < 2)
		return TupleSet(input.take(tupleTag).toList());

	if (arguments_.numPositionalArguments() < 2)
		return {"filter: when filtering by value, a value is required."};

	auto filterBy = removeOuterQuotes(arguments_.positionalArgument(1));
	QString tupleValueTag = tagParts[1];
	TupleSet result;

	for (const auto& candidate : input.take(tupleTag))
	{
		auto it = candidate.find(tupleValueTag);
		if (it == candidate.end())
			return {QString("Tuple %1 does not contain %2").arg(tupleTag, tupleValueTag)};
		Property value = it->second;
		if (!value.isConvertibleTo<QString>())
			return {"filter only works on string values"};
		QString valueString = value;
		auto matcher = Model::SymbolMatcher::guessMatcher(filterBy);
		if (matcher.matches(valueString))
			result.add(candidate);
	}
	return result;
}

void Filter::registerDefaultQueries()
{
	QueryRegistry::registerQuery<Filter>("filter");
}

Filter::Filter(Model::Node* target, QStringList args)
	: LinearQuery{target}, arguments_{{
			PositionalArgument{"tag", "The tag to filter on"},
			PositionalArgument{"by", "The value by which we should filter"}
	}, args}
{
	if (arguments_.numPositionalArguments() < 1)
		throw QueryParsingException(arguments_.queryName() + " Requires at least one arguments");
}

QString Filter::removeOuterQuotes(const QString& from)
{
	QString result = from;
	if (result.startsWith("\""))
		result = result.mid(1);
	if (result.endsWith("\""))
		result = result.left(result.size() - 1);
	return result;
}

} /* namespace InformationScripting */
<|endoftext|>


<commit_before>#include <ROOT/RDataFrame.hxx>
#include <ROOT/RArrowDS.hxx>
#include <ROOT/TSeq.hxx>

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#include <arrow/builder.h>
#include <arrow/memory_pool.h>
#include <arrow/record_batch.h>
#include <arrow/table.h>
#include <arrow/test-util.h>
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

#include <gtest/gtest.h>

#include <iostream>

using namespace ROOT;
using namespace ROOT::RDF;
using namespace arrow;

std::shared_ptr<Schema> exampleSchema()
{
   return schema({field("Name", arrow::utf8()), field("Age", arrow::int64()), field("Height", arrow::float64()),
                  field("Married", arrow::boolean()), field("Babies", arrow::uint32())});
}

std::shared_ptr<Table> createTestTable()
{
   auto schema_ = exampleSchema();

   std::vector<bool> is_valid(6, true);
   std::vector<std::string> names = {"Harry", "Bob,Bob", "\"Joe\"", "Tom", " John  ", " Mary Ann "};
   std::vector<int64_t> ages = {64, 50, 40, 30, 2, 0};
   std::vector<double> heights = {180.0, 200.5, 1.7, 1.9, 1.0, 0.8};
   std::vector<bool> marriageStatus = {true, true, false, true, false, false};
   std::vector<unsigned int> babies = {1, 0, 2, 3, 4, 21};

   std::shared_ptr<Array> arrays_[5];

   arrow::ArrayFromVector<StringType, std::string>(names, &arrays_[0]);
   arrow::ArrayFromVector<Int64Type, int64_t>(ages, &arrays_[1]);
   arrow::ArrayFromVector<DoubleType, double>(heights, &arrays_[2]);
   arrow::ArrayFromVector<BooleanType, bool>(marriageStatus, &arrays_[3]);
   arrow::ArrayFromVector<UInt32Type, unsigned int>(babies, &arrays_[4]);

   std::vector<std::shared_ptr<Column>> columns_ = {
      std::make_shared<Column>(schema_->field(0), arrays_[0]), std::make_shared<Column>(schema_->field(1), arrays_[1]),
      std::make_shared<Column>(schema_->field(2), arrays_[2]), std::make_shared<Column>(schema_->field(3), arrays_[3]),
      std::make_shared<Column>(schema_->field(4), arrays_[4])};

   auto table_ = Table::Make(schema_, columns_);
   return table_;
}

TEST(RArrowDS, ColTypeNames)
{
   RArrowDS tds(createTestTable(), {"Name", "Age", "Height", "Married", "Babies"});
   tds.SetNSlots(1);

   auto colNames = tds.GetColumnNames();

   EXPECT_TRUE(tds.HasColumn("Name"));
   EXPECT_TRUE(tds.HasColumn("Age"));
   EXPECT_FALSE(tds.HasColumn("Address"));

   ASSERT_EQ(colNames.size(), 5U);
   EXPECT_STREQ("Height", colNames[2].c_str());
   EXPECT_STREQ("Married", colNames[3].c_str());

   EXPECT_STREQ("string", tds.GetTypeName("Name").c_str());
   EXPECT_STREQ("Long64_t", tds.GetTypeName("Age").c_str());
   EXPECT_STREQ("double", tds.GetTypeName("Height").c_str());
   EXPECT_STREQ("bool", tds.GetTypeName("Married").c_str());
   EXPECT_STREQ("ULong_t", tds.GetTypeName("Babies").c_str());
}

TEST(RArrowDS, EntryRanges)
{
   RArrowDS tds(createTestTable(), {});
   tds.SetNSlots(3U);
   tds.Initialise();

   // Still dividing in equal parts...
   auto ranges = tds.GetEntryRanges();

   ASSERT_EQ(3U, ranges.size());
   EXPECT_EQ(0U, ranges[0].first);
   EXPECT_EQ(2U, ranges[0].second);
   EXPECT_EQ(2U, ranges[1].first);
   EXPECT_EQ(4U, ranges[1].second);
   EXPECT_EQ(4U, ranges[2].first);
   EXPECT_EQ(6U, ranges[2].second);
}

TEST(RArrowDS, ColumnReaders)
{
   RArrowDS tds(createTestTable(), {});

   const auto nSlots = 3U;
   tds.SetNSlots(nSlots);
   auto vals = tds.GetColumnReaders<Long64_t>("Age");
   tds.Initialise();
   auto ranges = tds.GetEntryRanges();
   auto slot = 0U;
   std::vector<Long64_t> ages = {64, 50, 40, 30, 2, 0};
   for (auto &&range : ranges) {
      tds.InitSlot(slot, range.first);
      ASSERT_LT(slot, vals.size());
      for (auto i : ROOT::TSeq<int>(range.first, range.second)) {
         tds.SetEntry(slot, i);
         auto val = **vals[slot];
         EXPECT_EQ(ages[i], val);
      }
      slot++;
   }
}

TEST(RArrowDS, ColumnReadersString)
{
   RArrowDS tds(createTestTable(), {});

   const auto nSlots = 3U;
   tds.SetNSlots(nSlots);
   auto vals = tds.GetColumnReaders<std::string>("Name");
   tds.Initialise();
   auto ranges = tds.GetEntryRanges();
   auto slot = 0U;
   std::vector<std::string> names = {"Harry", "Bob,Bob", "\"Joe\"", "Tom", " John  ", " Mary Ann "};
   for (auto &&range : ranges) {
      tds.InitSlot(slot, range.first);
      ASSERT_LT(slot, vals.size());
      for (auto i : ROOT::TSeqU(range.first, range.second)) {
         tds.SetEntry(slot, i);
         auto val = *((std::string *)*vals[slot]);
         ASSERT_LT(i, names.size());
         EXPECT_EQ(names[i], val);
      }
      slot++;
   }
}

#ifndef NDEBUG

TEST(RArrowDS, SetNSlotsTwice)
{
   auto theTest = []() {
      RArrowDS tds(createTestTable(), {});
      tds.SetNSlots(1);
      tds.SetNSlots(1);
   };
   ASSERT_DEATH(theTest(), "Setting the number of slots even if the number of slots is different from zero.");
}
#endif

#ifdef R__B64

TEST(RArrowDS, FromARDF)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame rdf(std::move(tds));
   auto max = rdf.Max<double>("Height");
   auto min = rdf.Min<double>("Height");
   auto c = rdf.Count();

   EXPECT_EQ(6U, *c);
   EXPECT_DOUBLE_EQ(200.5, *max);
   EXPECT_DOUBLE_EQ(0.8, *min);
}

TEST(RArrowDS, FromARDFWithJitting)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame rdf(std::move(tds));
   auto max = rdf.Filter("Age<40").Max("Age");
   auto min = rdf.Define("Age2", "Age").Filter("Age2>30").Min("Age2");

   EXPECT_EQ(30, *max);
   EXPECT_EQ(40, *min);
}

// NOW MT!-------------
#ifdef R__USE_IMT

TEST(RArrowDS, DefineSlotCheckMT)
{
   const auto nSlots = 4U;
   ROOT::EnableImplicitMT(nSlots);

   std::vector<unsigned int> ids(nSlots, 0u);
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame d(std::move(tds));
   auto m = d.DefineSlot("x", [&](unsigned int slot) {
                ids[slot] = 1u;
                return 1;
             }).Max("x");
   EXPECT_EQ(1, *m); // just in case

   const auto nUsedSlots = std::accumulate(ids.begin(), ids.end(), 0u);
   EXPECT_GT(nUsedSlots, 0u);
   EXPECT_LE(nUsedSlots, nSlots);
}

TEST(RArrowDS, FromARDFMT)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame tdf(std::move(tds));
   auto max = tdf.Max<double>("Height");
   auto min = tdf.Min<double>("Height");
   auto c = tdf.Count();

   EXPECT_EQ(6U, *c);
   EXPECT_DOUBLE_EQ(200.5, *max);
   EXPECT_DOUBLE_EQ(.8, *min);
}

TEST(RArrowDS, FromARDFWithJittingMT)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame tdf(std::move(tds));
   auto max = tdf.Filter("Age<40").Max("Age");
   auto min = tdf.Define("Age2", "Age").Filter("Age2>30").Min("Age2");

   EXPECT_EQ(30, *max);
   EXPECT_EQ(40, *min);
}

#endif // R__USE_IMT

#endif // R__B64
<commit_msg>[DF] Add missing include in RArrowDS test<commit_after>#include <ROOT/RDataFrame.hxx>
#include <ROOT/RArrowDS.hxx>
#include <ROOT/TSeq.hxx>
#include <TROOT.h>

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
#include <arrow/builder.h>
#include <arrow/memory_pool.h>
#include <arrow/record_batch.h>
#include <arrow/table.h>
#include <arrow/test-util.h>
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

#include <gtest/gtest.h>

#include <iostream>

using namespace ROOT;
using namespace ROOT::RDF;
using namespace arrow;

std::shared_ptr<Schema> exampleSchema()
{
   return schema({field("Name", arrow::utf8()), field("Age", arrow::int64()), field("Height", arrow::float64()),
                  field("Married", arrow::boolean()), field("Babies", arrow::uint32())});
}

std::shared_ptr<Table> createTestTable()
{
   auto schema_ = exampleSchema();

   std::vector<bool> is_valid(6, true);
   std::vector<std::string> names = {"Harry", "Bob,Bob", "\"Joe\"", "Tom", " John  ", " Mary Ann "};
   std::vector<int64_t> ages = {64, 50, 40, 30, 2, 0};
   std::vector<double> heights = {180.0, 200.5, 1.7, 1.9, 1.0, 0.8};
   std::vector<bool> marriageStatus = {true, true, false, true, false, false};
   std::vector<unsigned int> babies = {1, 0, 2, 3, 4, 21};

   std::shared_ptr<Array> arrays_[5];

   arrow::ArrayFromVector<StringType, std::string>(names, &arrays_[0]);
   arrow::ArrayFromVector<Int64Type, int64_t>(ages, &arrays_[1]);
   arrow::ArrayFromVector<DoubleType, double>(heights, &arrays_[2]);
   arrow::ArrayFromVector<BooleanType, bool>(marriageStatus, &arrays_[3]);
   arrow::ArrayFromVector<UInt32Type, unsigned int>(babies, &arrays_[4]);

   std::vector<std::shared_ptr<Column>> columns_ = {
      std::make_shared<Column>(schema_->field(0), arrays_[0]), std::make_shared<Column>(schema_->field(1), arrays_[1]),
      std::make_shared<Column>(schema_->field(2), arrays_[2]), std::make_shared<Column>(schema_->field(3), arrays_[3]),
      std::make_shared<Column>(schema_->field(4), arrays_[4])};

   auto table_ = Table::Make(schema_, columns_);
   return table_;
}

TEST(RArrowDS, ColTypeNames)
{
   RArrowDS tds(createTestTable(), {"Name", "Age", "Height", "Married", "Babies"});
   tds.SetNSlots(1);

   auto colNames = tds.GetColumnNames();

   EXPECT_TRUE(tds.HasColumn("Name"));
   EXPECT_TRUE(tds.HasColumn("Age"));
   EXPECT_FALSE(tds.HasColumn("Address"));

   ASSERT_EQ(colNames.size(), 5U);
   EXPECT_STREQ("Height", colNames[2].c_str());
   EXPECT_STREQ("Married", colNames[3].c_str());

   EXPECT_STREQ("string", tds.GetTypeName("Name").c_str());
   EXPECT_STREQ("Long64_t", tds.GetTypeName("Age").c_str());
   EXPECT_STREQ("double", tds.GetTypeName("Height").c_str());
   EXPECT_STREQ("bool", tds.GetTypeName("Married").c_str());
   EXPECT_STREQ("ULong_t", tds.GetTypeName("Babies").c_str());
}

TEST(RArrowDS, EntryRanges)
{
   RArrowDS tds(createTestTable(), {});
   tds.SetNSlots(3U);
   tds.Initialise();

   // Still dividing in equal parts...
   auto ranges = tds.GetEntryRanges();

   ASSERT_EQ(3U, ranges.size());
   EXPECT_EQ(0U, ranges[0].first);
   EXPECT_EQ(2U, ranges[0].second);
   EXPECT_EQ(2U, ranges[1].first);
   EXPECT_EQ(4U, ranges[1].second);
   EXPECT_EQ(4U, ranges[2].first);
   EXPECT_EQ(6U, ranges[2].second);
}

TEST(RArrowDS, ColumnReaders)
{
   RArrowDS tds(createTestTable(), {});

   const auto nSlots = 3U;
   tds.SetNSlots(nSlots);
   auto vals = tds.GetColumnReaders<Long64_t>("Age");
   tds.Initialise();
   auto ranges = tds.GetEntryRanges();
   auto slot = 0U;
   std::vector<Long64_t> ages = {64, 50, 40, 30, 2, 0};
   for (auto &&range : ranges) {
      tds.InitSlot(slot, range.first);
      ASSERT_LT(slot, vals.size());
      for (auto i : ROOT::TSeq<int>(range.first, range.second)) {
         tds.SetEntry(slot, i);
         auto val = **vals[slot];
         EXPECT_EQ(ages[i], val);
      }
      slot++;
   }
}

TEST(RArrowDS, ColumnReadersString)
{
   RArrowDS tds(createTestTable(), {});

   const auto nSlots = 3U;
   tds.SetNSlots(nSlots);
   auto vals = tds.GetColumnReaders<std::string>("Name");
   tds.Initialise();
   auto ranges = tds.GetEntryRanges();
   auto slot = 0U;
   std::vector<std::string> names = {"Harry", "Bob,Bob", "\"Joe\"", "Tom", " John  ", " Mary Ann "};
   for (auto &&range : ranges) {
      tds.InitSlot(slot, range.first);
      ASSERT_LT(slot, vals.size());
      for (auto i : ROOT::TSeqU(range.first, range.second)) {
         tds.SetEntry(slot, i);
         auto val = *((std::string *)*vals[slot]);
         ASSERT_LT(i, names.size());
         EXPECT_EQ(names[i], val);
      }
      slot++;
   }
}

#ifndef NDEBUG

TEST(RArrowDS, SetNSlotsTwice)
{
   auto theTest = []() {
      RArrowDS tds(createTestTable(), {});
      tds.SetNSlots(1);
      tds.SetNSlots(1);
   };
   ASSERT_DEATH(theTest(), "Setting the number of slots even if the number of slots is different from zero.");
}
#endif

#ifdef R__B64

TEST(RArrowDS, FromARDF)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame rdf(std::move(tds));
   auto max = rdf.Max<double>("Height");
   auto min = rdf.Min<double>("Height");
   auto c = rdf.Count();

   EXPECT_EQ(6U, *c);
   EXPECT_DOUBLE_EQ(200.5, *max);
   EXPECT_DOUBLE_EQ(0.8, *min);
}

TEST(RArrowDS, FromARDFWithJitting)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame rdf(std::move(tds));
   auto max = rdf.Filter("Age<40").Max("Age");
   auto min = rdf.Define("Age2", "Age").Filter("Age2>30").Min("Age2");

   EXPECT_EQ(30, *max);
   EXPECT_EQ(40, *min);
}

// NOW MT!-------------
#ifdef R__USE_IMT

TEST(RArrowDS, DefineSlotCheckMT)
{
   const auto nSlots = 4U;
   ROOT::EnableImplicitMT(nSlots);

   std::vector<unsigned int> ids(nSlots, 0u);
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame d(std::move(tds));
   auto m = d.DefineSlot("x", [&](unsigned int slot) {
                ids[slot] = 1u;
                return 1;
             }).Max("x");
   EXPECT_EQ(1, *m); // just in case

   const auto nUsedSlots = std::accumulate(ids.begin(), ids.end(), 0u);
   EXPECT_GT(nUsedSlots, 0u);
   EXPECT_LE(nUsedSlots, nSlots);
}

TEST(RArrowDS, FromARDFMT)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame tdf(std::move(tds));
   auto max = tdf.Max<double>("Height");
   auto min = tdf.Min<double>("Height");
   auto c = tdf.Count();

   EXPECT_EQ(6U, *c);
   EXPECT_DOUBLE_EQ(200.5, *max);
   EXPECT_DOUBLE_EQ(.8, *min);
}

TEST(RArrowDS, FromARDFWithJittingMT)
{
   std::unique_ptr<RDataSource> tds(new RArrowDS(createTestTable(), {}));
   ROOT::RDataFrame tdf(std::move(tds));
   auto max = tdf.Filter("Age<40").Max("Age");
   auto min = tdf.Define("Age2", "Age").Filter("Age2>30").Min("Age2");

   EXPECT_EQ(30, *max);
   EXPECT_EQ(40, *min);
}

#endif // R__USE_IMT

#endif // R__B64
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * 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.
 */

// define some itksys* things to make ShareForward.h happy
#define itksys_SHARED_FORWARD_DIR_BUILD ""
#define itksys_SHARED_FORWARD_PATH_BUILD ""
#define itksys_SHARED_FORWARD_PATH_INSTALL ""
#define itksys_SHARED_FORWARD_EXE_BUILD ""
#define itksys_SHARED_FORWARD_EXE_INSTALL ""

#include <map>
#include <string>
#include <iostream>
#include <fstream>
#include "itksys/SystemTools.hxx"
#include "itkMacro.h"

// include SharedForward to avoid duplicating the code which find the library path variable
// name and the path separator
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include "itksys/SharedForward.h"
#pragma GCC diagnostic pop
#else
#include "itksys/SharedForward.h"
#endif

#include "itksys/Process.h"

// Temporary definition of otbTestMain
int otbTestMain(int arc, char * arv[]);

/** Display usage */
void usage()
{
  std::cerr << "usage: otbTestDriver [global_options] [non_regression_commands] Execute prg [args]" << std::endl;
  std::cerr << std::endl;
  std::cerr <<
  "otbTestDriver alter the environment, run a test program and does regression testing based on capabilities provided by otbTestMain.h"
            << std::endl;
  std::cerr << std::endl;
  std::cerr << "Global options:" << std::endl;
  std::cerr << "  --add-before-libpath PATH" << std::endl;
  std::cerr << "      Add a path to the library path environment. This option take care of" << std::endl;
  std::cerr << "      choosing the right environment variable for your system." << std::endl;
  std::cerr << "      This option can be used several times." << std::endl;
  std::cerr << std::endl;
  std::cerr << "  --add-before-env NAME VALUE" << std::endl;
  std::cerr << "      Add a VALUE to the variable name in the environment." << std::endl;
  std::cerr << "      This option can be used several times." << std::endl;
  std::cerr << std::endl;
  std::cerr << "  --help" << std::endl;
  std::cerr << "      Display this message and exit." << std::endl;
  std::cerr << std::endl;

}

/** This function parses the command line and process everything
 *  related to the --add-before-libpath, --add-before-env and --help.
 *  Every other args are added to the remainingArgs vector */
int parseCommandLine(int ac, char * av[], std::vector<char *>& remainingArgs)
{
  // parse the command line
  int  i = 1;
  bool skip = false;
  while (i < ac)
    {
    if (!skip && strcmp(av[i], "--add-before-libpath") == 0)
      {
      if (i + 1 >= ac)
        {
        usage();
        return 1;
        }
      std::string libpath = KWSYS_SHARED_FORWARD_LDPATH;
      libpath += "=";
      libpath += av[i + 1];
      char * oldenv = getenv(KWSYS_SHARED_FORWARD_LDPATH);
      if (oldenv)
        {
        libpath += KWSYS_SHARED_FORWARD_PATH_SEP;
        libpath += oldenv;
        }
      itksys::SystemTools::PutEnv(libpath.c_str());
      // on some 64 bit systems, LD_LIBRARY_PATH_64 is used before
      // LD_LIBRARY_PATH if it is set. It can lead the test to load
      // the system library instead of the expected one, so this
      // var must also be set
      if (std::string(KWSYS_SHARED_FORWARD_LDPATH) == "LD_LIBRARY_PATH")
        {
        std::string libpath64 = "LD_LIBRARY_PATH_64";
        libpath64 += "=";
        libpath64 += av[i + 1];
        char * oldenv2 = getenv("LD_LIBRARY_PATH_64");
        if (oldenv2)
          {
          libpath64 += KWSYS_SHARED_FORWARD_PATH_SEP;
          libpath64 += oldenv;
          }
        itksys::SystemTools::PutEnv(libpath64.c_str());
        }
      i += 2;
      }
    else if (!skip && strcmp(av[i], "--add-before-env") == 0)
      {
      if (i + 2 >= ac)
        {
        usage();
        return 1;
        }
      std::string env = av[i + 1];
      env += "=";
      env += av[i + 2];
      char * oldenv = getenv(av[i + 1]);
      if (oldenv)
        {
        env += KWSYS_SHARED_FORWARD_PATH_SEP;
        env += oldenv;
        }
      itksys::SystemTools::PutEnv(env.c_str());
      i += 3;
      }
    else if (!skip && strcmp(av[i], "--help") == 0)
      {
      usage();
      return 0;
      }
    else
      {
      remainingArgs.push_back(av[i]);
      i += 1;
      }
    }
  return 0;
}

int main(int ac, char* av[])
{
  // A vector to store remaining args
  std::vector<char*> remainingArgs;

  // First parse the command line for system wide options
  int ret = parseCommandLine(ac, av, remainingArgs);

  // Check for the return code
  if (ret)
    {
    std::cerr << "Error while parsing arguments, exiting ..." << std::endl;
    return 1;
    }

  // Check if there are remaining args
  if (remainingArgs.empty())
    {
    usage();
    return 1;
    }
  // a NULL is required at the end of the table
  char** argv = new char*[remainingArgs.size() + 2];
  for (int i = 0; i < static_cast<int>(remainingArgs.size()); ++i)
    {
    argv[i + 1] = remainingArgs[i];
    }
  argv[remainingArgs.size() + 1] = ITK_NULLPTR;

  /** Call to the otbTestMain */
  return otbTestMain(static_cast<int>(remainingArgs.size()), argv);

}

// This is a dummy main to be registered as a test for the otbTestMain
int Execute(int argc, char * argv[])
{
  argc -= 1;
  argv += 1;
  // Create the appropriate itk process
  itksysProcess * process = itksysProcess_New();
  itksysProcess_SetCommand(process, argv);
  itksysProcess_SetPipeShared(process, itksysProcess_Pipe_STDOUT, true);
  itksysProcess_SetPipeShared(process, itksysProcess_Pipe_STDERR, true);
  itksysProcess_Execute(process);
  itksysProcess_WaitForExit(process, ITK_NULLPTR);
  int retCode = itksysProcess_GetExitValue(process);
  return retCode;
}

// Include otbTestMain and switch otbTestMain with main definition in otbTestMain.h
#undef otbTestMain
#undef main
#define main otbTestMain
#include "otbTestMain.h"

void RegisterTests()
{
  REGISTER_TEST(Execute);
}
<commit_msg>Fix coverity #1271591<commit_after>/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * 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.
 */

// define some itksys* things to make ShareForward.h happy
#define itksys_SHARED_FORWARD_DIR_BUILD ""
#define itksys_SHARED_FORWARD_PATH_BUILD ""
#define itksys_SHARED_FORWARD_PATH_INSTALL ""
#define itksys_SHARED_FORWARD_EXE_BUILD ""
#define itksys_SHARED_FORWARD_EXE_INSTALL ""

#include <map>
#include <string>
#include <iostream>
#include <fstream>
#include "itksys/SystemTools.hxx"
#include "itkMacro.h"

// include SharedForward to avoid duplicating the code which find the library path variable
// name and the path separator
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include "itksys/SharedForward.h"
#pragma GCC diagnostic pop
#else
#include "itksys/SharedForward.h"
#endif

#include "itksys/Process.h"

// Temporary definition of otbTestMain
int otbTestMain(int arc, char * arv[]);

/** Display usage */
void usage()
{
  std::cerr << "usage: otbTestDriver [global_options] [non_regression_commands] Execute prg [args]" << std::endl;
  std::cerr << std::endl;
  std::cerr <<
  "otbTestDriver alter the environment, run a test program and does regression testing based on capabilities provided by otbTestMain.h"
            << std::endl;
  std::cerr << std::endl;
  std::cerr << "Global options:" << std::endl;
  std::cerr << "  --add-before-libpath PATH" << std::endl;
  std::cerr << "      Add a path to the library path environment. This option take care of" << std::endl;
  std::cerr << "      choosing the right environment variable for your system." << std::endl;
  std::cerr << "      This option can be used several times." << std::endl;
  std::cerr << std::endl;
  std::cerr << "  --add-before-env NAME VALUE" << std::endl;
  std::cerr << "      Add a VALUE to the variable name in the environment." << std::endl;
  std::cerr << "      This option can be used several times." << std::endl;
  std::cerr << std::endl;
  std::cerr << "  --help" << std::endl;
  std::cerr << "      Display this message and exit." << std::endl;
  std::cerr << std::endl;

}

/** This function parses the command line and process everything
 *  related to the --add-before-libpath, --add-before-env and --help.
 *  Every other args are added to the remainingArgs vector */
int parseCommandLine(int ac, char * av[], std::vector<char *>& remainingArgs)
{
  // parse the command line
  int  i = 1;
  bool skip = false;
  while (i < ac)
    {
    if (!skip && strcmp(av[i], "--add-before-libpath") == 0)
      {
      if (i + 1 >= ac)
        {
        usage();
        return 1;
        }
      std::string libpath = KWSYS_SHARED_FORWARD_LDPATH;
      libpath += "=";
      libpath += av[i + 1];
      char * oldenv = getenv(KWSYS_SHARED_FORWARD_LDPATH);
      if (oldenv)
        {
        libpath += KWSYS_SHARED_FORWARD_PATH_SEP;
        libpath += oldenv;
        }
      itksys::SystemTools::PutEnv(libpath.c_str());
      // on some 64 bit systems, LD_LIBRARY_PATH_64 is used before
      // LD_LIBRARY_PATH if it is set. It can lead the test to load
      // the system library instead of the expected one, so this
      // var must also be set
      if (std::string(KWSYS_SHARED_FORWARD_LDPATH) == "LD_LIBRARY_PATH")
        {
        std::string libpath64 = "LD_LIBRARY_PATH_64";
        libpath64 += "=";
        libpath64 += av[i + 1];
        char * oldenv2 = getenv("LD_LIBRARY_PATH_64");
        if (oldenv2)
          {
          libpath64 += KWSYS_SHARED_FORWARD_PATH_SEP;
          libpath64 += oldenv2;
          }
        itksys::SystemTools::PutEnv(libpath64.c_str());
        }
      i += 2;
      }
    else if (!skip && strcmp(av[i], "--add-before-env") == 0)
      {
      if (i + 2 >= ac)
        {
        usage();
        return 1;
        }
      std::string env = av[i + 1];
      env += "=";
      env += av[i + 2];
      char * oldenv = getenv(av[i + 1]);
      if (oldenv)
        {
        env += KWSYS_SHARED_FORWARD_PATH_SEP;
        env += oldenv;
        }
      itksys::SystemTools::PutEnv(env.c_str());
      i += 3;
      }
    else if (!skip && strcmp(av[i], "--help") == 0)
      {
      usage();
      return 0;
      }
    else
      {
      remainingArgs.push_back(av[i]);
      i += 1;
      }
    }
  return 0;
}

int main(int ac, char* av[])
{
  // A vector to store remaining args
  std::vector<char*> remainingArgs;

  // First parse the command line for system wide options
  int ret = parseCommandLine(ac, av, remainingArgs);

  // Check for the return code
  if (ret)
    {
    std::cerr << "Error while parsing arguments, exiting ..." << std::endl;
    return 1;
    }

  // Check if there are remaining args
  if (remainingArgs.empty())
    {
    usage();
    return 1;
    }
  // a NULL is required at the end of the table
  char** argv = new char*[remainingArgs.size() + 2];
  for (int i = 0; i < static_cast<int>(remainingArgs.size()); ++i)
    {
    argv[i + 1] = remainingArgs[i];
    }
  argv[remainingArgs.size() + 1] = ITK_NULLPTR;

  /** Call to the otbTestMain */
  return otbTestMain(static_cast<int>(remainingArgs.size()), argv);

}

// This is a dummy main to be registered as a test for the otbTestMain
int Execute(int argc, char * argv[])
{
  argc -= 1;
  argv += 1;
  // Create the appropriate itk process
  itksysProcess * process = itksysProcess_New();
  itksysProcess_SetCommand(process, argv);
  itksysProcess_SetPipeShared(process, itksysProcess_Pipe_STDOUT, true);
  itksysProcess_SetPipeShared(process, itksysProcess_Pipe_STDERR, true);
  itksysProcess_Execute(process);
  itksysProcess_WaitForExit(process, ITK_NULLPTR);
  int retCode = itksysProcess_GetExitValue(process);
  return retCode;
}

// Include otbTestMain and switch otbTestMain with main definition in otbTestMain.h
#undef otbTestMain
#undef main
#define main otbTestMain
#include "otbTestMain.h"

void RegisterTests()
{
  REGISTER_TEST(Execute);
}
<|endoftext|>