//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// glview2d.cc
//
// Vincent LE PRINCE <vincentleprince@users.sourceforge.net>
// Copyright (C) 2000-2005 Vincent LE PRINCE
// This file is part of the TRUEVISION Package

//   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */ 
//*******************************************************************************************
#include "include/glview2d.h"
#include "include/preferences.h"
#include "include/viewmanager.h"
#include "include/objectlist.h"
#include "include/objparam.h"
#include "include/tvio.h"
#include <math.h>
 
/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

// Définition
const char *titres[] = { N_("Top"), N_("Front"), N_("Right") };
const float labels_width = 2;
GlDisplayList glview2d::labels2D_lists[3];
TvWidget_float *glview2d::common_zoom = NULL;

const float sensitivity = 180;
const float zoom2d_max = 8;
const float zoom2d_min = 0.1;

//********************************************
// Constructeur
//********************************************
glview2d::glview2d( app_objs *appr,  ViewType mod, GtkWidget *box, GtkWidget *f1, GtkWidget *f2 ) : glview( appr, box, f1, f2 )
{
type = mod;
PREF_DEF
set_title( titres[(int)type] );

background = (TvWidget_color*)(pref->v2dbkg_color);
axis->set( pref->axis2D->value() );
grid = new TvWidget_bool( N_("Grid"), "GRID", NULL, app_ref,  pref->grid2D->value() );
tv_widgets.push_back( grid );
label->set( pref->labels2D->value() );
local_zoom = new TvWidget_float( N_("Zoom"), "LZOOM", NULL, app_ref, 1.1 );
local_zoom->set_range( zoom2d_max, zoom2d_min, 0.1 );
tv_widgets.push_back( local_zoom );
if ( common_zoom == NULL )
	{
	common_zoom = new TvWidget_float( N_("Common Zoom"), "CZOOM", NULL, app_ref, 1.1 );
	common_zoom->set_range( zoom2d_max, zoom2d_min, 0.1 );
	}
tv_widgets.push_back( common_zoom );	
zoom = ( pref->zoom2d->value() ) ? common_zoom : local_zoom;

set_rotate();
attach_popup();
}

glview2d::~glview2d()
{
delete local_zoom;
common_zoom->set( 1.1 );
delete grid;
}

//*****************************************************
// Set normal
//*****************************************************
void glview2d::set_rotate()
{
rotate.reset();
switch ( type )
	{
	case TV_VIEW_TOP:
		rotate.add( 1, 0, 0, -M_PI/2 );
		break;

	case TV_VIEW_RIGHT:
		rotate.add( 0, 1, 0, M_PI/2 );
		break;

	default:
		break;
	}
}


//*****************************************************
// Add menus
//*****************************************************
void glview2d::add_menu()
{
// Grid
menu_grid = gtk_check_menu_item_new_with_label( N_("Show grid") );
gtk_signal_connect( GTK_OBJECT(menu_grid), "activate", GTK_SIGNAL_FUNC(sign_toggle_grid), this );
gtk_check_menu_item_set_show_toggle( GTK_CHECK_MENU_ITEM(menu_grid), TRUE );
GTK_CHECK_MENU_ITEM(menu_grid)->active =  grid->value();
gtk_menu_append( GTK_MENU(popup), menu_grid );

// labels
menu_labels = gtk_check_menu_item_new_with_label( N_("Show labels") );
gtk_signal_connect( GTK_OBJECT(menu_labels), "activate", GTK_SIGNAL_FUNC(sign_toggle_labels), this );
gtk_check_menu_item_set_show_toggle( GTK_CHECK_MENU_ITEM(menu_labels), TRUE );
GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
gtk_menu_append( GTK_MENU(popup), menu_labels );
}


//*****************************************************
// Pref Changed !
//*****************************************************
void glview2d::pref_changed()
{
glview::pref_changed();
for ( int i = 0 ; i < 3 ; i++ )
	labels2D_lists[i].invalidate();
refresh();
}

//********************************************
// Refresh
//********************************************
void glview2d::refresh()
{
//cout << "\nEnter 2D refresh !"; cout.flush();
if ( !visible ) return;
glview::refresh();

glMatrixMode( GL_PROJECTION );
glLoadIdentity();
float aratio = (float)area->allocation.width / (float)area->allocation.height;
float zoomv = zoom->value();
glOrtho( -zoomv * aratio, zoomv * aratio, -zoomv, zoomv, -50, 50 );

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();

rotate.gl_rotate();
glTranslatef( -lookat->get(0), -lookat->get(1), -lookat->get(2) );

OBJLIST_DEF
objlist->display( this );
objlist->display_current_param( this );

if ( axis->value() ) draw_axis();
if ( grid->value()  ) draw_grid( (int)type, zoomv );
if ( label->value()  ) draw_labels();

glFlush();
gdk_gl_drawable_swap_buffers( get_drawable() );
//cout << "\nExit 2d refresh !"; cout.flush();
gdk_window_process_all_updates ();
}

