/*
 * Copyright (C) 2003-2004, 2012 Clemens Fuchslocher
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include <GL/gl.h>
#include <GL/glu.h>
#ifdef __MINGW32__
#include <GL/glext.h>
#endif

#include <cstdlib>
#include <sstream>

#include <sys/stat.h>

#include "config.h"

#include "GraphNode.h"
#include "Transform.h"
#include "VertexArray.h"

#include "Utility.h"
#include "Trackball.h"
#include "VRMLReader.h"
#include "InfoDialog.h"
#include "IncompleteVRMLViewer.h"
#include "GlCompileFlatVisitor.h"
#include "GlCompileSmoothVisitor.h"
#include "GlUncompileVisitor.h"


using nsReader::VRMLReader;
using nsSceneGraph::SceneGraph;
using nsSceneGraph::GraphNode;
using nsSceneGraph::NodeType;
using nsSceneGraph::TRANSFORM;
using nsSceneGraph::VERTEXARRAY;

IncompleteVRMLViewer::IncompleteVRMLViewer(int argc, char *argv[]) : ToolkitGtk(argc, argv)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::IncompleteVRMLViewer()")

	this->numberOfVertices = 0;
	this->numberOfTriangles = 0;

	this->useFrictionlessTrackball = true;
	this->trackball.setZoom(7.0);
	this->trackball.setZoomMin(1.0);
	this->trackball.setZoomMax(30.0);
	this->trackball.setFrictionless(true);
	this->trackball.setTranslateRectangle(-5, -5, 5, 5);

	this->showFramesPerSecond = false;

	this->font = NULL;
	this->fontHeight = 0;
	this->fontWidth = 0;
	this->fontListBase = 0;

	this->openDialog = NULL;

	this->setSceneGraph(new SceneGraph());

	this->defaultMaterial = 0;
}


IncompleteVRMLViewer::~IncompleteVRMLViewer(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::~IncompleteVRMLViewer()")

	if (this->defaultMaterial != 0)
	{
		glDeleteLists(this->defaultMaterial, 1);
	}

	if (this->fontListBase != 0)
	{
		glDeleteLists(this->fontListBase, 128);
	}
}


unsigned int IncompleteVRMLViewer::getNumberOfVertices(nsSceneGraph::GraphNode *node)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::getNumberOfVertices()")

	if (node == NULL)
	{
		node = this->getSceneGraph()->getScene();
	}

	unsigned int vertices = 0;

	std::list<GraphNode *>::iterator iter = node->getChildren().begin();
	std::list<GraphNode *>::iterator end = node->getChildren().end();
	for (; iter != end; iter++)
	{
		vertices += this->getNumberOfVertices(*iter);
	}

	if (node->getType() != VERTEXARRAY)
	{
		return vertices;
	}

	return vertices + ((nsSceneGraph::VertexArray *) node)->getVertexArray().size();
}


unsigned int IncompleteVRMLViewer::getNumberOfTriangles(nsSceneGraph::GraphNode *node)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::getNumberOfTriangles()")

	if (node == NULL)
	{
		node = this->getSceneGraph()->getScene();
	}

	unsigned int triangles = 0;

	std::list<GraphNode *>::iterator iter = node->getChildren().begin();
	std::list<GraphNode *>::iterator end = node->getChildren().end();
	for (; iter != end; iter++)
	{
		triangles += this->getNumberOfTriangles(*iter);
	}

	if (node->getType() != VERTEXARRAY)
	{
		return triangles;
	}

	return triangles + ((nsSceneGraph::VertexArray *) node)->getVertexIndex().size();
}


void IncompleteVRMLViewer::sceneGraphBuild(GraphNode *node, GtkTreeIter *parent)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::sceneGraphBuild()")

	std::string name = node->getTypeName();
	if (*node->getNodeName())
	{
		name += " (";
		name += node->getNodeName();
		name += ")";
	}

	GtkTreeIter child;
	gtk_tree_store_append(this->sceneGraphModel, &child, parent);
	gtk_tree_store_set(this->sceneGraphModel, &child,
		VISIBLE_COLUMN, "", NAME_COLUMN, name.c_str(), NODE_POINTER, node, -1);

	std::list<GraphNode *>::iterator iter = node->getChildren().begin();
	std::list<GraphNode *>::iterator end = node->getChildren().end();
	for (; iter != end; iter++)
	{
		this->sceneGraphBuild(*iter, &child);
	}
}


gboolean IncompleteVRMLViewer::sceneGraphGetSelectedIter(GtkTreeIter *iter)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::sceneGraphGetSelectedIter()")

	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(this->sceneGraphView));
	if (gtk_tree_selection_get_selected(selection, NULL, iter))
	{
		return true;
	}

	return false;
}


GraphNode *IncompleteVRMLViewer::sceneGraphGetSelectedNode(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::sceneGraphGetSelectedNode()")

	GtkTreeIter iter;
	if (this->sceneGraphGetSelectedIter(&iter))
	{
		GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(this->sceneGraphView));
		if (model == NULL)
		{
			std::cerr << __FILE__ << '[' << __LINE__ << "]: IncompleteVRMLViewer::popupHide()" << std::endl;
			return NULL;
		}

		GraphNode *node;
		gtk_tree_model_get(model, &iter, NODE_POINTER, &node, -1);

		return node;
	}

	return NULL;
}


void IncompleteVRMLViewer::performInit(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::performInit()")

	static GtkItemFactoryEntry menuItems[] = {
		{ "/_File", NULL, NULL, 0, "<Branch>" },
		{ "/_File/Tearoff", NULL, NULL, 0, "<Tearoff>" },
		{ "/_File/_Open...", "<CTRL>O", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuFileOpen, 1, "<StockItem>", GTK_STOCK_OPEN },
		{ "/_File/Separator", NULL, NULL, 0, "<Separator>" },
		{ "/_File/_Quit", "<CTRL>Q", gtk_main_quit, 0, "<StockItem>", GTK_STOCK_QUIT },
		{ "/_View", NULL, NULL, 0, "<Branch>" },
		{ "/_View/Tearoff", NULL, NULL, 0, "<Tearoff>" },
		{ "/_View/_Reset", "<CTRL>R", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewReset, 1, "<StockItem>", GTK_STOCK_CANCEL },
		{ "/_View/Separator", NULL, NULL, 0, "<Separator>" },
		{ "/_View/_Wireframe", "<CTRL>W", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewSetWireframe, 1, "<CheckItem>" },
		{ "/_View/Friction_less", "<CTRL>L", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewSetFrictionless, 1, "<CheckItem>" },
		{ "/_View/_Backface Culling", "<CTRL>B", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewSetBackfaceCulling, 1, "<CheckItem>" },
		{ "/_View/_Frames Per Second", "<CTRL>F", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewFramesPerSecond, 1, "<CheckItem>" },
		{ "/_View/Separator", NULL, NULL, 0, "<Separator>" },
		{ "/_View/Shading _Model/Tearoff", NULL, NULL, 0, "<Tearoff>" },
		{ "/_View/Shading _Model/_Flat", "<CTRL>1", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewShadingModel, SHADING_MODEL_FLAT, "<RadioItem>" },
		{ "/_View/Shading _Model/_Smooth", "<CTRL>2", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewShadingModel, SHADING_MODEL_SMOOTH, "/View/Shading Model/Flat" },
		{ "/_View/Separator", NULL, NULL, 0, "<Separator>" },
		{ "/_View/_Model Info...", "<CTRL>M", (GtkItemFactoryCallback) IncompleteVRMLViewer::menuViewModelInfo, 1, "<StockItem>", GTK_STOCK_DIALOG_INFO },
		{ "/_Help", NULL, NULL, 0, "<LastBranch>" },
		{ "/_Help/Tearoff", NULL, NULL, 0, "<Tearoff>" },
		{ "/_Help/_About...", NULL, (GtkItemFactoryCallback) IncompleteVRMLViewer::menuHelpAbout, 1, "<StockItem>", GTK_STOCK_DIALOG_INFO },
		{ "/_Help/_OpenGL Info...", NULL, (GtkItemFactoryCallback) IncompleteVRMLViewer::menuHelpOpenGLInfo, 1, "<StockItem>", GTK_STOCK_DIALOG_INFO }
	};

	static GtkItemFactoryEntry popupItems[] = {
		{ "/Tearoff", NULL, NULL, 0, "<Tearoff>" },
		{ "/_Hide Node", NULL, (GtkItemFactoryCallback) IncompleteVRMLViewer::popupHide, 1, "<Item>" },
		{ "/_Unhide Node", NULL, (GtkItemFactoryCallback) IncompleteVRMLViewer::popupUnhide, 1, "<Item>" }
	};

	// Window
	gtk_window_set_title(GTK_WINDOW(this->getWindow()), "iVRML Viewer");
	gtk_widget_set_size_request(GTK_WIDGET(this->getWindow()), 600, 500);

	// Menubar
	GtkAccelGroup *menuAccel = gtk_accel_group_new();
	this->menuItemFactory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", menuAccel);
	gtk_item_factory_create_items(this->menuItemFactory, sizeof(menuItems) / sizeof(menuItems[0]), menuItems, this);
	gtk_window_add_accel_group(GTK_WINDOW(this->getWindow()), menuAccel);
	GtkWidget *menubar = gtk_item_factory_get_widget(this->menuItemFactory, "<main>");
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Shading Model/Smooth")), TRUE);

	//
	GtkWidget *paned = gtk_hpaned_new();

	// Tree
	this->sceneGraphModel = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
	this->sceneGraphView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(this->sceneGraphModel));
	this->sceneGraphTextRenderer = gtk_cell_renderer_text_new();
	this->sceneGraphPixbufRenderer = gtk_cell_renderer_pixbuf_new();
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(this->sceneGraphView), true);
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(this->sceneGraphView)), GTK_SELECTION_SINGLE);

	GtkTreeViewColumn *column;
	column = gtk_tree_view_column_new_with_attributes("  ", this->sceneGraphPixbufRenderer, "stock_id", VISIBLE_COLUMN, NULL);
	gtk_tree_view_append_column(GTK_TREE_VIEW(this->sceneGraphView), column);

	column = gtk_tree_view_column_new_with_attributes(" Scenegraph", this->sceneGraphTextRenderer, "text", NAME_COLUMN, NULL);
	gtk_tree_view_append_column(GTK_TREE_VIEW(this->sceneGraphView), column);
	gtk_tree_view_set_expander_column(GTK_TREE_VIEW(this->sceneGraphView), column);

	// Popup
	GtkAccelGroup *popupAccel = gtk_accel_group_new();
	this->popupItemFactory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", menuAccel);
	gtk_item_factory_create_items(this->popupItemFactory, sizeof(popupItems) / sizeof(popupItems[0]), popupItems, this);
	gtk_window_add_accel_group(GTK_WINDOW(this->getWindow()), popupAccel);
	this->popup = gtk_item_factory_get_widget(this->popupItemFactory, "<main>");

	g_signal_connect_swapped(GTK_OBJECT(this->sceneGraphView), "button_press_event", G_CALLBACK(popupHandler), this);

	// Scrollbar
	GtkWidget *scrollbar = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollbar), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
	gtk_container_add(GTK_CONTAINER(scrollbar), this->sceneGraphView);

	// Pack
	GtkWidget *box = gtk_vbox_new(false, 0);
	gtk_box_pack_start(GTK_BOX(box), menubar, false, true, 0);
	gtk_box_pack_start(GTK_BOX(box), paned, true, true, 1);
	gtk_paned_add1(GTK_PANED(paned), scrollbar);
	gtk_paned_add2(GTK_PANED(paned), this->getDrawingArea());
	gtk_container_add(GTK_CONTAINER(this->getWindow()), box);
}


void IncompleteVRMLViewer::eventRealize(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventRealize()")

	glClearColor(0.0, 0.0, 0.0, 0.0);

	static GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
	static GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	static GLfloat light_position[] = { 0.0, 0.0, 10.0, 0.0 };

	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);

	static GLfloat ambient[] = { 0.1, 0.1, 0.1, 1.0 };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

	glShadeModel(GL_SMOOTH);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);

	/*
 	 * The OpenGL32.dll is only providing OpenGL 1.1. And
 	 * GL_RESCALE_NORMAL is an OpenGL 1.2 capability. Using
 	 * it here would result in an "invalid enumerant" error.
 	 * So we must use the slower GL_NORMALIZE instead.
 	 */
	if (g_ascii_strtod((char *) glGetString(GL_VERSION), NULL) >= 1.2)
	{
		glEnable(GL_RESCALE_NORMAL);
	}
	else
	{
		glEnable(GL_NORMALIZE);
	}

	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);

	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);

	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Wireframe")), false);
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Frictionless")), this->trackball.getFrictionless());
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Backface Culling")), true);
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Frames Per Second")), false);

	this->defaultMaterial = glGenLists(1);
	glNewList(this->defaultMaterial, GL_COMPILE);
		GLfloat glDiffuse[] = { 0.8, 0.8, 0.8, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, glDiffuse);

		GLfloat glEmission[] = { 0.0, 0.0, 0.0, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, glEmission);

		GLfloat glSpecular[] = { 0.0, 0.0, 0.0, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, glSpecular);

		GLfloat glAmbient[] = { 0.2, 0.2, 0.2, 1.0 };
		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, glAmbient);

		GLfloat glShininess = 0.0;
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, glShininess);
	glEndList();

	// Font
	PangoFontDescription *fontDesc = pango_font_description_from_string("Courier 16");

	this->fontListBase = glGenLists(128);
	this->font = gdk_gl_font_use_pango_font(fontDesc, 0, 128, this->fontListBase);
	if (this->font != NULL)
	{
		PangoFontMetrics *fontMetrics = pango_font_get_metrics(this->font, NULL);
		this->fontHeight = PANGO_PIXELS(pango_font_metrics_get_ascent(fontMetrics) + pango_font_metrics_get_descent(fontMetrics));
		this->fontWidth = PANGO_PIXELS(pango_font_metrics_get_approximate_char_width(fontMetrics));
		pango_font_metrics_unref(fontMetrics);
	}

	pango_font_description_free(fontDesc);
}


