/******************************************************************************
Copyright (C) 2009 Matteo Lucarelli

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; either version 2
of the License, or (at your option) any later version.

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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
******************************************************************************/

// OpenGL/FLTK 2D display with zoom, drag, measures and dynamic grid
// Use notes are in header file

#include "Fl_gl2display.h"

// Fl_gl2displayLabel //////////////////////////////////////////////////////////////////////////////////////////

Fl_gl2displayLabel::Fl_gl2displayLabel(int X, int Y, int W, int H, Fl_gl2display_drawCB *drawCB) : Fl_Box(X,Y,W,H) 
{
	m_Display=new Fl_gl2display(X,Y,W,H-20,drawCB);
	
	m_Label=new Fl_Output(X,Y+H-20,W,20);
	
	m_Label->color(0);
	m_Label->textcolor(FL_GREEN);
	m_Label->box(FL_FLAT_BOX);
	m_Label->textfont(FL_SCREEN);
	
	m_Display->setLabel(m_Label);
}

Fl_gl2displayLabel::~Fl_gl2displayLabel()
{
	delete m_Display;
	delete m_Label;
}

GLdouble Fl_gl2displayLabel::getXoffset()
{
	return m_Display->getXoffset();
}

GLdouble Fl_gl2displayLabel::getYoffset()
{
	return m_Display->getYoffset();
}

GLdouble Fl_gl2displayLabel::getScale()
{
	return m_Display->getScale();
}

void Fl_gl2displayLabel::setXoffset(GLdouble val)
{
	return m_Display->setXoffset(val);
}

void Fl_gl2displayLabel::setYoffset(GLdouble val)
{
	return m_Display->setYoffset(val);
}

void Fl_gl2displayLabel::setScale(GLdouble val)
{
	return m_Display->setScale(val);
}

void Fl_gl2displayLabel::draw()
{
	m_Display->redraw();
	m_Label->redraw();
}

void Fl_gl2displayLabel::setGrid(int minOrder,int maxOrder,int pixFilter,int baseColor, int stepColor)
{
	return m_Display->setGrid(minOrder,maxOrder,pixFilter,baseColor,stepColor);
}

// Fl_gl2display ///////////////////////////////////////////////////////////////////////////////////////////////

Fl_gl2display::Fl_gl2display(int X,int Y,int W,int H, Fl_gl2display_drawCB *drawCB) : Fl_Gl_Window(X,Y,W,H) 
{ 
	m_drawCB=drawCB;
	
	glClearColor(0,0,0,0);
	
	m_ScaleFactor=1;
	m_TranslateX=0;
	m_TranslateY=0;
	
	m_doGrid=false;
	
	m_Label=NULL;
}

Fl_gl2display::~Fl_gl2display()
{
}

void Fl_gl2display::refreshLabel()
{
	if (!m_Label) return;
	
	char tmp[1024];
	snprintf(tmp,1024,"Grid size:%dx%d - Cursor (%.2f,%.2f)",(int)(w()/m_ScaleFactor),(int)(h()/m_ScaleFactor),Fl::event_x()/m_ScaleFactor-m_TranslateX,(h()-Fl::event_y())/m_ScaleFactor-m_TranslateY);
	m_Label->value(tmp);
	m_Label->redraw();
}

void Fl_gl2display::setGrid(int minOrder,int maxOrder,int pixFilter,int baseColor,int stepColor)
{
	m_doGrid=true;
	m_minOrder=minOrder;
	m_maxOrder=maxOrder;
	m_pixFilter=pixFilter;
	
	m_baseColor=baseColor;
	m_stepColor=stepColor;
}