//********************************************
// Mouse moved
//********************************************
void glview2d::mouse_moved( GdkEventMotion *ev )
{
GdkRectangle rect;
GdkModifierType state;
VMAN_DEF
PREF_DEF
OBJLIST_DEF
if ( drag.view != this ) return;

if ( ev->is_hint ) gdk_window_get_pointer( ev->window, &drag.current_x, &drag.current_y, &state );
else {
	drag.current_x = (int)ev->x;
	drag.current_y = (int)ev->y;
	state = (GdkModifierType)ev->state;
	}
rect.x = 0;
rect.y = 0;
rect.width = area->allocation.width;
rect.height = area->allocation.height;

switch ( vmanager->get_pointer_mode() )
	{		
	case TV_PMODE_TRACKBALL:
		if ( state & GDK_BUTTON1_MASK )
			{
			if ( type == TV_VIEW_RIGHT ) 
				lookat->setz( lookat->get(2) + (drag.current_x - drag.previous_x) / sensitivity );
			else 
				lookat->setx( lookat->get(0) - (drag.current_x - drag.previous_x) / sensitivity );
			if ( type == TV_VIEW_TOP ) 
				lookat->setz( lookat->get(2) - (drag.current_y - drag.previous_y) / sensitivity );
			else 
				lookat->sety( lookat->get(1) + (drag.current_y - drag.previous_y) / sensitivity );
						
			if ( pref->nav2D->value() ) vmanager->refresh();
				else gtk_widget_draw( area, &rect );
			}	
		if ( state & GDK_BUTTON2_MASK )
			{
			zoom->set( zoom->value() + ( (float)(drag.current_y - drag.previous_y) / rect.height ) );
//			if ( zoom < zoom2d_min ) zoom = zoom2d_min;
//			if ( zoom > zoom2d_max ) zoom = zoom2d_max;
			if ( zoom == common_zoom ) vmanager->refresh();
			else gtk_widget_draw( area, &rect );
			}
		drag.previous_x = drag.current_x;
		drag.previous_y = drag.current_y;
		break;

	default:
		drag.gdkview = &rect;
		drag.shift = ( state & GDK_SHIFT_MASK ) ? true : false;
		drag.control = ( state & GDK_CONTROL_MASK ) ? true : false;
		if ( state & GDK_BUTTON1_MASK )	objlist->mouse_interact_drag( &drag );
		drag.previous_x = drag.current_x;
		drag.previous_y = drag.current_y;		
		break;
	}
}

void glview2d::mouse_scrolled( GdkEventScroll *ev )
{
	VMAN_DEF
	float shift = 0;
	if (  ev->direction == GDK_SCROLL_UP )
		shift = 0.1;
	else
		shift = -0.1;
	
	if ( ev->state & GDK_SHIFT_MASK ) { // up and down
			if ( type == TV_VIEW_RIGHT || type == TV_VIEW_FRONT ) 
				lookat->sety( lookat->get(1) + (shift * zoom->value() ) );
			else 
				lookat->setz( lookat->get(2) + (shift * zoom->value() ) );
	} else if ( ev->state & GDK_CONTROL_MASK ) { // left and right
			if ( type == TV_VIEW_TOP || type == TV_VIEW_FRONT ) 
				lookat->setx( lookat->get(0) + (shift * zoom->value() ) );
			else 
				lookat->setz( lookat->get(2) + (shift * zoom->value() ) );
	} else
		zoom->set( zoom->value() + shift );
	
//force_refresh();
vmanager->refresh();
}

void glview2d::set_viewport()
{
//cout << "\nSetting viewport;.."; cout.flush();
float aratio = (float)area->allocation.width / (float)area->allocation.height;
float zoomv = zoom->value();
glOrtho( -zoomv * aratio, zoomv * aratio, -zoomv, zoomv, -50, 50 );

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
rotate.gl_rotate();
glTranslatef( -lookat->get(0), -lookat->get(1), -lookat->get(2) );
//cout << " Done."; cout.flush();
}




//*********************************************************
// Reset view
//*********************************************************
void glview2d::reset_view( int ntype )
{
type = (ViewType)ntype;
set_title( titres[(int)type] );
set_rotate();
refresh();
}