void IncompleteVRMLViewer::eventUnrealize(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventUnrealize()")
}


gboolean IncompleteVRMLViewer::eventConfigure(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventConfigure()")

	GLsizei width = this->getDrawingArea()->allocation.width;
	GLsizei height = this->getDrawingArea()->allocation.height;

	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, 1.0, 1.0, 40.0);
	glMatrixMode(GL_MODELVIEW);

	return true;
}


gboolean IncompleteVRMLViewer::eventMap(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventMap()")

	this->setFrictionless(true);

	return true;
}


gboolean IncompleteVRMLViewer::eventUnmap(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventUnmap()")

	this->setFrictionless(false);

	return true;
}


gboolean IncompleteVRMLViewer::eventVisibilityNotify(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventVisibilityNotify()")

	switch (event->visibility.state)
	{
		case GDK_VISIBILITY_UNOBSCURED:
		case GDK_VISIBILITY_PARTIAL:
		{
			this->framesPerSecond.reset();
			this->showFramesPerSecond = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Frames Per Second")));
			this->setFrictionless(true);
			break;
		}

		case GDK_VISIBILITY_FULLY_OBSCURED:
		{
			this->showFramesPerSecond = false;
			this->setFrictionless(false);
			break;
		}
	}

	return true;
}