void Fl_gl2display::drawGrid()
{
	int color;
	float i,j,step;
	
	// limiti della finestra
	float xFrom=-m_TranslateX;
	float xTo=xFrom+w()/m_ScaleFactor;
	float yFrom=-m_TranslateY;
	float yTo=yFrom+h()/m_ScaleFactor;
	
	glBegin(GL_LINES);
	
	color=m_baseColor;

	for (j=m_minOrder;j<=m_maxOrder;j++){
	
		step=pow(10,j);
		
		if (step*m_ScaleFactor<m_pixFilter) continue;

		if (color>255) color=255;
		glColor3ub(0,(GLubyte)color,0);
		
		color += m_stepColor;	
		
		for (i=(int)(xFrom/step)*step;i<=(int)(xTo/step)*step;i+=step){
			glVertex2d(i,yFrom);
			glVertex2d(i,yTo);
		}
		
		for (i=(int)(yFrom/step)*step;i<=(int)(yTo/step)*step;i+=step){
			glVertex2d(xFrom,i);
			glVertex2d(xTo,i);
		}
	}
	
	glColor3ub(0,255,0);
	
	glVertex2d(xFrom,0);
	glVertex2d(xTo,0);
	
	glVertex2d(0,yFrom);
	glVertex2d(0,yTo);
	
	glEnd();
}

void Fl_gl2display::draw() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	
	
	enable2D();
	
	glPushMatrix();	
	glScaled(m_ScaleFactor,m_ScaleFactor,0);
	glTranslated(m_TranslateX,m_TranslateY,0);
	
	if (m_doGrid) drawGrid();	
	if (m_Label) refreshLabel();
	
	m_drawCB();
	glPopMatrix();   
	
	glViewport(0,0,w(),h());

	disable2D();
}

void Fl_gl2display::enable2D()
{
	int vPort[4];
	
	glGetIntegerv(GL_VIEWPORT, vPort);
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	
	glOrtho(0, vPort[2], 0, vPort[3], -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void Fl_gl2display::disable2D()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();   
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void Fl_gl2display::resize (int X,int Y, int W, int H)
{     
        glViewport(0,0,W,H);
	Fl_Gl_Window::resize(X,Y,W,H);
}

int Fl_gl2display::handle(int event)
{
	static int dragx,dragy;
	
	switch (event){
	
  		case FL_ENTER: 
			cursor(FL_CURSOR_CROSS);
			return 1; 
			break;
  		case FL_LEAVE: 
			cursor(FL_CURSOR_DEFAULT); 
			return 1; 
			break;	
	
		case FL_MOUSEWHEEL:
			if (Fl::event_dy()==-1) m_ScaleFactor *=1.189;
			if (Fl::event_dy()==1) m_ScaleFactor /=1.189;
			redraw();
			return 1;
			break;
			
		case FL_PUSH:
			dragx=Fl::event_x();
			dragy=Fl::event_y();
			return 1;
			break;
			
		case FL_RELEASE:
			return 1;
			break;
			
		case FL_DRAG:
			if (Fl::event_button()==1){
				m_TranslateX -= (dragx-Fl::event_x())/m_ScaleFactor;
				m_TranslateY += (dragy-Fl::event_y())/m_ScaleFactor;
				dragx=Fl::event_x();
				dragy=Fl::event_y();
				redraw();
				return 1;
			}else if(Fl::event_button()==3){
				
				if ((dragy-Fl::event_y())>0) m_ScaleFactor *=1.044;
				else if ((dragy-Fl::event_y())<0) m_ScaleFactor /=1.044;
				dragx=Fl::event_x();
				dragy=Fl::event_y();
				redraw();
				return 1;			
			}
			break;
			
		case FL_MOVE:
			if (m_Label) refreshLabel();
			break;

		
			
		default: break;
	}
	
	return Fl_Gl_Window::handle(event);
}

GLdouble Fl_gl2display::getXoffset()
{
	return m_TranslateX;
}
GLdouble Fl_gl2display::getYoffset()
{
	return m_TranslateY;
}

GLdouble Fl_gl2display::getScale()
{
	return m_ScaleFactor;
}

void Fl_gl2display::setXoffset(GLdouble val)
{
	m_TranslateX=val;
	redraw();
}

void Fl_gl2display::setYoffset(GLdouble val)
{
	m_TranslateY=val;
	redraw();
}

void Fl_gl2display::setScale(GLdouble val)
{
	m_ScaleFactor=val;
	redraw();
}

void Fl_gl2display::setLabel(Fl_Output* label)
{
	m_Label=label;
}