//*********************************************************
// Draw labels
//*********************************************************
void glview2d::draw_labels()
{
PREF_DEF
if ( ! labels2D_lists[(int)type].exec() ) 
	{
	const float pos = 1.02;
	const float offset = 0.03;
	
	labels2D_lists[type].begin();
	glLineWidth( labels_width );
	pref->labels_color->gl_set_rgb();
	glBegin( GL_LINES );
	switch( type )
		{
		default:
		case TV_VIEW_RIGHT:
			glVertex3f( 0, pos, offset*1/3 );     // Y
			glVertex3f( 0, pos+offset, -offset*2/3 );
			glVertex3f( 0, pos+offset/2, 0 );
			glVertex3f( 0, pos+offset, offset*2/3 );
			glVertex3f( 0, offset, pos+offset );  // Z
			glVertex3f( 0, offset, pos );
			glVertex3f( 0, offset, pos );			
			glVertex3f( 0, -offset, pos+offset );
			glVertex3f( 0, -offset, pos+offset );			
			glVertex3f( 0, -offset, pos );		
			
			break;

		case TV_VIEW_TOP:
			glVertex3f( pos, 0, offset );     // X
			glVertex3f( pos+offset, 0, -offset );
			glVertex3f( pos, 0, -offset );
			glVertex3f( pos+offset, 0, offset );
			glVertex3f( -offset, 0, pos );     // Z
			glVertex3f( offset, 0, pos );
			glVertex3f( offset, 0, pos );			
			glVertex3f( -offset, 0, pos+offset );
			glVertex3f( -offset, 0, pos+offset );			
			glVertex3f( offset, 0, pos+offset );		
			break;

		case TV_VIEW_FRONT:
			glVertex3f( pos, offset, 0 );     // X
			glVertex3f( pos+offset, -offset, 0 );
			glVertex3f( pos, -offset, 0 );
			glVertex3f( pos+offset, offset, 0 );
			glVertex3f( -offset*1/3, pos, 0 );     // Y
			glVertex3f( offset*2/3, pos+offset, 0 );
			glVertex3f( 0, pos+offset/2, 0 );
			glVertex3f( -offset*2/3, pos+offset, 0 );
			break;
		}

	glEnd();
	glLineWidth( LINE_WIDTH );
	labels2D_lists[type].end();
	} 
}


void glview2d::save( ofstream & file )
{
file << "\nGLVIEW2D{\n";
save_basics( file );
grid->save( file );
local_zoom->save( file );
common_zoom->save( file );
file << "\n}";
}

bool glview2d::load( ifstream & file, char *ltag )
{
if ( strcmp( ltag, "GLVIEW2D" ) ) return false;
TvWidget_int wtype( N_("Type"), "TYPE", NULL, app_ref, type );
TvWidget_bool new_maximized( N_("Maximized"), "MAXI", NULL, app_ref, false );
TvWidget_bool new_rolled_up( N_("Rolled"), "ROLLU", NULL, app_ref, false );

char * tag = NULL;
do {
	tag = tvio_get_next_tag( file );
	if ( tag == NULL ) break;
    if ( load_basics( file, tag ) ) continue;
	if ( new_maximized.load( file, tag ) ) continue;
	if ( new_rolled_up.load( file, tag ) ) continue;
	if ( wtype.load( file, tag ) ) continue;
	
	if ( grid->load( file, tag ) ) continue;
	if ( local_zoom->load( file, tag ) ) continue;
	if ( common_zoom->load( file, tag ) ) continue;
	tvio_skip_section(file );
	} while ( tag != NULL );

if ( type != wtype.value() ) { change_type( (ViewType)(wtype.value()) ); return true; }	
if ( maximized->value() != new_maximized.value() )  MaximizeRestore();
if ( rolled_up->value() != new_rolled_up.value() ) RollUp();

GTK_CHECK_MENU_ITEM(menu_gl_solid)->active =  gl_solid->value();
GTK_CHECK_MENU_ITEM(menu_gl_lighting)->active =  gl_lighting->value();
GTK_CHECK_MENU_ITEM(menu_gl_smooth)->active =  gl_smooth->value();
GTK_CHECK_MENU_ITEM(menu_axis)->active =  axis->value();
GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
GTK_CHECK_MENU_ITEM(menu_grid)->active =  grid->value();
if ( !gdk_gl_drawable_gl_begin( get_drawable(), get_context() ) ) return true;
OBJLIST_DEF
objlist->light_disable_all();
gdk_gl_drawable_gl_end( get_drawable() );
return true;
}


void glview2d::clear( ViewType cl_type )
{
if ( cl_type != type )   { change_type( cl_type ); return; }
glview::clear( cl_type );
local_zoom->set( 1.1 );
common_zoom->set( 1.1 );
grid->set( true );
}

void glview2d::reset_home()
{
local_zoom->set( 1.1 );
common_zoom->set( 1.1 );
local_lookat->set( 0, 0, 0 );
common_lookat->set( 0, 0, 0 );
VMAN_DEF
vmanager->refresh();
}



void glview2d::key_press( GdkEventKey *ev )
{
if ( ev->length == 0 ) return;
if ( handle_base_key_press( ev ) ) return;

switch ( ev->keyval )
	{
	case GDK_B:	
	case GDK_b:
		label->toggle();
		GTK_CHECK_MENU_ITEM(menu_labels)->active =  label->value();
		refresh();
		break;
	
	case GDK_G:	
	case GDK_g:
		grid->toggle();
		GTK_CHECK_MENU_ITEM(menu_grid)->active =  grid->value();
		refresh();
		break;
	
	default:
		break;
	}
}