gboolean IncompleteVRMLViewer::eventExpose(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventExpose()")

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (this->defaultMaterial != 0)
	{
		glCallList(this->defaultMaterial);
	}

	if (this->getSceneGraph()->getScene())
	{
		glLoadIdentity();
		gluLookAt(0.0, 0.0, this->trackball.getZoom(), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		glTranslatef(this->trackball.getTranslateX(), this->trackball.getTranslateY(), 0);

		this->trackball.eventExpose();
		this->performSceneGraphAction(&this->actionVisitor);

		// Frames per second
		if (this->showFramesPerSecond == true)
		{
			this->framesPerSecond++;

			if (this->font != NULL)
			{
				GLsizei width = this->getDrawingArea()->allocation.width;
				GLsizei height = this->getDrawingArea()->allocation.height;

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glOrtho(0, width, 0, height, -1.0, 1.0);

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

				GLfloat white[] = { 1, 1, 1, 1 };
				glMaterialfv(GL_FRONT, GL_AMBIENT, white);
				glMaterialfv(GL_FRONT, GL_DIFFUSE, white);
				glMaterialfv(GL_FRONT, GL_SPECULAR, white);
				glMaterialfv(GL_FRONT, GL_EMISSION, white);

				char *str = g_strdup_printf("%5.1f fps", this->framesPerSecond.getFramesPerSecond());
				glRasterPos3f((width / 2.0) -(this->fontWidth * strlen(str) / 2), this->fontHeight / 2.0, 1.0);
				for (char *s = str; *s != '\0'; s++)
				{
					glCallList(this->fontListBase + *s);
				}

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				gluPerspective(60.0, 1.0, 1.0, 40.0);
				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

				free(str);
			}
		}
	}

	this->swapBuffers();

	return true;
}


gboolean IncompleteVRMLViewer::eventKeyPress(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventKeyPress()")

	return false;
}


gboolean IncompleteVRMLViewer::eventMotionNotify(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventMotionNotify()")

	if (event->button.state & GDK_BUTTON1_MASK)
	{
		this->trackball.eventRotationUpdate(
			(int) event->button.x,
			(int) event->button.y,
			this->getDrawingArea()->allocation.width,
			this->getDrawingArea()->allocation.height
		);
	}
	else if (event->button.state & GDK_BUTTON2_MASK)
	{
		this->trackball.eventZoomUpdate(
			(int) event->button.y,
			this->getDrawingArea()->allocation.height
		);
	}
	else if (event->button.state & GDK_BUTTON3_MASK)
	{
		this->trackball.eventTranslateUpdate(
			(int) event->button.x,
			(int) event->button.y,
			this->getDrawingArea()->allocation.width,
			this->getDrawingArea()->allocation.height
		);
	}

	if (this->useFrictionlessTrackball == false)
	{
		this->redrawScene();
	}

	return false;
}


gboolean IncompleteVRMLViewer::eventButtonPress(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventButtonPress()")

	if (event->button.button == 1)
	{
		this->trackball.eventRotationBegin(
			(int) event->button.x,
			(int) event->button.y,
			this->getDrawingArea()->allocation.width,
			this->getDrawingArea()->allocation.height
		);
		return true;
	}
	else if (event->button.button == 2)
	{
		this->trackball.eventZoomBegin(
			(int) event->button.y
		);
		return true;
	}
	else if (event->button.button == 3)
	{
		this->trackball.eventTranslateBegin(
			(int) event->button.x,
			(int) event->button.y
		);
		return true;
	}

	return false;
}


gboolean IncompleteVRMLViewer::eventButtonRelease(GdkEvent *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventButtonRelease()")

	if (event->button.button == 3)
	{
		this->trackball.eventTranslateEnd();
		return true;
	}

	return false;
}


void IncompleteVRMLViewer::eventIdle(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::eventIdle()")

	this->redrawScene();
}


void IncompleteVRMLViewer::menuFileOpen(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuFileOpen()")

	thiz->openDialog = gtk_file_selection_new("iVRML Viewer - Open File");
	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(thiz->openDialog)->ok_button),
		"clicked", G_CALLBACK(IncompleteVRMLViewer::menuFileOpenOk), (gpointer) thiz);
	g_signal_connect_swapped(G_OBJECT(GTK_FILE_SELECTION(thiz->openDialog)->cancel_button),
		"clicked", G_CALLBACK(gtk_widget_destroy), (gpointer) thiz->openDialog);
	gtk_widget_show_all(thiz->openDialog);
}


void IncompleteVRMLViewer::menuFileOpenOk(GtkWidget *widget, IncompleteVRMLViewer *thiz)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuFileOpenOk()")

	char *filename =(char *) gtk_file_selection_get_filename(GTK_FILE_SELECTION(thiz->openDialog));
	if (*filename == '\0')
	{
		return;
	}

	struct stat st;
	if (stat(filename, &st) == -1)
	{
		return;
	}

	if (!S_ISREG(st.st_mode))
	{
		return;
	}

	gtk_widget_destroy(thiz->openDialog);
	gtk_main_iteration_do(true);

	VRMLReader reader;

	thiz->loading.start();
	GraphNode *scene = reader.readFile(filename);
	if (scene == NULL)
	{
		GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(thiz->getWindow()),
			GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, " ");

		gtk_window_set_title(GTK_WINDOW(dialog), "iVRML Viewer - Error");
		gtk_label_set_justify(GTK_LABEL(GTK_MESSAGE_DIALOG(dialog)->label), GTK_JUSTIFY_FILL);
		gtk_label_set_line_wrap(GTK_LABEL(GTK_MESSAGE_DIALOG(dialog)->label), true);
		gtk_label_set_markup(GTK_LABEL(GTK_MESSAGE_DIALOG(dialog)->label),
			"<span font_family='Sans' size='larger' weight='bold'>An error occured while parsing the VRML scene.</span>\n\n"
			"<span font_family='Sans'>Please make sure that only allowed VRML nodes are used. "
			"At the moment only the following VRML nodes are supported: The Group node, the "
			"IndexedFaceSet node, the Material node and the Tranform node. Be sure that the IndexedFaceSet "
			"is triangulated and that no textures are used.</span>");
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
		return;
	}
	thiz->loading.stop();

	gtk_tree_store_clear(thiz->sceneGraphModel);
	thiz->sceneGraphBuild(scene);
	gtk_tree_view_expand_all(GTK_TREE_VIEW(thiz->sceneGraphView));

	if (thiz->getSceneGraph()->getScene())
	{
		delete thiz->getSceneGraph()->getScene();
		thiz->getSceneGraph()->setScene(NULL);
	}

	nsSceneGraph::GlCompileFlatVisitor flat;
	nsSceneGraph::GlCompileSmoothVisitor smooth;

	thiz->getSceneGraph()->setScene(scene);
	thiz->normalCalculation.start();
	thiz->beginGLExecution();
	if (thiz->isShadingModelFlat())
	{
		thiz->performSceneGraphAction(&flat);
	}
	else if (thiz->isShadingModelSmooth())
	{
		thiz->performSceneGraphAction(&smooth);
	}
	thiz->endGLExececution();
	thiz->normalCalculation.stop();
	thiz->setFrictionless(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(thiz->menuItemFactory, "/View/Frictionless"))));

	thiz->numberOfVertices = thiz->getNumberOfVertices();
	thiz->numberOfTriangles = thiz->getNumberOfTriangles();
}


void IncompleteVRMLViewer::menuViewReset(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewReset()")

	thiz->trackball.reset();
	thiz->trackball.setZoom(7.0);
	IncompleteVRMLViewer::menuViewSetFrictionless(thiz, 1, widget);
	IncompleteVRMLViewer::menuViewSetWireframe(thiz, 1, widget);
	IncompleteVRMLViewer::menuViewSetBackfaceCulling(thiz, 1, widget);
	IncompleteVRMLViewer::menuViewFramesPerSecond(thiz, 1, widget);

	if (thiz->trackball.getFrictionless() == false)
	{
		thiz->redrawScene();
	}
}


void IncompleteVRMLViewer::menuViewSetWireframe(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewSetWireframe()")

	if (thiz->beginGLExecution() == false)
	{
		return;
	}

	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(thiz->menuItemFactory, "/View/Wireframe"))))
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	else
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	if (thiz->trackball.getFrictionless() == false)
	{
		thiz->redrawScene();
	}

	thiz->endGLExececution();
}


void IncompleteVRMLViewer::menuViewSetFrictionless(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewSetFrictionless()")

	thiz->useFrictionlessTrackball = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(thiz->menuItemFactory, "/View/Frictionless")));
	thiz->setFrictionless(thiz->useFrictionlessTrackball);
	thiz->trackball.setFrictionless(thiz->useFrictionlessTrackball);
}


void IncompleteVRMLViewer::menuViewSetBackfaceCulling(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewSetBackfaceCulling()")

	if (thiz->beginGLExecution() == false)
	{
		return;
	}

	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(thiz->menuItemFactory, "/View/Backface Culling"))))
	{
		glEnable(GL_CULL_FACE);
	}
	else
	{
		glDisable(GL_CULL_FACE);
	}

	if (thiz->trackball.getFrictionless() == false)
	{
		thiz->redrawScene();
	}

	thiz->endGLExececution();
}


void IncompleteVRMLViewer::menuViewFramesPerSecond(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewFramesPerSecond()")

	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(thiz->menuItemFactory, "/View/Frames Per Second"))))
	{
		thiz->showFramesPerSecond = true;
		thiz->setFrictionless(true);
	}
	else
	{
		thiz->showFramesPerSecond = false;
		thiz->setFrictionless(thiz->useFrictionlessTrackball);
	}

	thiz->framesPerSecond.reset();
	thiz->redrawScene();
}


void IncompleteVRMLViewer::menuViewShadingModel(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewShadingModel()")

	if (!GTK_WIDGET_REALIZED(thiz->getDrawingArea()))
	{
		return;
	}

	if (thiz->beginGLExecution() == false)
	{
		return;
	}

	nsSceneGraph::GlCompileFlatVisitor flat;
	nsSceneGraph::GlCompileSmoothVisitor smooth;
	nsSceneGraph::GlUncompileVisitor uncompile;

	thiz->normalCalculation.start();
	thiz->performSceneGraphAction(&uncompile);
	if (thiz->isShadingModelFlat())
	{
		if (action == SHADING_MODEL_FLAT)
		{
			glShadeModel(GL_FLAT);
			thiz->performSceneGraphAction(&flat);
		}
	}
	else if (thiz->isShadingModelSmooth())
	{
		if (action == SHADING_MODEL_SMOOTH)
		{
			glShadeModel(GL_SMOOTH);
			thiz->performSceneGraphAction(&smooth);
		}
	}
	thiz->normalCalculation.stop();
	thiz->endGLExececution();
	thiz->redrawScene();
}


void IncompleteVRMLViewer::menuViewModelInfo(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuViewModelInfo()")

	InfoDialog dialog("iVRML Viewer - Model Info");

	dialog.addHeader("Number of Vertices:\n");
	std::ostringstream text;
	text << thiz->numberOfVertices << "\n\n";
	dialog.addText(text.str().c_str());

	dialog.addHeader("Number of Triangles:\n");
	text.str("");
	text << thiz->numberOfTriangles << "\n\n";
	dialog.addText(text.str().c_str());

	dialog.addHeader("Time for Loading Scene:\n");
	text.str("");
	text << thiz->loading.getElapsedMilliseconds() << "ms (";
	text << thiz->loading.getElapsedMilliseconds() / 1000.0 << "s)\n\n";
	dialog.addText(text.str().c_str());

	dialog.addHeader("Time for Calculating Normals:\n");
	text.str("");
	text << thiz->normalCalculation.getElapsedMilliseconds() << "ms (";
	text << thiz->normalCalculation.getElapsedMilliseconds() / 1000.0 << "s)\n\n";
	dialog.addText(text.str().c_str());

	dialog.performAction();
}


void IncompleteVRMLViewer::menuHelpAbout(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuHelpAbout()")

	InfoDialog dialog("iVRML Viewer - About");
	dialog.addHeader("Name:\n");
	dialog.addText("iVRML Viewer\n\n");
	dialog.addHeader("Version:\n");
	dialog.addText(IVV_VERSION "\n\n");
	dialog.addHeader("Author:\n");
	dialog.addText("Clemens Fuchslocher <clemens@vakuumverpackt.de>\n\n");
	dialog.addHeader("Website:\n");
	dialog.addText("http://www.vakuumverpackt.de/ivv/\n\n");
	dialog.addHeader("License:\n");
	dialog.addText(GPL "\n");
	dialog.performAction();
}


void IncompleteVRMLViewer::menuHelpOpenGLInfo(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::menuHelpOpenGLInfo()")

	if (thiz->beginGLExecution() == false)
	{
		return;
	}

	std::string ext((char *) glGetString(GL_EXTENSIONS));

	int pos;
	while ((pos = ext.find(' ')) != std::string::npos)
	{
		ext.replace(pos, 1, "\n\t");
	}

	int n;
	glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &n);
	std::ostringstream depth;
	depth << n;

	InfoDialog dialog("iVRML Viewer - OpenGL Info");
	dialog.addHeader("GL_VENDOR:\n");
	dialog.addText((char *) glGetString(GL_VENDOR));
	dialog.addHeader("\n\nGL_RENDERER:\n");
	dialog.addText((char *) glGetString(GL_RENDERER));
	dialog.addHeader("\n\nGL_VERSION:\n");
	dialog.addText((char *) glGetString(GL_VERSION));
	dialog.addHeader("\n\nGL_MAX_MODELVIEW_STACK_DEPTH:\n");
	dialog.addText(depth.str().c_str());
	dialog.addHeader("\n\nGL_EXTENSIONS:\n");
	dialog.addText((char *) ext.c_str());
	dialog.performAction();

	thiz->endGLExececution();
}


gboolean IncompleteVRMLViewer::popupHandler(IncompleteVRMLViewer *thiz, GdkEventButton *event)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::popupHandler()")

	if (event->button == 3)
	{
		gtk_menu_popup(GTK_MENU(thiz->popup), NULL, NULL, NULL, NULL, event->button, event->time);
	}

	return false;
}


void IncompleteVRMLViewer::popupHide(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::popupHide()")

	GraphNode *node = thiz->sceneGraphGetSelectedNode();
	if (node == NULL)
	{
		return;
	}

	GtkTreeIter iter;
	if (thiz->sceneGraphGetSelectedIter(&iter))
	{
		gtk_tree_store_set(thiz->sceneGraphModel, &iter, VISIBLE_COLUMN, GTK_STOCK_CANCEL, -1);
	}

	node->hideNode();

	if (thiz->trackball.getFrictionless() == false)
	{
		thiz->redrawScene();
	}
}


void IncompleteVRMLViewer::popupUnhide(IncompleteVRMLViewer *thiz, guint action, GtkWidget *widget)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::popupUnhide()")

	GraphNode *node = thiz->sceneGraphGetSelectedNode();
	if (node == NULL)
	{
		return;
	}

	GtkTreeIter iter;
	if (thiz->sceneGraphGetSelectedIter(&iter))
	{
		gtk_tree_store_set(thiz->sceneGraphModel, &iter, VISIBLE_COLUMN, "", -1);
	}

	node->unhideNode();

	if (thiz->trackball.getFrictionless() == false)
	{
		thiz->redrawScene();
	}
}


bool IncompleteVRMLViewer::isShadingModelFlat(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::isShadingModelFlat()")

	return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Shading Model/Flat")));
}


bool IncompleteVRMLViewer::isShadingModelSmooth(void)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::isShadingModelSmooth()")

	return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(this->menuItemFactory, "/View/Shading Model/Smooth")));
}


void IncompleteVRMLViewer::setFrictionless(bool state)
{
	DEBUG_MESSAGE("IncompleteVRMLViewer::setFrictionless()")

	if (this->getSceneGraph()->getScene() == NULL)
	{
		return;
	}

	if (this->showFramesPerSecond)
	{
		this->enableIdleEvent();
		return;
	}

	if (this->useFrictionlessTrackball)
	{
		if (state)
		{
			this->enableIdleEvent();
		}
		else
		{
			this->disableIdleEvent();
		}
	}
	else
	{
		this->disableIdleEvent();
	}
}

