/****************************************************************************

 Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.

 This file is part of the QGLViewer library version 2.6.3.

 http://www.libqglviewer.com - contact@libqglviewer.com

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the LICENSE file included in the packaging of this file.
 In addition, as a special exception, Gilles Debunne gives you certain 
 additional rights, described in the file GPL_EXCEPTION in this package.

 libQGLViewer uses dual licensing. Commercial/proprietary software must
 purchase a libQGLViewer Commercial License.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#include "domUtils.h"
#include "qglviewer.h"
#include "camera.h"
#include "keyFrameInterpolator.h"
#include "manipulatedCameraFrame.h"

# include <QtAlgorithms>
# include <QTextEdit>
# include <QApplication>
# include <QFileInfo>
# include <QDateTime>
# include <QMessageBox>
# include <QPushButton>
# include <QTabWidget>
# include <QTextStream>
# include <QMouseEvent>
#include <QPainter>
# include <QTimer>
# include <QImage>
# include <QDir>
# include <QUrl>

using namespace std;
using namespace qglviewer;

// Static private variable
QList<QGLViewer*> QGLViewer::QGLViewerPool_;


/*! \mainpage

libQGLViewer is a free C++ library based on Qt that enables the quick creation of OpenGL 3D viewers.
It features a powerful camera trackball and simple applications simply require an implementation of
the <code>draw()</code> method. This makes it a tool of choice for OpenGL beginners and
assignments. It provides screenshot saving, mouse manipulated frames, stereo display, interpolated
keyFrames, object selection, and much more. It is fully
customizable and easy to extend to create complex applications, with a possible Qt GUI.

libQGLViewer is <i>not</i> a 3D viewer that can be used directly to view 3D scenes in various
formats. It is more likely to be the starting point for the coding of such a viewer.

libQGLViewer is based on the Qt toolkit and hence compiles on any architecture (Unix-Linux, Mac,
Windows, ...). Full reference documentation and many examples are provided.

See the project main page for details on the project and installation steps. */

void QGLViewer::defaultConstructor()
{
	// Test OpenGL context
	// if (glGetString(GL_VERSION) == 0)
	// qWarning("Unable to get OpenGL version, context may not be available - Check your configuration");

	std::cout << "OpenGL version: " << format().majorVersion()
			  << "." << format().minorVersion() << std::endl;

	int poolIndex = QGLViewer::QGLViewerPool_.indexOf(NULL);
	setFocusPolicy(Qt::StrongFocus);

	if (poolIndex >= 0)
		QGLViewer::QGLViewerPool_.replace(poolIndex, this);
	else
		QGLViewer::QGLViewerPool_.append(this);

	camera_ = new Camera();
	setCamera(camera());

	setDefaultShortcuts();
	setDefaultMouseBindings();

	setSnapshotFileName(tr("snapshot", "Default snapshot file name"));
	initializeSnapshotFormats();
	setSnapshotCounter(0);
	setSnapshotQuality(95);

	fpsTime_.start();
	fpsCounter_     = 0;
	f_p_s_          = 0.0;
	fpsString_      = tr("%1Hz", "Frames per seconds, in Hertz").arg("?");
	visualHint_     = 0;
	previousPathId_	= 0;
	// prevPos_ is not initialized since pos() is not meaningful here.
	// It will be set when setFullScreen(false) is called after setFullScreen(true)

	// #CONNECTION# default values in initFromDOMElement()
	manipulatedFrame_ = NULL;
	manipulatedFrameIsACamera_ = false;
	mouseGrabberIsAManipulatedFrame_ = false;
	mouseGrabberIsAManipulatedCameraFrame_ = false;
	displayMessage_ = false;
	connect(&messageTimer_, SIGNAL(timeout()), SLOT(hideMessage()));
	messageTimer_.setSingleShot(true);
	helpWidget_ = NULL;
	setMouseGrabber(NULL);

	setSceneRadius(1.0);
	showEntireScene();
	setStateFileName(".qglviewer.xml");

	// #CONNECTION# default values in initFromDOMElement()
	setAxisIsDrawn(false);
	setGridIsDrawn(false);
	setFPSIsDisplayed(false);
	setCameraIsEdited(false);
	setTextIsEnabled(true);
	setStereoDisplay(false);
	// Make sure move() is not called, which would call initializeGL()
	fullScreen_ = false;
	setFullScreen(false);

	animationTimerId_ = 0;
	stopAnimation();
	setAnimationPeriod(40); // 25Hz

	selectBuffer_ = NULL;

	bufferTextureId_ = 0;
	bufferTextureMaxU_ = 0.0;
	bufferTextureMaxV_ = 0.0;
	bufferTextureWidth_ = 0;
	bufferTextureHeight_ = 0;
	previousBufferTextureFormat_ = 0;
	previousBufferTextureInternalFormat_ = 0;
	currentlyPressedKey_ = Qt::Key(0);

	setAttribute(Qt::WA_NoSystemBackground);

	tileRegion_ = NULL;
}

#if !defined QT3_SUPPORT
/*! Constructor. See \c QGLWidget documentation for details.

All viewer parameters (display flags, scene parameters, associated objects...) are set to their default values. See
the associated documentation.

If the \p shareWidget parameter points to a valid \c QGLWidget, the QGLViewer will share the OpenGL
context with \p shareWidget (see isSharing()). */
QGLViewer::QGLViewer(QWidget* parent, Qt::WindowFlags flags)
    : QOpenGLWidget(parent, flags)
{ defaultConstructor(); }
#endif // QT3_SUPPORT

/*! Virtual destructor.

The viewer is replaced by \c NULL in the QGLViewerPool() (in order to preserve other viewer's indexes) and allocated
memory is released. The camera() is deleted and should be copied before if it is shared by an other viewer. */
QGLViewer::~QGLViewer()
{
	// See closeEvent comment. Destructor is called (and not closeEvent) only when the widget is embedded.
	// Hence we saveToFile here. It is however a bad idea if virtual domElement() has been overloaded !
	// if (parent())
	// saveStateToFileForAllViewers();

	QGLViewer::QGLViewerPool_.replace(QGLViewer::QGLViewerPool_.indexOf(this), NULL);

	delete camera();
	delete[] selectBuffer_;
	if (helpWidget())
	{
		// Needed for Qt 4 which has no main widget.
		helpWidget()->close();
		delete helpWidget_;
	}
}


static QString QGLViewerVersionString()
{
	return QString::number((QGLVIEWER_VERSION & 0xff0000) >> 16) + "." +
			QString::number((QGLVIEWER_VERSION & 0x00ff00) >> 8) + "." +
			QString::number(QGLVIEWER_VERSION & 0x0000ff);
}

static Qt::KeyboardModifiers keyboardModifiersFromState(unsigned int state) {
	// Convertion of keyboard modifiers and mouse buttons as an int is no longer supported : emulate
	return Qt::KeyboardModifiers(int(state & 0xFF000000));
}


static Qt::MouseButton mouseButtonFromState(unsigned int state) {
	// Convertion of keyboard modifiers and mouse buttons as an int is no longer supported : emulate
	return Qt::MouseButton(state & 0xFFFF);
}

/*! Initializes the QGLViewer OpenGL context and then calls user-defined init().

This method is automatically called once, before the first call to paintGL().

Overload init() instead of this method to modify viewer specific OpenGL state or to create display
lists.

To make beginners' life easier and to simplify the examples, this method slightly modifies the
standard OpenGL state:
\code
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glEnable(GL_COLOR_MATERIAL);
\endcode

If you port an existing application to QGLViewer and your display changes, you probably want to
disable these flags in init() to get back to a standard OpenGL state. */
void QGLViewer::initializeGL()
{
	glEnable(GL_DEPTH_TEST);

	// Default colors
	setForegroundColor(QColor(180, 180, 180));
	setBackgroundColor(QColor(51, 51, 51));

	// Clear the buffer where we're going to draw
	if (format().stereo())
	{
		glDrawBuffer(GL_BACK_RIGHT);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glDrawBuffer(GL_BACK_LEFT);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}
	else
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Calls user defined method. Default emits a signal.
	init();

	// Give time to glInit to finish and then call setFullScreen().
	if (isFullScreen())
		QTimer::singleShot( 100, this, SLOT(delayedFullScreen()) );
}

/*! Main paint method, inherited from \c QGLWidget.

Calls the following methods, in that order:
\arg preDraw() (or preDrawStereo() if viewer displaysInStereo()) : places the camera in the world coordinate system.
\arg draw() (or fastDraw() when the camera is manipulated) : main drawing method. Should be overloaded.
\arg postDraw() : display of visual hints (world axis, FPS...) */
void QGLViewer::paintGL()
{
	if (displaysInStereo())
	{
		for (int view=1; view>=0; --view)
		{
			// Clears screen, set model view matrix with shifted matrix for ith buffer
			preDrawStereo(view);
			// Used defined method. Default is empty
			if (camera()->frame()->isManipulated())
				fastDraw();
			else
				draw();
			postDraw();
		}
	}
	else
	{
		// Clears screen, set model view matrix...
		preDraw();
		// Used defined method. Default calls draw()
		if (camera()->frame()->isManipulated())
			fastDraw();
		else
			draw();
		// Add visual hints: axis, camera, grid...
		postDraw();
	}
	Q_EMIT drawFinished(true);
}

/*! Sets OpenGL state before draw().

Default behavior clears screen and sets the projection and modelView matrices:
\code
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

camera()->loadProjectionMatrix();
camera()->loadModelViewMatrix();
\endcode

Emits the drawNeeded() signal once this is done (see the <a href="../examples/callback.html">callback example</a>). */
void QGLViewer::preDraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// GL_PROJECTION matrix
	camera()->loadProjectionMatrix();
	// GL_MODELVIEW matrix
	camera()->loadModelViewMatrix();

	Q_EMIT drawNeeded();
}

/*! Called after draw() to draw viewer visual hints.

Default implementation displays axis, grid, FPS... when the respective flags are sets.

See the <a href="../examples/multiSelect.html">multiSelect</a> and <a
href="../examples/contribs.html#thumbnail">thumbnail</a> examples for an overloading illustration.

The GLContext (color, LIGHTING, BLEND...) is \e not modified by this method, so that in
draw(), the user can rely on the OpenGL context he defined. Respect this convention (by pushing/popping the
different attributes) if you overload this method. */
void QGLViewer::postDraw()
{
	// Reset model view matrix to world coordinates origin
	camera()->loadModelViewMatrix();
	// TODO restore model loadProjectionMatrixStereo

	// FPS computation
	const unsigned int maxCounter = 20;
	if (++fpsCounter_ == maxCounter)
	{
		f_p_s_ = 1000.0 * maxCounter / fpsTime_.restart();
		fpsString_ = tr("%1Hz", "Frames per seconds, in Hertz").arg(f_p_s_, 0, 'f', ((f_p_s_ < 10.0)?1:0));
		fpsCounter_ = 0;
	}

	bool depthTestWasEnabled = glIsEnabled(GL_DEPTH_TEST);
	glDisable(GL_DEPTH_TEST);

	// Restore GL state
	if (depthTestWasEnabled)
		glEnable(GL_DEPTH_TEST);
}

/*! Called before draw() (instead of preDraw()) when viewer displaysInStereo().

Same as preDraw() except that the glDrawBuffer() is set to \c GL_BACK_LEFT or \c GL_BACK_RIGHT
depending on \p leftBuffer, and it uses qglviewer::Camera::loadProjectionMatrixStereo() and
qglviewer::Camera::loadModelViewMatrixStereo() instead. */
void QGLViewer::preDrawStereo(bool leftBuffer)
{
	// Set buffer to draw in
	// Seems that SGI and Crystal Eyes are not synchronized correctly !
	// That's why we don't draw in the appropriate buffer...
	if (!leftBuffer)
		glDrawBuffer(GL_BACK_LEFT);
	else
		glDrawBuffer(GL_BACK_RIGHT);

	// Clear the buffer where we're going to draw
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// GL_PROJECTION matrix
	//camera()->loadProjectionMatrixStereo(leftBuffer);
	// GL_MODELVIEW matrix
	camera()->loadModelViewMatrixStereo(leftBuffer);

	Q_EMIT drawNeeded();
}

/*! Draws a simplified version of the scene to guarantee interactive camera displacements.

This method is called instead of draw() when the qglviewer::Camera::frame() is
qglviewer::ManipulatedCameraFrame::isManipulated(). Default implementation simply calls draw().

Overload this method if your scene is too complex to allow for interactive camera manipulation. See
the <a href="../examples/fastDraw.html">fastDraw example</a> for an illustration. */
void QGLViewer::fastDraw()
{
	draw();
}

/*! Starts (\p edit = \c true, default) or stops (\p edit=\c false) the edition of the camera().

Current implementation is limited to paths display. Get current state using cameraIsEdited().

\attention This method sets the qglviewer::Camera::zClippingCoefficient() to 5.0 when \p edit is \c
true, so that the Camera paths (see qglviewer::Camera::keyFrameInterpolator()) are not clipped. It
restores the previous value when \p edit is \c false. */
void QGLViewer::setCameraIsEdited(bool edit)
{
	cameraIsEdited_ = edit;
	if (edit)
	{
		previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient();
		// #CONNECTION# 5.0 also used in domElement() and in initFromDOMElement().
		camera()->setZClippingCoefficient(5.0);
	}
	else
		camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_);

	Q_EMIT cameraIsEditedChanged(edit);

	update();
}

// Key bindings. 0 means not defined
void QGLViewer::setDefaultShortcuts()
{
	// D e f a u l t   a c c e l e r a t o r s
	setShortcut(DRAW_AXIS, Qt::Key_A);
	setShortcut(DRAW_GRID, Qt::Key_G);
	setShortcut(DISPLAY_FPS, Qt::Key_F);
	setShortcut(ENABLE_TEXT, Qt::SHIFT+Qt::Key_Question);
	setShortcut(EXIT_VIEWER, Qt::Key_Escape);
	setShortcut(SAVE_SCREENSHOT, Qt::CTRL+Qt::Key_S);
	setShortcut(CAMERA_MODE, Qt::Key_Space);
	setShortcut(FULL_SCREEN, Qt::ALT+Qt::Key_Return);
	setShortcut(STEREO, Qt::Key_S);
	setShortcut(ANIMATION, Qt::Key_Return);
	setShortcut(HELP, Qt::Key_H);
	setShortcut(EDIT_CAMERA, Qt::Key_C);
	setShortcut(MOVE_CAMERA_LEFT, Qt::Key_Left);
	setShortcut(MOVE_CAMERA_RIGHT,Qt::Key_Right);
	setShortcut(MOVE_CAMERA_UP, Qt::Key_Up);
	setShortcut(MOVE_CAMERA_DOWN, Qt::Key_Down);
	setShortcut(INCREASE_FLYSPEED, Qt::Key_Plus);
	setShortcut(DECREASE_FLYSPEED, Qt::Key_Minus);
	setShortcut(SNAPSHOT_TO_CLIPBOARD, Qt::CTRL+Qt::Key_C);

	keyboardActionDescription_[DISPLAY_FPS] = tr("Toggles the display of the FPS", "DISPLAY_FPS action description");
	keyboardActionDescription_[SAVE_SCREENSHOT] = tr("Saves a screenshot", "SAVE_SCREENSHOT action description");
	keyboardActionDescription_[FULL_SCREEN] = tr("Toggles full screen display", "FULL_SCREEN action description");
	keyboardActionDescription_[DRAW_AXIS] = tr("Toggles the display of the world axis", "DRAW_AXIS action description");
	keyboardActionDescription_[DRAW_GRID] = tr("Toggles the display of the XY grid", "DRAW_GRID action description");
	keyboardActionDescription_[CAMERA_MODE] = tr("Changes camera mode (observe or fly)", "CAMERA_MODE action description");
	keyboardActionDescription_[STEREO] = tr("Toggles stereo display", "STEREO action description");
	keyboardActionDescription_[HELP] = tr("Opens this help window", "HELP action description");
	keyboardActionDescription_[ANIMATION] = tr("Starts/stops the animation", "ANIMATION action description");
	keyboardActionDescription_[EDIT_CAMERA] = tr("Toggles camera paths display", "EDIT_CAMERA action description"); // TODO change
	keyboardActionDescription_[ENABLE_TEXT] = tr("Toggles the display of the text", "ENABLE_TEXT action description");
	keyboardActionDescription_[EXIT_VIEWER] = tr("Exits program", "EXIT_VIEWER action description");
	keyboardActionDescription_[MOVE_CAMERA_LEFT] = tr("Moves camera left", "MOVE_CAMERA_LEFT action description");
	keyboardActionDescription_[MOVE_CAMERA_RIGHT] = tr("Moves camera right", "MOVE_CAMERA_RIGHT action description");
	keyboardActionDescription_[MOVE_CAMERA_UP] = tr("Moves camera up", "MOVE_CAMERA_UP action description");
	keyboardActionDescription_[MOVE_CAMERA_DOWN] = tr("Moves camera down", "MOVE_CAMERA_DOWN action description");
	keyboardActionDescription_[INCREASE_FLYSPEED] = tr("Increases fly speed", "INCREASE_FLYSPEED action description");
	keyboardActionDescription_[DECREASE_FLYSPEED] = tr("Decreases fly speed", "DECREASE_FLYSPEED action description");
	keyboardActionDescription_[SNAPSHOT_TO_CLIPBOARD] = tr("Copies a snapshot to clipboard", "SNAPSHOT_TO_CLIPBOARD action description");

	// K e y f r a m e s   s h o r t c u t   k e y s
	setPathKey(Qt::Key_F1,   1);
	setPathKey(Qt::Key_F2,   2);
	setPathKey(Qt::Key_F3,   3);
	setPathKey(Qt::Key_F4,   4);
	setPathKey(Qt::Key_F5,   5);
	setPathKey(Qt::Key_F6,   6);
	setPathKey(Qt::Key_F7,   7);
	setPathKey(Qt::Key_F8,   8);
	setPathKey(Qt::Key_F9,   9);
	setPathKey(Qt::Key_F10, 10);
	setPathKey(Qt::Key_F11, 11);
	setPathKey(Qt::Key_F12, 12);

	setAddKeyFrameKeyboardModifiers(Qt::AltModifier);
	setPlayPathKeyboardModifiers(Qt::NoModifier);
}

// M o u s e   b e h a v i o r
void QGLViewer::setDefaultMouseBindings()
{
	const Qt::KeyboardModifiers cameraKeyboardModifiers = Qt::NoModifier;
	const Qt::KeyboardModifiers frameKeyboardModifiers = Qt::ControlModifier;

	//#CONNECTION# toggleCameraMode()
	for (int handler=0; handler<2; ++handler)
	{
		MouseHandler mh = (MouseHandler)(handler);
		Qt::KeyboardModifiers modifiers = (mh == FRAME) ? frameKeyboardModifiers : cameraKeyboardModifiers;

		setMouseBinding(modifiers, Qt::LeftButton,  mh, ROTATE);
		setMouseBinding(modifiers, Qt::MidButton,   mh, ZOOM);
		setMouseBinding(modifiers, Qt::RightButton, mh, TRANSLATE);

		setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, mh, SCREEN_ROTATE);

		setWheelBinding(modifiers, mh, ZOOM);
	}

	// Z o o m   o n   r e g i o n
	setMouseBinding(Qt::ShiftModifier, Qt::MidButton, CAMERA, ZOOM_ON_REGION);

	// S e l e c t
	setMouseBinding(Qt::ShiftModifier, Qt::LeftButton, SELECT);

	setMouseBinding(Qt::ShiftModifier, Qt::RightButton, RAP_FROM_PIXEL);
	// D o u b l e   c l i c k
	setMouseBinding(Qt::NoModifier, Qt::LeftButton, ALIGN_CAMERA,      true);
	setMouseBinding(Qt::NoModifier, Qt::MidButton,  SHOW_ENTIRE_SCENE, true);
	setMouseBinding(Qt::NoModifier, Qt::RightButton, CENTER_SCENE,     true);

	setMouseBinding(frameKeyboardModifiers, Qt::LeftButton, ALIGN_FRAME, true);
	// middle double click makes no sense for manipulated frame
	setMouseBinding(frameKeyboardModifiers, Qt::RightButton, CENTER_FRAME, true);

	// A c t i o n s   w i t h   k e y   m o d i f i e r s
	setMouseBinding(Qt::Key_Z, Qt::NoModifier, Qt::LeftButton, ZOOM_ON_PIXEL);
	setMouseBinding(Qt::Key_Z, Qt::NoModifier, Qt::RightButton, ZOOM_TO_FIT);

#ifdef Q_OS_MAC
	// Specific Mac bindings for touchpads. Two fingers emulate a wheelEvent which zooms.
	// There is no right button available : make Option key + left emulate the right button.
	// A Control+Left indeed emulates a right click (OS X system configuration), but it does
	// no seem to support dragging.
	// Done at the end to override previous settings.
	const Qt::KeyboardModifiers macKeyboardModifiers = Qt::AltModifier;

	setMouseBinding(macKeyboardModifiers, Qt::LeftButton, CAMERA, TRANSLATE);
	setMouseBinding(macKeyboardModifiers, Qt::LeftButton, CENTER_SCENE, true);
	setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, Qt::LeftButton, CENTER_FRAME, true);
	setMouseBinding(frameKeyboardModifiers | macKeyboardModifiers, Qt::LeftButton, FRAME, TRANSLATE);
#endif
}

/*! Associates a new qglviewer::Camera to the viewer.

You should only use this method when you derive a new class from qglviewer::Camera and want to use
one of its instances instead of the original class.

It you simply want to save and restore Camera positions, use qglviewer::Camera::addKeyFrameToPath()
and qglviewer::Camera::playPath() instead.

This method silently ignores \c NULL \p camera pointers. The calling method is responsible for deleting
the previous camera pointer in order to prevent memory leaks if needed.

The sceneRadius() and sceneCenter() of \p camera are set to the \e current QGLViewer values.

All the \p camera qglviewer::Camera::keyFrameInterpolator()
qglviewer::KeyFrameInterpolator::interpolated() signals are connected to the viewer update() slot.
The connections with the previous viewer's camera are removed. */
void QGLViewer::setCamera(Camera* const camera)
{
	if (!camera)
		return;

	camera->setSceneRadius(sceneRadius());
	camera->setSceneCenter(sceneCenter());
	camera->setScreenWidthAndHeight(width(), height());

	// Disconnect current camera from this viewer.
	disconnect(this->camera()->frame(), SIGNAL(manipulated()), this, SLOT(update()));
	disconnect(this->camera()->frame(), SIGNAL(spun()), this, SLOT(update()));

	// Connect camera frame to this viewer.
	connect(camera->frame(), SIGNAL(manipulated()), SLOT(update()));
	connect(camera->frame(), SIGNAL(spun()), SLOT(update()));

	connectAllCameraKFIInterpolatedSignals(false);
	camera_ = camera;
	connectAllCameraKFIInterpolatedSignals();

	previousCameraZClippingCoefficient_ = this->camera()->zClippingCoefficient();
}

void QGLViewer::connectAllCameraKFIInterpolatedSignals(bool connection)
{
	for (QMap<unsigned int, KeyFrameInterpolator*>::ConstIterator it = camera()->kfi_.begin(), end=camera()->kfi_.end(); it != end; ++it)
	{
		if (connection)
			connect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), SLOT(update()));
		else
			disconnect(camera()->keyFrameInterpolator(it.key()), SIGNAL(interpolated()), this, SLOT(update()));
	}

	if (connection)
		connect(camera()->interpolationKfi_, SIGNAL(interpolated()), SLOT(update()));
	else
		disconnect(camera()->interpolationKfi_, SIGNAL(interpolated()), this, SLOT(update()));
}

/*! Briefly displays a message in the lower left corner of the widget. Convenient to provide
feedback to the user.

\p message is displayed during \p delay milliseconds (default is 2 seconds) using drawText().

This method should not be called in draw(). If you want to display a text in each draw(), use
drawText() instead.

If this method is called when a message is already displayed, the new message replaces the old one.
Use setTextIsEnabled() (default shortcut is '?') to enable or disable text (and hence messages)
display. */
void QGLViewer::displayMessage(const QString& message, int delay)
{
	message_ = message;
	displayMessage_ = true;
	// Was set to single shot in defaultConstructor.
	messageTimer_.start(delay);
	if (textIsEnabled())
		update();
}

void QGLViewer::hideMessage()
{
	displayMessage_ = false;
	if (textIsEnabled())
		update();
}


/*! Overloading of the \c QObject method.

If animationIsStarted(), calls animate() and draw(). */
void QGLViewer::timerEvent(QTimerEvent *)
{
	if (animationIsStarted())
	{
		animate();
		update();
	}
}

/*! Starts the animation loop. See animationIsStarted(). */
void QGLViewer::startAnimation()
{
	animationTimerId_ = startTimer(animationPeriod());
	animationStarted_ = true;
}

/*! Stops animation. See animationIsStarted(). */
void QGLViewer::stopAnimation()
{
	animationStarted_ = false;
	if (animationTimerId_ != 0)
		killTimer(animationTimerId_);
}

/*! Overloading of the \c QWidget method.

Saves the viewer state using saveStateToFile() and then calls QGLWidget::closeEvent(). */
void QGLViewer::closeEvent(QCloseEvent *e)
{
	// When the user clicks on the window close (x) button:
	// - If the viewer is a top level window, closeEvent is called and then saves to file.
	// - Otherwise, nothing happen s:(
	// When the user press the EXIT_VIEWER keyboard shortcut:
	// - If the viewer is a top level window, saveStateToFile() is also called
	// - Otherwise, closeEvent is NOT called and keyPressEvent does the job.

	/* After tests:
	E : Embedded widget
	N : Widget created with new
	C : closeEvent called
	D : destructor called

	E	N	C	D
	y	y
	y	n		y
	n	y	y
	n	n	y	y

	closeEvent is called iif the widget is NOT embedded.

	Destructor is called iif the widget is created on the stack
	or if widget (resp. parent if embedded) is created with WDestructiveClose flag.

	closeEvent always before destructor.

	Close using qApp->closeAllWindows or (x) is identical.
	*/

	// #CONNECTION# Also done for EXIT_VIEWER in keyPressEvent().
	saveStateToFile();
    QOpenGLWidget::closeEvent(e);
}

static QString mouseButtonsString(Qt::MouseButtons b)
{
	QString result("");
	bool addAmpersand = false;
	if (b & Qt::LeftButton)    { result += QGLViewer::tr("Left", "left mouse button"); addAmpersand=true; }
	if (b & Qt::MidButton)     { if (addAmpersand) result += " & "; result += QGLViewer::tr("Middle", "middle mouse button"); addAmpersand=true; }
	if (b & Qt::RightButton)   { if (addAmpersand) result += " & "; result += QGLViewer::tr("Right", "right mouse button"); }
	return result;
}

void QGLViewer::performClickAction(ClickAction ca, const QMouseEvent* const e)
{
	// Note: action that need it should call update().
	switch (ca)
	{
		// # CONNECTION setMouseBinding prevents adding NO_CLICK_ACTION in clickBinding_
		// This case should hence not be possible. Prevents unused case warning.
		case NO_CLICK_ACTION :
			break;
		case ZOOM_ON_PIXEL :
			camera()->interpolateToZoomOnPixel(e->pos());
			break;
		case ZOOM_TO_FIT :
			camera()->interpolateToFitScene();
			break;
		case SELECT :
			//select(e);	DEPRECATED!
			update();
			break;
		case RAP_FROM_PIXEL :
			if (! camera()->setPivotPointFromPixel(e->pos()))
				camera()->setPivotPoint(sceneCenter());
			setVisualHintsMask(1);
			update();
			break;
		case RAP_IS_CENTER :
			camera()->setPivotPoint(sceneCenter());
			setVisualHintsMask(1);
			update();
			break;
		case CENTER_FRAME :
			if (manipulatedFrame())
				manipulatedFrame()->projectOnLine(camera()->position(), camera()->viewDirection());
			break;
		case CENTER_SCENE :
			camera()->centerScene();
			break;
		case SHOW_ENTIRE_SCENE :
			camera()->showEntireScene();
			break;
		case ALIGN_FRAME :
			if (manipulatedFrame())
				manipulatedFrame()->alignWithFrame(camera()->frame());
			break;
		case ALIGN_CAMERA :
			Frame * frame = new Frame();
			frame->setTranslation(camera()->pivotPoint());
			camera()->frame()->alignWithFrame(frame, true);
			delete frame;
			break;
	}
}

/*! Overloading of the \c QWidget method.

When the user clicks on the mouse:
\arg if a mouseGrabber() is defined, qglviewer::MouseGrabber::mousePressEvent() is called,
\arg otherwise, the camera() or the manipulatedFrame() interprets the mouse displacements,
depending on mouse bindings.

Mouse bindings customization can be achieved using setMouseBinding() and setWheelBinding(). See the
<a href="../mouse.html">mouse page</a> for a complete description of mouse bindings.

See the mouseMoveEvent() documentation for an example of more complex mouse behavior customization
using overloading.

\note When the mouseGrabber() is a manipulatedFrame(), the modifier keys are not taken into
account. This allows for a direct manipulation of the manipulatedFrame() when the mouse hovers,
which is probably what is expected. */
void QGLViewer::mousePressEvent(QMouseEvent* e)
{
	//#CONNECTION# mouseDoubleClickEvent has the same structure
	//#CONNECTION# mouseString() concatenates bindings description in inverse order.
	ClickBindingPrivate cbp(e->modifiers(), e->button(), false, (Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_);

	if (clickBinding_.contains(cbp)) {
		performClickAction(clickBinding_[cbp], e);
	} else
		if (mouseGrabber())
		{
			if (mouseGrabberIsAManipulatedFrame_)
			{
				for (QMap<MouseBindingPrivate, MouseActionPrivate>::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it!=end; ++it)
					if ((it.value().handler == FRAME) && (it.key().button == e->button()))
					{
						ManipulatedFrame* mf = dynamic_cast<ManipulatedFrame*>(mouseGrabber());
						if (mouseGrabberIsAManipulatedCameraFrame_)
						{
							mf->ManipulatedFrame::startAction(it.value().action, it.value().withConstraint);
							mf->ManipulatedFrame::mousePressEvent(e, camera());
						}
						else
						{
							mf->startAction(it.value().action, it.value().withConstraint);
							mf->mousePressEvent(e, camera());
						}
						break;
					}
			}
			else
				mouseGrabber()->mousePressEvent(e, camera());
			update();
		}
		else
		{
			//#CONNECTION# wheelEvent has the same structure
			const MouseBindingPrivate mbp(e->modifiers(), e->button(), currentlyPressedKey_);

			if (mouseBinding_.contains(mbp))
			{
				MouseActionPrivate map = mouseBinding_[mbp];
				switch (map.handler)
				{
					case CAMERA :
						camera()->frame()->startAction(map.action, map.withConstraint);
						camera()->frame()->mousePressEvent(e, camera());
						break;
					case FRAME :
						if (manipulatedFrame())
						{
							if (manipulatedFrameIsACamera_)
							{
								manipulatedFrame()->ManipulatedFrame::startAction(map.action, map.withConstraint);
								manipulatedFrame()->ManipulatedFrame::mousePressEvent(e, camera());
							}
							else
							{
								manipulatedFrame()->startAction(map.action, map.withConstraint);
								manipulatedFrame()->mousePressEvent(e, camera());
							}
						}
						break;
				}
				if (map.action == SCREEN_ROTATE)
					// Display visual hint line
					update();
			}
			else
				e->ignore();
		}
}

/*! Overloading of the \c QWidget method.

Mouse move event is sent to the mouseGrabber() (if any) or to the camera() or the
manipulatedFrame(), depending on mouse bindings (see setMouseBinding()).

If you want to define your own mouse behavior, do something like this:
\code
void Viewer::mousePressEvent(QMouseEvent* e)
{

if ((e->button() == myButton) && (e->modifiers() == myModifiers))
  myMouseBehavior = true;
else
  QGLViewer::mousePressEvent(e);
}

void Viewer::mouseMoveEvent(QMouseEvent *e)
{
if (myMouseBehavior)
  // Use e->x() and e->y() as you want...
else
  QGLViewer::mouseMoveEvent(e);
}

void Viewer::mouseReleaseEvent(QMouseEvent* e)
{
if (myMouseBehavior)
  myMouseBehavior = false;
else
  QGLViewer::mouseReleaseEvent(e);
}
\endcode */
void QGLViewer::mouseMoveEvent(QMouseEvent* e)
{
	if (mouseGrabber())
	{
		mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera());
		if (mouseGrabber()->grabsMouse())
			if (mouseGrabberIsAManipulatedCameraFrame_)
				(dynamic_cast<ManipulatedFrame*>(mouseGrabber()))->ManipulatedFrame::mouseMoveEvent(e, camera());
			else
				mouseGrabber()->mouseMoveEvent(e, camera());
		else
			setMouseGrabber(NULL);
		update();
	}

	if (!mouseGrabber())
	{
		//#CONNECTION# mouseReleaseEvent has the same structure
		if (camera()->frame()->isManipulated())
		{
			camera()->frame()->mouseMoveEvent(e, camera());
			// #CONNECTION# manipulatedCameraFrame::mouseMoveEvent specific if at the beginning
			if (camera()->frame()->action_ == ZOOM_ON_REGION)
				update();
		}
		else // !
			if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated()))
				if (manipulatedFrameIsACamera_)
					manipulatedFrame()->ManipulatedFrame::mouseMoveEvent(e, camera());
				else
					manipulatedFrame()->mouseMoveEvent(e, camera());
			else
				if (hasMouseTracking())
				{
					Q_FOREACH (MouseGrabber* mg, MouseGrabber::MouseGrabberPool())
					{
						mg->checkIfGrabsMouse(e->x(), e->y(), camera());
						if (mg->grabsMouse())
						{
							setMouseGrabber(mg);
							// Check that MouseGrabber is not disabled
							if (mouseGrabber() == mg)
							{
								update();
								break;
							}
						}
					}
				}
	}
}

/*! Overloading of the \c QWidget method.

Calls the mouseGrabber(), camera() or manipulatedFrame \c mouseReleaseEvent method.

See the mouseMoveEvent() documentation for an example of mouse behavior customization. */
void QGLViewer::mouseReleaseEvent(QMouseEvent* e)
{
	if (mouseGrabber())
	{
		if (mouseGrabberIsAManipulatedCameraFrame_)
			(dynamic_cast<ManipulatedFrame*>(mouseGrabber()))->ManipulatedFrame::mouseReleaseEvent(e, camera());
		else
			mouseGrabber()->mouseReleaseEvent(e, camera());
		mouseGrabber()->checkIfGrabsMouse(e->x(), e->y(), camera());
		if (!(mouseGrabber()->grabsMouse()))
			setMouseGrabber(NULL);
		// update();
	}
	else
		//#CONNECTION# mouseMoveEvent has the same structure
		if (camera()->frame()->isManipulated())
		{
			camera()->frame()->mouseReleaseEvent(e, camera());
		}
		else
			if ((manipulatedFrame()) && (manipulatedFrame()->isManipulated()))
			{
				if (manipulatedFrameIsACamera_)
					manipulatedFrame()->ManipulatedFrame::mouseReleaseEvent(e, camera());
				else
					manipulatedFrame()->mouseReleaseEvent(e, camera());
			}
			else
				e->ignore();

	// Not absolutely needed (see above commented code for the optimal version), but may reveal
	// useful for specific applications.
	update();
}

/*! Overloading of the \c QWidget method.

If defined, the wheel event is sent to the mouseGrabber(). It is otherwise sent according to wheel
bindings (see setWheelBinding()). */
void QGLViewer::wheelEvent(QWheelEvent* e)
{
	if (mouseGrabber())
	{
		if (mouseGrabberIsAManipulatedFrame_)
		{
			for (QMap<WheelBindingPrivate, MouseActionPrivate>::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it!=end; ++it)
				if (it.value().handler == FRAME)
				{
					ManipulatedFrame* mf = dynamic_cast<ManipulatedFrame*>(mouseGrabber());
					if (mouseGrabberIsAManipulatedCameraFrame_)
					{
						mf->ManipulatedFrame::startAction(it.value().action, it.value().withConstraint);
						mf->ManipulatedFrame::wheelEvent(e, camera());
					}
					else
					{
						mf->startAction(it.value().action, it.value().withConstraint);
						mf->wheelEvent(e, camera());
					}
					break;
				}
		}
		else
			mouseGrabber()->wheelEvent(e, camera());
		update();
	}
	else
	{
		//#CONNECTION# mousePressEvent has the same structure
		WheelBindingPrivate wbp(e->modifiers(), currentlyPressedKey_);

		if (wheelBinding_.contains(wbp))
		{
			MouseActionPrivate map = wheelBinding_[wbp];
			switch (map.handler)
			{
				case CAMERA :
					camera()->frame()->startAction(map.action, map.withConstraint);
					camera()->frame()->wheelEvent(e, camera());
					break;
				case FRAME :
					if (manipulatedFrame()) {
						if (manipulatedFrameIsACamera_)
						{
							manipulatedFrame()->ManipulatedFrame::startAction(map.action, map.withConstraint);
							manipulatedFrame()->ManipulatedFrame::wheelEvent(e, camera());
						}
						else
						{
							manipulatedFrame()->startAction(map.action, map.withConstraint);
							manipulatedFrame()->wheelEvent(e, camera());
						}
					}
					break;
			}
		}
		else
			e->ignore();
	}
}

/*! Overloading of the \c QWidget method.

The behavior of the mouse double click depends on the mouse binding. See setMouseBinding() and the
<a href="../mouse.html">mouse page</a>. */
void QGLViewer::mouseDoubleClickEvent(QMouseEvent* e)
{
	//#CONNECTION# mousePressEvent has the same structure
	ClickBindingPrivate cbp(e->modifiers(), e->button(), true, (Qt::MouseButtons)(e->buttons() & ~(e->button())), currentlyPressedKey_);
	if (clickBinding_.contains(cbp))
		performClickAction(clickBinding_[cbp], e);
	else
		if (mouseGrabber())
			mouseGrabber()->mouseDoubleClickEvent(e, camera());
		else
			e->ignore();
}

/*! Sets the state of displaysInStereo(). See also toggleStereoDisplay().

First checks that the display is able to handle stereovision using QGLWidget::format(). Opens a
warning message box in case of failure. Emits the stereoChanged() signal otherwise. */
void QGLViewer::setStereoDisplay(bool stereo)
{
	if (format().stereo())
	{
		stereo_ = stereo;
		if (!displaysInStereo())
		{
			glDrawBuffer(GL_BACK_LEFT);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			glDrawBuffer(GL_BACK_RIGHT);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		}

		Q_EMIT stereoChanged(stereo_);

		update();
	}
	else
		if (stereo)
			QMessageBox::warning(this, tr("Stereo not supported", "Message box window title"), tr("Stereo is not supported on this display."));
		else
			stereo_ = false;
}

/*! Sets the isFullScreen() state.

If the QGLViewer is embedded in an other QWidget (see QWidget::topLevelWidget()), this widget is
displayed in full screen instead. */
void QGLViewer::setFullScreen(bool fullScreen)
{
	if (fullScreen_ == fullScreen) return;

	fullScreen_ = fullScreen;

	QWidget* tlw = topLevelWidget();

	if (isFullScreen())
	{
		prevPos_ = topLevelWidget()->pos();
		tlw->showFullScreen();
		tlw->move(0,0);
	}
	else
	{
		tlw->showNormal();
		tlw->move(prevPos_);
	}
}

/*! Directly defines the mouseGrabber().

You should not call this method directly as it bypasses the
qglviewer::MouseGrabber::checkIfGrabsMouse() test performed by mouseMoveEvent().

If the MouseGrabber is disabled (see mouseGrabberIsEnabled()), this method silently does nothing. */
void QGLViewer::setMouseGrabber(MouseGrabber* mouseGrabber)
{
	if (!mouseGrabberIsEnabled(mouseGrabber))
		return;

	mouseGrabber_ = mouseGrabber;

	mouseGrabberIsAManipulatedFrame_       = (dynamic_cast<ManipulatedFrame*>(mouseGrabber) != NULL);
	mouseGrabberIsAManipulatedCameraFrame_ = ((dynamic_cast<ManipulatedCameraFrame*>(mouseGrabber) != NULL) &&
											  (mouseGrabber != camera()->frame()));
	Q_EMIT mouseGrabberChanged(mouseGrabber);
}

/*! Sets the mouseGrabberIsEnabled() state. */
void QGLViewer::setMouseGrabberIsEnabled(const qglviewer::MouseGrabber* const mouseGrabber, bool enabled)
{
	if (enabled)
		disabledMouseGrabbers_.remove(reinterpret_cast<size_t>(mouseGrabber));
	else
		disabledMouseGrabbers_[reinterpret_cast<size_t>(mouseGrabber)];
}

QString QGLViewer::mouseActionString(QGLViewer::MouseAction ma)
{
	switch (ma)
	{
		case QGLViewer::NO_MOUSE_ACTION : return QString::null;
		case QGLViewer::ROTATE : return QGLViewer::tr("Rotates", "ROTATE mouse action");
		case QGLViewer::ZOOM : return QGLViewer::tr("Zooms", "ZOOM mouse action");
		case QGLViewer::TRANSLATE : return QGLViewer::tr("Translates", "TRANSLATE mouse action");
		case QGLViewer::MOVE_FORWARD : return QGLViewer::tr("Moves forward", "MOVE_FORWARD mouse action");
		case QGLViewer::LOOK_AROUND : return QGLViewer::tr("Looks around", "LOOK_AROUND mouse action");
		case QGLViewer::MOVE_BACKWARD : return QGLViewer::tr("Moves backward", "MOVE_BACKWARD mouse action");
		case QGLViewer::SCREEN_ROTATE : return QGLViewer::tr("Rotates in screen plane", "SCREEN_ROTATE mouse action");
		case QGLViewer::ROLL : return QGLViewer::tr("Rolls", "ROLL mouse action");
		case QGLViewer::DRIVE : return QGLViewer::tr("Drives", "DRIVE mouse action");
		case QGLViewer::SCREEN_TRANSLATE : return QGLViewer::tr("Horizontally/Vertically translates", "SCREEN_TRANSLATE mouse action");
		case QGLViewer::ZOOM_ON_REGION : return QGLViewer::tr("Zooms on region for", "ZOOM_ON_REGION mouse action");
	}
	return QString::null;
}

QString QGLViewer::clickActionString(QGLViewer::ClickAction ca)
{
	switch (ca)
	{
		case QGLViewer::NO_CLICK_ACTION : return QString::null;
		case QGLViewer::ZOOM_ON_PIXEL : return QGLViewer::tr("Zooms on pixel", "ZOOM_ON_PIXEL click action");
		case QGLViewer::ZOOM_TO_FIT : return QGLViewer::tr("Zooms to fit scene", "ZOOM_TO_FIT click action");
		case QGLViewer::SELECT : return QGLViewer::tr("Selects", "SELECT click action");
		case QGLViewer::RAP_FROM_PIXEL : return QGLViewer::tr("Sets pivot point", "RAP_FROM_PIXEL click action");
		case QGLViewer::RAP_IS_CENTER : return QGLViewer::tr("Resets pivot point", "RAP_IS_CENTER click action");
		case QGLViewer::CENTER_FRAME : return QGLViewer::tr("Centers manipulated frame", "CENTER_FRAME click action");
		case QGLViewer::CENTER_SCENE : return QGLViewer::tr("Centers scene", "CENTER_SCENE click action");
		case QGLViewer::SHOW_ENTIRE_SCENE : return QGLViewer::tr("Shows entire scene", "SHOW_ENTIRE_SCENE click action");
		case QGLViewer::ALIGN_FRAME : return QGLViewer::tr("Aligns manipulated frame", "ALIGN_FRAME click action");
		case QGLViewer::ALIGN_CAMERA : return QGLViewer::tr("Aligns camera", "ALIGN_CAMERA click action");
	}
	return QString::null;
}

static QString keyString(unsigned int key)
{
# if QT_VERSION >= 0x040100
	return QKeySequence(int(key)).toString(QKeySequence::NativeText);
# else
	return QString(QKeySequence(key));
# endif
}

QString QGLViewer::formatClickActionPrivate(ClickBindingPrivate cbp)
{
	bool buttonsBefore = cbp.buttonsBefore != Qt::NoButton;
	QString keyModifierString = keyString(cbp.modifiers + cbp.key);
	if (!keyModifierString.isEmpty()) {
#ifdef Q_OS_MAC
		// modifiers never has a '+' sign. Add one space to clearly separate modifiers (and possible key) from button
		keyModifierString += " ";
#else
		// modifiers might be of the form : 'S' or 'Ctrl+S' or 'Ctrl+'. For consistency, add an other '+' if needed, no spaces
		if (!keyModifierString.endsWith('+'))
			keyModifierString += "+";
#endif
	}

	return tr("%1%2%3%4%5%6", "Modifier / button or wheel / double click / with / button / pressed")
			.arg(keyModifierString)
			.arg(mouseButtonsString(cbp.button)+(cbp.button == Qt::NoButton ? tr("Wheel", "Mouse wheel") : ""))
			.arg(cbp.doubleClick ? tr(" double click", "Suffix after mouse button") : "")
			.arg(buttonsBefore ? tr(" with ", "As in : Left button with Ctrl pressed") : "")
			.arg(buttonsBefore ? mouseButtonsString(cbp.buttonsBefore) : "")
			.arg(buttonsBefore ? tr(" pressed", "As in : Left button with Ctrl pressed") : "");
}

bool QGLViewer::isValidShortcutKey(int key) {
	return (key >= Qt::Key_Any && key < Qt::Key_Escape) || (key >= Qt::Key_F1 && key <= Qt::Key_F35);
}

#ifndef DOXYGEN
/*! This method is deprecated since version 2.5.0

 Use setMouseBindingDescription(Qt::KeyboardModifiers, Qt::MouseButtons, QString, bool, Qt::MouseButtons) instead.
*/
void QGLViewer::setMouseBindingDescription(unsigned int state, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore) {
	qWarning("setMouseBindingDescription(int state,...) is deprecated. Use the modifier/button equivalent");
	setMouseBindingDescription(keyboardModifiersFromState(state),
							   mouseButtonFromState(state),
							   description,
							   doubleClick,
							   buttonsBefore);
}
#endif

/*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab.

 Same as calling setMouseBindingDescription(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, QString, bool, Qt::MouseButtons),
 with a key value of Qt::Key(0) (i.e. binding description when no regular key needs to be pressed). */
void QGLViewer::setMouseBindingDescription(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore)
{
	setMouseBindingDescription(Qt::Key(0), modifiers, button, description, doubleClick, buttonsBefore);
}

/*! Defines a custom mouse binding description, displayed in the help() window's Mouse tab.

\p modifiers is a combination of Qt::KeyboardModifiers (\c Qt::ControlModifier, \c Qt::AltModifier, \c
Qt::ShiftModifier, \c Qt::MetaModifier). Possibly combined using the \c "|" operator.

\p button is one of the Qt::MouseButtons (\c Qt::LeftButton, \c Qt::MidButton,
\c Qt::RightButton...).

\p doubleClick indicates whether or not the user has to double click this button to perform the
described action. \p buttonsBefore lists the buttons that need to be pressed before the double click.

Set an empty \p description to \e remove a mouse binding description.

\code
// The R key combined with the Left mouse button rotates the camera in the screen plane.
setMouseBindingDescription(Qt::Key_R, Qt::NoModifier, Qt::LeftButton, "Rotates camera in screen plane");

// A left button double click toggles full screen
setMouseBindingDescription(Qt::NoModifier, Qt::LeftButton, "Toggles full screen mode", true);

// Removes the description of Ctrl+Right button
setMouseBindingDescription(Qt::ControlModifier, Qt::RightButton, "");
\endcode

Overload mouseMoveEvent() and friends to implement your custom mouse behavior (see the
mouseMoveEvent() documentation for an example). See the <a
href="../examples/keyboardAndMouse.html">keyboardAndMouse example</a> for an illustration.

Use setMouseBinding() and setWheelBinding() to change the standard mouse action bindings. */
void QGLViewer::setMouseBindingDescription(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, QString description, bool doubleClick, Qt::MouseButtons buttonsBefore)
{
	ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key);

	if (description.isEmpty())
		mouseDescription_.remove(cbp);
	else
		mouseDescription_[cbp] = description;
}

static QString tableLine(const QString& left, const QString& right)
{
	static bool even = false;
	const QString tdtd("</b></td><td>");
	const QString tdtr("</td></tr>\n");

	QString res("<tr bgcolor=\"");

	if (even)
		res += "#eeeeff\">";
	else
		res += "#ffffff\">";
	res += "<td><b>" + left + tdtd + right + tdtr;
	even = !even;

	return res;
}

/*! Returns a QString that describes the application mouse bindings, displayed in the help() window
\c Mouse tab.

Result is a table that describes custom application mouse binding descriptions defined using
setMouseBindingDescription() as well as standard mouse bindings (defined using setMouseBinding()
and setWheelBinding()). See the <a href="../mouse.html">mouse page</a> for details on mouse
bindings.

See also helpString() and keyboardString(). */
QString QGLViewer::mouseString() const
{
	QString text("<center><table border=\"1\" cellspacing=\"0\" cellpadding=\"4\">\n");
	const QString trtd("<tr><td>");
	const QString tdtr("</td></tr>\n");
	const QString tdtd("</td><td>");

	text += QString("<tr bgcolor=\"#aaaacc\"><th align=\"center\">%1</th><th align=\"center\">%2</th></tr>\n").
			arg(tr("Button(s)", "Buttons column header in help window mouse tab")).arg(tr("Description", "Description column header in help window mouse tab"));

	QMap<ClickBindingPrivate, QString> mouseBinding;

	// User-defined mouse bindings come first.
	for (QMap<ClickBindingPrivate, QString>::ConstIterator itm=mouseDescription_.begin(), endm=mouseDescription_.end(); itm!=endm; ++itm)
		mouseBinding[itm.key()] = itm.value();

	for (QMap<ClickBindingPrivate, QString>::ConstIterator it=mouseBinding.begin(), end=mouseBinding.end(); it != end; ++it)
	{
		// Should not be needed (see setMouseBindingDescription())
		if (it.value().isNull())
			continue;

		text += tableLine(formatClickActionPrivate(it.key()), it.value());
	}

	// Optional separator line
	if (!mouseBinding.isEmpty())
	{
		mouseBinding.clear();
		text += QString("<tr bgcolor=\"#aaaacc\"><td colspan=2>%1</td></tr>\n").arg(tr("Standard mouse bindings", "In help window mouse tab"));
	}

	// Then concatenates the descriptions of wheelBinding_, mouseBinding_ and clickBinding_.
	// The order is significant and corresponds to the priorities set in mousePressEvent() (reverse priority order, last one overwrites previous)
	// #CONNECTION# mousePressEvent() order
	for (QMap<MouseBindingPrivate, MouseActionPrivate>::ConstIterator itmb=mouseBinding_.begin(), endmb=mouseBinding_.end();
		 itmb != endmb; ++itmb)
	{
		ClickBindingPrivate cbp(itmb.key().modifiers, itmb.key().button, false, Qt::NoButton, itmb.key().key);

		QString text = mouseActionString(itmb.value().action);

		if (!text.isNull())
		{
			switch (itmb.value().handler)
			{
				case CAMERA: text += " " + tr("camera", "Suffix after action"); break;
				case FRAME:  text += " " + tr("manipulated frame", "Suffix after action"); break;
			}
			if (!(itmb.value().withConstraint))
				text += "*";
		}
		mouseBinding[cbp] = text;
	}

	for (QMap<WheelBindingPrivate, MouseActionPrivate>::ConstIterator itw=wheelBinding_.begin(), endw=wheelBinding_.end(); itw != endw; ++itw)
	{
		ClickBindingPrivate cbp(itw.key().modifiers, Qt::NoButton, false, Qt::NoButton, itw.key().key);

		QString text = mouseActionString(itw.value().action);

		if (!text.isNull())
		{
			switch (itw.value().handler)
			{
				case CAMERA: text += " " + tr("camera", "Suffix after action"); break;
				case FRAME:  text += " " + tr("manipulated frame", "Suffix after action"); break;
			}
			if (!(itw.value().withConstraint))
				text += "*";
		}

		mouseBinding[cbp] = text;
	}

	for (QMap<ClickBindingPrivate, ClickAction>::ConstIterator itcb=clickBinding_.begin(), endcb=clickBinding_.end(); itcb!=endcb; ++itcb)
		mouseBinding[itcb.key()] = clickActionString(itcb.value());

	for (QMap<ClickBindingPrivate, QString>::ConstIterator it2=mouseBinding.begin(), end2=mouseBinding.end(); it2 != end2; ++it2)
	{
		if (it2.value().isNull())
			continue;

		text += tableLine(formatClickActionPrivate(it2.key()), it2.value());
	}

	text += "</table></center>";

	return text;
}

/*! Defines a custom keyboard shortcut description, that will be displayed in the help() window \c
Keyboard tab.

The \p key definition is given as an \c int using Qt enumerated values. Set an empty \p description
to remove a shortcut description:
\code
setKeyDescription(Qt::Key_W, "Toggles wireframe display");
setKeyDescription(Qt::CTRL+Qt::Key_L, "Loads a new scene");
// Removes a description
setKeyDescription(Qt::CTRL+Qt::Key_C, "");
\endcode

See the <a href="../examples/keyboardAndMouse.html">keyboardAndMouse example</a> for illustration
and the <a href="../keyboard.html">keyboard page</a> for details. */
void QGLViewer::setKeyDescription(unsigned int key, QString description)
{
	if (description.isEmpty())
		keyDescription_.remove(key);
	else
		keyDescription_[key] = description;
}

QString QGLViewer::cameraPathKeysString() const
{
	if (pathIndex_.isEmpty())
		return QString::null;

	QVector<Qt::Key> keys;
	keys.reserve(pathIndex_.count());
	for (QMap<Qt::Key, unsigned int>::ConstIterator i = pathIndex_.begin(), endi=pathIndex_.end(); i != endi; ++i)
		keys.push_back(i.key());
	qSort(keys);

	QVector<Qt::Key>::const_iterator it = keys.begin(), end = keys.end();
	QString res = keyString(*it);

	const int maxDisplayedKeys = 6;
	int nbDisplayedKeys = 0;
	Qt::Key previousKey = (*it);
	int state = 0;
	++it;
	while ((it != end) && (nbDisplayedKeys < maxDisplayedKeys-1))
	{
		switch (state)
		{
			case 0 :
				if ((*it) == previousKey + 1)
					state++;
				else
				{
					res += ", " + keyString(*it);
					nbDisplayedKeys++;
				}
				break;
			case 1 :
				if ((*it) == previousKey + 1)
					state++;
				else
				{
					res += ", " + keyString(previousKey);
					res += ", " + keyString(*it);
					nbDisplayedKeys += 2;
					state = 0;
				}
				break;
			default :
				if ((*it) != previousKey + 1)
				{
					res += ".." + keyString(previousKey);
					res += ", " + keyString(*it);
					nbDisplayedKeys += 2;
					state = 0;
				}
				break;
		}
		previousKey = *it;
		++it;
	}

	if (state == 1)
		res += ", " + keyString(previousKey);
	if (state == 2)
		res += ".." + keyString(previousKey);
	if (it != end)
		res += "...";

	return res;
}

/*! Returns a QString that describes the application keyboard shortcut bindings, and that will be
displayed in the help() window \c Keyboard tab.

Default value is a table that describes the custom shortcuts defined using setKeyDescription() as
well as the \e standard QGLViewer::KeyboardAction shortcuts (defined using setShortcut()). See the
<a href="../keyboard.html">keyboard page</a> for details on key customization.

See also helpString() and mouseString(). */
QString QGLViewer::keyboardString() const
{
	QString text("<center><table border=\"1\" cellspacing=\"0\" cellpadding=\"4\">\n");
	text += QString("<tr bgcolor=\"#aaaacc\"><th align=\"center\">%1</th><th align=\"center\">%2</th></tr>\n").
			arg(QGLViewer::tr("Key(s)", "Keys column header in help window mouse tab")).arg(QGLViewer::tr("Description", "Description column header in help window mouse tab"));

	QMap<unsigned int, QString> keyDescription;

	// 1 - User defined key descriptions
	for (QMap<unsigned int, QString>::ConstIterator kd=keyDescription_.begin(), kdend=keyDescription_.end(); kd!=kdend; ++kd)
		keyDescription[kd.key()] = kd.value();

	// Add to text in sorted order
	for (QMap<unsigned int, QString>::ConstIterator kb=keyDescription.begin(), endb=keyDescription.end(); kb!=endb; ++kb)
		text += tableLine(keyString(kb.key()), kb.value());


	// 2 - Optional separator line
	if (!keyDescription.isEmpty())
	{
		keyDescription.clear();
		text += QString("<tr bgcolor=\"#aaaacc\"><td colspan=2>%1</td></tr>\n").arg(QGLViewer::tr("Standard viewer keys", "In help window keys tab"));
	}


	// 3 - KeyboardAction bindings description
	for (QMap<KeyboardAction, unsigned int>::ConstIterator it=keyboardBinding_.begin(), end=keyboardBinding_.end(); it != end; ++it)
		if ((it.value() != 0) && ((!cameraIsInRotateMode()) || ((it.key() != INCREASE_FLYSPEED) && (it.key() != DECREASE_FLYSPEED))))
			keyDescription[it.value()] = keyboardActionDescription_[it.key()];

	// Add to text in sorted order
	for (QMap<unsigned int, QString>::ConstIterator kb2=keyDescription.begin(), endb2=keyDescription.end(); kb2!=endb2; ++kb2)
		text += tableLine(keyString(kb2.key()), kb2.value());


	// 4 - Camera paths keys description
	const QString cpks = cameraPathKeysString();
	if (!cpks.isNull())
	{
		text += "<tr bgcolor=\"#ccccff\"><td colspan=2>\n";
		text += QGLViewer::tr("Camera paths are controlled using the %1 keys (noted <i>Fx</i> below):", "Help window key tab camera keys").arg(cpks) + "</td></tr>\n";
		text += tableLine(keyString(playPathKeyboardModifiers()) + "<i>" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "</i>",
						  QGLViewer::tr("Plays path (or resets saved position)"));
		text += tableLine(keyString(addKeyFrameKeyboardModifiers()) + "<i>" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "</i>",
						  QGLViewer::tr("Adds a key frame to path (or defines a position)"));
		text += tableLine(keyString(addKeyFrameKeyboardModifiers()) + "<i>" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "</i>+<i>" + QGLViewer::tr("Fx", "Generic function key (F1..F12)") + "</i>",
						  QGLViewer::tr("Deletes path (or saved position)"));
	}
	text += "</table></center>";

	return text;
}

/*! Displays the help window "About" tab. See help() for details. */
void QGLViewer::aboutQGLViewer() {
	help();
	helpWidget()->setCurrentIndex(3);
}


/*! Opens a modal help window that includes four tabs, respectively filled with helpString(),
keyboardString(), mouseString() and about libQGLViewer.

Rich html-like text can be used (see the QStyleSheet documentation). This method is called when the
user presses the QGLViewer::HELP key (default is 'H').

You can use helpWidget() to access to the help widget (to add/remove tabs, change layout...).

The helpRequired() signal is emitted. */
void QGLViewer::help()
{
	Q_EMIT helpRequired();

	bool resize = false;
	int width=600;
	int height=400;

	static QString label[] = {tr("&Help", "Help window tab title"), tr("&Keyboard", "Help window tab title"), tr("&Mouse", "Help window tab title"), tr("&About", "Help window about title")};

	if (!helpWidget())
	{
		// Qt4 requires a NULL parent...
		helpWidget_ = new QTabWidget(NULL);
		helpWidget()->setWindowTitle(tr("Help", "Help window title"));

		resize = true;
		for (int i=0; i<4; ++i)
		{
			QTextEdit* tab = new QTextEdit(NULL);
			tab->setReadOnly(true);

			helpWidget()->insertTab(i, tab, label[i]);
			if (i==3) {
#               include "qglviewer-icon.xpm"
				QPixmap pixmap(qglviewer_icon);
				tab->document()->addResource(QTextDocument::ImageResource,
											 QUrl("mydata://qglviewer-icon.xpm"), QVariant(pixmap));
			}
		}
	}

	for (int i=0; i<4; ++i)
	{
		QString text;
		switch (i)
		{
			case 0 : text = helpString();	  break;
			case 1 : text = keyboardString(); break;
			case 2 : text = mouseString();	  break;
			case 3 : text = QString("<center><br><img src=\"mydata://qglviewer-icon.xpm\">") + tr(
							"<h1>libQGLViewer</h1>"
							"<h3>Version %1</h3><br>"
							"A versatile 3D viewer based on OpenGL and Qt<br>"
							"Copyright 2002-%2 Gilles Debunne<br>"
							"<code>%3</code>").arg(QGLViewerVersionString()).arg("2014").arg("http://www.libqglviewer.com") +
						QString("</center>");
				break;
			default : break;
		}

		QTextEdit* textEdit = (QTextEdit*)(helpWidget()->widget(i));
		textEdit->setHtml(text);
		textEdit->setText(text);

		if (resize && (textEdit->height() > height))
			height = textEdit->height();
	}

	if (resize)
		helpWidget()->resize(width, height+40); // 40 pixels is ~ tabs' height
	helpWidget()->show();
	helpWidget()->raise();
}

/*! Overloading of the \c QWidget method.

Default keyboard shortcuts are defined using setShortcut(). Overload this method to implement a
specific keyboard binding. Call the original method if you do not catch the event to preserve the
viewer default key bindings:
\code
void Viewer::keyPressEvent(QKeyEvent *e)
{
  // Defines the Alt+R shortcut.
  if ((e->key() == Qt::Key_R) && (e->modifiers() == Qt::AltModifier))
  {
	myResetFunction();
	update(); // Refresh display
  }
  else
	QGLViewer::keyPressEvent(e);
}

// With Qt 2 or 3, you would retrieve modifiers keys using :
// const Qt::ButtonState modifiers = (Qt::ButtonState)(e->state() & Qt::KeyButtonMask);
\endcode
When you define a new keyboard shortcut, use setKeyDescription() to provide a short description
which is displayed in the help() window Keyboard tab. See the <a
href="../examples/keyboardAndMouse.html">keyboardAndMouse</a> example for an illustration.

See also QGLWidget::keyReleaseEvent(). */
void QGLViewer::keyPressEvent(QKeyEvent *e)
{
	if (e->key() == 0)
	{
		e->ignore();
		return;
	}

	const Qt::Key key = Qt::Key(e->key());

	const Qt::KeyboardModifiers modifiers = e->modifiers();

	QMap<KeyboardAction, unsigned int>::ConstIterator it=keyboardBinding_.begin(), end=keyboardBinding_.end();
	const unsigned int target = key | modifiers;
	while ((it != end) && (it.value() != target))
		++it;

	if (it != end)
		handleKeyboardAction(it.key());
	else
		if (pathIndex_.contains(Qt::Key(key)))
		{
			// Camera paths
			unsigned int index = pathIndex_[Qt::Key(key)];

			// not safe, but try to double press on two viewers at the same time !
			static QTime doublePress;

			if (modifiers == playPathKeyboardModifiers())
			{
				int elapsed = doublePress.restart();
				if ((elapsed < 250) && (index==previousPathId_))
					camera()->resetPath(index);
				else
				{
					// Stop previous interpolation before starting a new one.
					if (index != previousPathId_)
					{
						KeyFrameInterpolator* previous = camera()->keyFrameInterpolator(previousPathId_);
						if ((previous) && (previous->interpolationIsStarted()))
							previous->resetInterpolation();
					}
					camera()->playPath(index);
				}
				previousPathId_ = index;
			}
			else if (modifiers == addKeyFrameKeyboardModifiers())
			{
				int elapsed = doublePress.restart();
				if ((elapsed < 250) && (index==previousPathId_))
				{
					if (camera()->keyFrameInterpolator(index))
					{
						disconnect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), this, SLOT(update()));
						if (camera()->keyFrameInterpolator(index)->numberOfKeyFrames() > 1)
							displayMessage(tr("Path %1 deleted", "Feedback message").arg(index));
						else
							displayMessage(tr("Position %1 deleted", "Feedback message").arg(index));
						camera()->deletePath(index);
					}
				}
				else
				{
					bool nullBefore = (camera()->keyFrameInterpolator(index) == NULL);
					camera()->addKeyFrameToPath(index);
					if (nullBefore)
						connect(camera()->keyFrameInterpolator(index), SIGNAL(interpolated()), SLOT(update()));
					int nbKF = camera()->keyFrameInterpolator(index)->numberOfKeyFrames();
					if (nbKF > 1)
						displayMessage(tr("Path %1, position %2 added", "Feedback message").arg(index).arg(nbKF));
					else
						displayMessage(tr("Position %1 saved", "Feedback message").arg(index));
				}
				previousPathId_ = index;
			}
			update();
		} else {
			if (isValidShortcutKey(key)) currentlyPressedKey_ = key;
			e->ignore();
		}
}

void QGLViewer::keyReleaseEvent(QKeyEvent * e) {
	if (isValidShortcutKey(e->key())) currentlyPressedKey_ = Qt::Key(0);
}

void QGLViewer::handleKeyboardAction(KeyboardAction id)
{
	switch (id)
	{
		case DRAW_AXIS :		toggleAxisIsDrawn(); break;
		case DRAW_GRID :		toggleGridIsDrawn(); break;
		case DISPLAY_FPS :		toggleFPSIsDisplayed(); break;
		case ENABLE_TEXT :		toggleTextIsEnabled(); break;
		case EXIT_VIEWER :		saveStateToFileForAllViewers(); qApp->closeAllWindows(); break;
		case SAVE_SCREENSHOT :	saveSnapshot(false, false); break;
		case FULL_SCREEN :		toggleFullScreen(); break;
		case STEREO :			toggleStereoDisplay(); break;
		case ANIMATION :		toggleAnimation(); break;
		case HELP :				help(); break;
		case EDIT_CAMERA :		toggleCameraIsEdited(); break;
		case SNAPSHOT_TO_CLIPBOARD :	snapshotToClipboard(); break;
		case CAMERA_MODE :
			toggleCameraMode();
			displayMessage(cameraIsInRotateMode()?tr("Camera in observer mode", "Feedback message"):tr("Camera in fly mode", "Feedback message"));
			break;

		case MOVE_CAMERA_LEFT :
			camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(-10.0*camera()->flySpeed(), 0.0, 0.0)));
			update();
			break;
		case MOVE_CAMERA_RIGHT :
			camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec( 10.0*camera()->flySpeed(), 0.0, 0.0)));
			update();
			break;
		case MOVE_CAMERA_UP :
			camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(0.0, 10.0*camera()->flySpeed(), 0.0)));
			update();
			break;
		case MOVE_CAMERA_DOWN :
			camera()->frame()->translate(camera()->frame()->inverseTransformOf(Vec(0.0, -10.0*camera()->flySpeed(), 0.0)));
			update();
			break;

		case INCREASE_FLYSPEED : camera()->setFlySpeed(camera()->flySpeed() * 1.5); break;
		case DECREASE_FLYSPEED : camera()->setFlySpeed(camera()->flySpeed() / 1.5); break;
	}
}

/*! Callback method used when the widget size is modified.

If you overload this method, first call the inherited method. Also called when the widget is
created, before its first display. */
void QGLViewer::resizeGL(int width, int height)
{
    QOpenGLWidget::resizeGL(width, height);
	glViewport( 0, 0, GLint(width), GLint(height) );
	camera()->setScreenWidthAndHeight(this->width(), this->height());
}

//////////////////////////////////////////////////////////////////////////
//              K e y b o a r d   s h o r t c u t s                     //
//////////////////////////////////////////////////////////////////////////

/*! Defines the shortcut() that triggers a given QGLViewer::KeyboardAction.

Here are some examples:
\code
// Press 'Q' to exit application
setShortcut(EXIT_VIEWER, Qt::Key_Q);

// Alt+M toggles camera mode
setShortcut(CAMERA_MODE, Qt::ALT + Qt::Key_M);

// The DISPLAY_FPS action is disabled
setShortcut(DISPLAY_FPS, 0);
\endcode

Only one shortcut can be assigned to a given QGLViewer::KeyboardAction (new bindings replace
previous ones). If several KeyboardAction are binded to the same shortcut, only one of them is
active. */
void QGLViewer::setShortcut(KeyboardAction action, unsigned int key)
{
	keyboardBinding_[action] = key;
}

/*! Returns the keyboard shortcut associated to a given QGLViewer::KeyboardAction.

Result is an \c unsigned \c int defined using Qt enumerated values, as in \c Qt::Key_Q or
\c Qt::CTRL + Qt::Key_X. Use Qt::MODIFIER_MASK to separate the key from the state keys. Returns \c 0 if
the KeyboardAction is disabled (not binded). Set using setShortcut().

If you want to define keyboard shortcuts for custom actions (say, open a scene file), overload
keyPressEvent() and then setKeyDescription().

These shortcuts and their descriptions are automatically included in the help() window \c Keyboard
tab.

See the <a href="../keyboard.html">keyboard page</a> for details and default values and the <a
href="../examples/keyboardAndMouse.html">keyboardAndMouse</a> example for a practical
illustration. */
unsigned int QGLViewer::shortcut(KeyboardAction action) const
{
	if (keyboardBinding_.contains(action))
		return keyboardBinding_[action];
	else
		return 0;
}

#ifndef DOXYGEN
void QGLViewer::setKeyboardAccelerator(KeyboardAction action, unsigned int key)
{
	qWarning("setKeyboardAccelerator is deprecated. Use setShortcut instead.");
	setShortcut(action, key);
}

unsigned int QGLViewer::keyboardAccelerator(KeyboardAction action) const
{
	qWarning("keyboardAccelerator is deprecated. Use shortcut instead.");
	return shortcut(action);
}
#endif

///////     Key Frames associated keys       ///////

/*! Returns the keyboard key associated to camera Key Frame path \p index.

Default values are F1..F12 for indexes 1..12.

addKeyFrameKeyboardModifiers() (resp. playPathKeyboardModifiers()) define the state key(s) that
must be pressed with this key to add a KeyFrame to (resp. to play) the associated Key Frame path.
If you quickly press twice the pathKey(), the path is reset (resp. deleted).

Use camera()->keyFrameInterpolator( \p index ) to retrieve the KeyFrameInterpolator that defines
the path.

If several keys are binded to a given \p index (see setPathKey()), one of them is returned.
Returns \c 0 if no key is associated with this index.

See also the <a href="../keyboard.html">keyboard page</a>. */
Qt::Key QGLViewer::pathKey(unsigned int index) const
{
	for (QMap<Qt::Key, unsigned int>::ConstIterator it = pathIndex_.begin(), end=pathIndex_.end(); it != end; ++it)
		if (it.value() == index)
			return it.key();
	return Qt::Key(0);
}

/*! Sets the pathKey() associated with the camera Key Frame path \p index.

Several keys can be binded to the same \p index. Use a negated \p key value to delete the binding
(the \p index value is then ignored):
\code
// Press 'space' to play/pause/add/delete camera path of index 0.
setPathKey(Qt::Key_Space, 0);

// Remove this binding
setPathKey(-Qt::Key_Space);
\endcode */
void QGLViewer::setPathKey(int key, unsigned int index)
{
	Qt::Key k = Qt::Key(abs(key));
	if (key < 0)
		pathIndex_.remove(k);
	else
		pathIndex_[k] = index;
}

/*! Sets the playPathKeyboardModifiers() value. */
void QGLViewer::setPlayPathKeyboardModifiers(Qt::KeyboardModifiers modifiers)
{
	playPathKeyboardModifiers_ = modifiers;
}

/*! Sets the addKeyFrameKeyboardModifiers() value. */
void QGLViewer::setAddKeyFrameKeyboardModifiers(Qt::KeyboardModifiers modifiers)
{
	addKeyFrameKeyboardModifiers_ = modifiers;
}

/*! Returns the keyboard modifiers that must be pressed with a pathKey() to add the current camera
position to a KeyFrame path.

It can be \c Qt::NoModifier, \c Qt::ControlModifier, \c Qt::ShiftModifier, \c Qt::AltModifier, \c
Qt::MetaModifier or a combination of these (using the bitwise '|' operator).

Default value is Qt::AltModifier. Defined using setAddKeyFrameKeyboardModifiers().

See also playPathKeyboardModifiers(). */
Qt::KeyboardModifiers QGLViewer::addKeyFrameKeyboardModifiers() const
{
	return addKeyFrameKeyboardModifiers_;
}

/*! Returns the keyboard modifiers that must be pressed with a pathKey() to play a camera KeyFrame path.

It can be \c Qt::NoModifier, \c Qt::ControlModifier, \c Qt::ShiftModifier, \c Qt::AltModifier, \c
Qt::MetaModifier or a combination of these (using the bitwise '|' operator).

Default value is Qt::NoModifier. Defined using setPlayPathKeyboardModifiers().

See also addKeyFrameKeyboardModifiers(). */
Qt::KeyboardModifiers QGLViewer::playPathKeyboardModifiers() const
{
	return playPathKeyboardModifiers_;
}

#ifndef DOXYGEN
// Deprecated methods
Qt::KeyboardModifiers QGLViewer::addKeyFrameStateKey() const
{
	qWarning("addKeyFrameStateKey has been renamed addKeyFrameKeyboardModifiers");
	return addKeyFrameKeyboardModifiers(); }

Qt::KeyboardModifiers QGLViewer::playPathStateKey() const
{
	qWarning("playPathStateKey has been renamed playPathKeyboardModifiers");
	return playPathKeyboardModifiers();
}

void QGLViewer::setAddKeyFrameStateKey(unsigned int buttonState)
{
	qWarning("setAddKeyFrameStateKey has been renamed setAddKeyFrameKeyboardModifiers");
	setAddKeyFrameKeyboardModifiers(keyboardModifiersFromState(buttonState));
}

void QGLViewer::setPlayPathStateKey(unsigned int buttonState)
{
	qWarning("setPlayPathStateKey has been renamed setPlayPathKeyboardModifiers");
	setPlayPathKeyboardModifiers(keyboardModifiersFromState(buttonState));
}

Qt::Key QGLViewer::keyFrameKey(unsigned int index) const
{
	qWarning("keyFrameKey has been renamed pathKey.");
	return pathKey(index);
}

Qt::KeyboardModifiers QGLViewer::playKeyFramePathStateKey() const
{
	qWarning("playKeyFramePathStateKey has been renamed playPathKeyboardModifiers.");
	return playPathKeyboardModifiers();
}

void QGLViewer::setKeyFrameKey(unsigned int index, int key)
{
	qWarning("setKeyFrameKey is deprecated, use setPathKey instead, with swapped parameters.");
	setPathKey(key, index);
}

void QGLViewer::setPlayKeyFramePathStateKey(unsigned int buttonState)
{
	qWarning("setPlayKeyFramePathStateKey has been renamed setPlayPathKeyboardModifiers.");
	setPlayPathKeyboardModifiers(keyboardModifiersFromState(buttonState));
}
#endif

////////////////////////////////////////////////////////////////////////////////
//              M o u s e   b e h a v i o r   s t a t e   k e y s             //
////////////////////////////////////////////////////////////////////////////////
#ifndef DOXYGEN
/*! This method has been deprecated since version 2.5.0

Associates keyboard modifiers to MouseHandler \p handler.

The \p modifiers parameter is \c Qt::AltModifier, \c Qt::ShiftModifier, \c Qt::ControlModifier, \c
Qt::MetaModifier or a combination of these using the '|' bitwise operator.

\e All the \p handler's associated bindings will then need the specified \p modifiers key(s) to be
activated.

With this code,
\code
setHandlerKeyboardModifiers(QGLViewer::CAMERA, Qt::AltModifier);
setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::NoModifier);
\endcode
you will have to press the \c Alt key while pressing mouse buttons in order to move the camera(),
while no key will be needed to move the manipulatedFrame().

This method has a very basic implementation: every action binded to \p handler has its keyboard
modifier replaced by \p modifiers. If \p handler had some actions binded to different modifiers,
these settings will be lost. You should hence consider using setMouseBinding() for finer tuning.

The default binding associates \c Qt::ControlModifier to all the QGLViewer::FRAME actions and \c
Qt::NoModifier to all QGLViewer::CAMERA actions. See <a href="../mouse.html">mouse page</a> for
details.

\attention This method calls setMouseBinding(), which ensures that only one action is binded to a
given modifiers. If you want to \e swap the QGLViewer::CAMERA and QGLViewer::FRAME keyboard
modifiers, you have to use a temporary dummy modifier (as if you were swapping two variables) or
else the first call will overwrite the previous settings:
\code
// Associate FRAME with Alt (temporary value)
setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::AltModifier);
// Control is associated with CAMERA
setHandlerKeyboardModifiers(QGLViewer::CAMERA, Qt::ControlModifier);
// And finally, FRAME can be associated with NoModifier
setHandlerKeyboardModifiers(QGLViewer::FRAME, Qt::NoModifier);
\endcode */
void QGLViewer::setHandlerKeyboardModifiers(MouseHandler handler, Qt::KeyboardModifiers modifiers)
{
	qWarning("setHandlerKeyboardModifiers is deprecated, call setMouseBinding() instead");

	QMap<MouseBindingPrivate, MouseActionPrivate> newMouseBinding;
	QMap<WheelBindingPrivate, MouseActionPrivate> newWheelBinding;
	QMap<ClickBindingPrivate, ClickAction> newClickBinding_;

	QMap<MouseBindingPrivate, MouseActionPrivate>::Iterator mit;
	QMap<WheelBindingPrivate, MouseActionPrivate>::Iterator wit;

	// First copy unchanged bindings.
	for (mit = mouseBinding_.begin(); mit != mouseBinding_.end(); ++mit)
		if ((mit.value().handler != handler) || (mit.value().action == ZOOM_ON_REGION))
			newMouseBinding[mit.key()] = mit.value();

	for (wit = wheelBinding_.begin(); wit != wheelBinding_.end(); ++wit)
		if (wit.value().handler != handler)
			newWheelBinding[wit.key()] = wit.value();

	// Then, add modified bindings, that can overwrite the previous ones.
	for (mit = mouseBinding_.begin(); mit != mouseBinding_.end(); ++mit)
		if ((mit.value().handler == handler) && (mit.value().action != ZOOM_ON_REGION))
		{
			MouseBindingPrivate mbp(modifiers, mit.key().button, mit.key().key);
			newMouseBinding[mbp] = mit.value();
		}

	for (wit = wheelBinding_.begin(); wit != wheelBinding_.end(); ++wit)
		if (wit.value().handler == handler)
		{
			WheelBindingPrivate wbp(modifiers, wit.key().key);
			newWheelBinding[wbp] = wit.value();
		}

	// Same for button bindings
	for (QMap<ClickBindingPrivate, ClickAction>::ConstIterator cb=clickBinding_.begin(), end=clickBinding_.end(); cb != end; ++cb)
		if (((handler==CAMERA) && ((cb.value() == CENTER_SCENE) || (cb.value() == ALIGN_CAMERA))) ||
			((handler==FRAME)  && ((cb.value() == CENTER_FRAME) || (cb.value() == ALIGN_FRAME))))
		{
			ClickBindingPrivate cbp(modifiers, cb.key().button, cb.key().doubleClick, cb.key().buttonsBefore, cb.key().key);
			newClickBinding_[cbp] = cb.value();
		}
		else
			newClickBinding_[cb.key()] = cb.value();

	mouseBinding_ = newMouseBinding;
	wheelBinding_ = newWheelBinding;
	clickBinding_ = newClickBinding_;
}

void QGLViewer::setHandlerStateKey(MouseHandler handler, unsigned int buttonState)
{
	qWarning("setHandlerStateKey has been renamed setHandlerKeyboardModifiers");
	setHandlerKeyboardModifiers(handler, keyboardModifiersFromState(buttonState));
}

void QGLViewer::setMouseStateKey(MouseHandler handler, unsigned int buttonState)
{
	qWarning("setMouseStateKey has been renamed setHandlerKeyboardModifiers.");
	setHandlerKeyboardModifiers(handler, keyboardModifiersFromState(buttonState));
}

/*! This method is deprecated since version 2.5.0

 Use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool) instead.
*/
void QGLViewer::setMouseBinding(unsigned int state, MouseHandler handler, MouseAction action, bool withConstraint)
{
	qWarning("setMouseBinding(int state, MouseHandler...) is deprecated. Use the modifier/button equivalent");
	setMouseBinding(keyboardModifiersFromState(state),
					mouseButtonFromState(state),
					handler,
					action,
					withConstraint);
}
#endif

/*! Defines a MouseAction binding.

  Same as calling setMouseBinding(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, MouseHandler, MouseAction, bool),
  with a key value of Qt::Key(0) (i.e. no regular extra key needs to be pressed to perform this action). */
void QGLViewer::setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, MouseHandler handler, MouseAction action, bool withConstraint) {
	setMouseBinding(Qt::Key(0), modifiers, button, handler, action, withConstraint);
}

/*! Associates a MouseAction to any mouse \p button, while keyboard \p modifiers and \p key are pressed.
The receiver of the mouse events is a MouseHandler (QGLViewer::CAMERA or QGLViewer::FRAME).

The parameters should read: when the mouse \p button is pressed, while the keyboard \p modifiers and \p key are down,
activate \p action on \p handler. Use Qt::NoModifier to indicate that no modifier
key is needed, and a \p key value of 0 if no regular key has to be pressed
(or simply use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButton, MouseHandler, MouseAction, bool)).

Use the '|' operator to combine modifiers:
\code
// The R key combined with the Left mouse button rotates the camera in the screen plane.
setMouseBinding(Qt::Key_R, Qt::NoModifier, Qt::LeftButton, CAMERA, SCREEN_ROTATE);

// Alt + Shift and Left button rotates the manipulatedFrame().
setMouseBinding(Qt::AltModifier | Qt::ShiftModifier, Qt::LeftButton, FRAME, ROTATE);
\endcode

If \p withConstraint is \c true (default), the possible
qglviewer::Frame::constraint() of the associated Frame will be enforced during motion.

The list of all possible MouseAction, some binding examples and default bindings are provided in
the <a href="../mouse.html">mouse page</a>.

See the <a href="../examples/keyboardAndMouse.html">keyboardAndMouse</a> example for an illustration.

If no mouse button is specified, the binding is ignored. If an action was previously
associated with this keyboard and button combination, it is silently overwritten (call mouseAction()
before to check).

To remove a specific mouse binding, use \p NO_MOUSE_ACTION as the \p action.

See also setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, ClickAction, bool, int), setWheelBinding() and clearMouseBindings(). */
void QGLViewer::setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, MouseHandler handler, MouseAction action, bool withConstraint)
{
	if ((handler == FRAME) && ((action == MOVE_FORWARD) || (action == MOVE_BACKWARD) ||
							   (action == ROLL) || (action == LOOK_AROUND) ||
							   (action == ZOOM_ON_REGION)))	{
		qWarning("Cannot bind %s to FRAME", mouseActionString(action).toLatin1().constData());
		return;
	}

	if (button == Qt::NoButton) {
		qWarning("No mouse button specified in setMouseBinding");
		return;
	}

	MouseActionPrivate map;
	map.handler = handler;
	map.action = action;
	map.withConstraint = withConstraint;

	MouseBindingPrivate mbp(modifiers, button, key);
	if (action == NO_MOUSE_ACTION)
		mouseBinding_.remove(mbp);
	else
		mouseBinding_.insert(mbp, map);

	ClickBindingPrivate cbp(modifiers, button, false, Qt::NoButton, key);
	clickBinding_.remove(cbp);
}

#ifndef DOXYGEN
/*! This method is deprecated since version 2.5.0

 Use setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool) instead.
*/
void QGLViewer::setMouseBinding(unsigned int state, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore) {
	qWarning("setMouseBinding(int state, ClickAction...) is deprecated. Use the modifier/button equivalent");
	setMouseBinding(keyboardModifiersFromState(state),
					mouseButtonFromState(state),
					action,
					doubleClick,
					buttonsBefore);
}
#endif

/*! Defines a ClickAction binding.

 Same as calling setMouseBinding(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, ClickAction, bool, Qt::MouseButtons),
 with a key value of Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */
void QGLViewer::setMouseBinding(Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore)
{
	setMouseBinding(Qt::Key(0), modifiers, button, action, doubleClick, buttonsBefore);
}

/*! Associates a ClickAction to a button and keyboard key and modifier(s) combination.

The parameters should read: when \p button is pressed, while the \p modifiers and \p key keys are down,
and possibly as a \p doubleClick, then perform \p action. Use Qt::NoModifier to indicate that no modifier
key is needed, and a \p key value of 0 if no regular key has to be pressed (or simply use
setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButton, ClickAction, bool, Qt::MouseButtons)).

If \p buttonsBefore is specified (valid only when \p doubleClick is \c true), then this (or these) other mouse
button(s) has (have) to be pressed \e before the double click occurs in order to execute \p action.

The list of all possible ClickAction, some binding examples and default bindings are listed in the
<a href="../mouse.html">mouse page</a>. See also the setMouseBinding() documentation.

See the <a href="../examples/keyboardAndMouse.html">keyboardAndMouse example</a> for an
illustration.

The binding is ignored if Qt::NoButton is specified as \p buttons.

See also setMouseBinding(Qt::KeyboardModifiers, Qt::MouseButtons, MouseHandler, MouseAction, bool), setWheelBinding() and clearMouseBindings().
*/
void QGLViewer::setMouseBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button, ClickAction action, bool doubleClick, Qt::MouseButtons buttonsBefore)
{
	if ((buttonsBefore != Qt::NoButton) && !doubleClick) {
		qWarning("Buttons before is only meaningful when doubleClick is true in setMouseBinding().");
		return;
	}

	if (button == Qt::NoButton) {
		qWarning("No mouse button specified in setMouseBinding");
		return;
	}

	ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key);

	// #CONNECTION performClickAction comment on NO_CLICK_ACTION
	if (action == NO_CLICK_ACTION)
		clickBinding_.remove(cbp);
	else
		clickBinding_.insert(cbp, action);

	if ((!doubleClick) && (buttonsBefore == Qt::NoButton)) {
		MouseBindingPrivate mbp(modifiers, button, key);
		mouseBinding_.remove(mbp);
	}
}

/*! Defines a mouse wheel binding.

 Same as calling setWheelBinding(Qt::Key, Qt::KeyboardModifiers, MouseHandler, MouseAction, bool),
 with a key value of Qt::Key(0) (i.e. no regular key needs to be pressed to activate this action). */
void QGLViewer::setWheelBinding(Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint) {
	setWheelBinding(Qt::Key(0), modifiers, handler, action, withConstraint);
}

/*! Associates a MouseAction and a MouseHandler to a mouse wheel event.

This method is very similar to setMouseBinding(), but specific to the wheel.

In the current implementation only QGLViewer::ZOOM can be associated with QGLViewer::FRAME, while
QGLViewer::CAMERA can receive QGLViewer::ZOOM and QGLViewer::MOVE_FORWARD.

The difference between QGLViewer::ZOOM and QGLViewer::MOVE_FORWARD is that QGLViewer::ZOOM speed
depends on the distance to the object, while QGLViewer::MOVE_FORWARD moves at a constant speed
defined by qglviewer::Camera::flySpeed(). */
void QGLViewer::setWheelBinding(Qt::Key key, Qt::KeyboardModifiers modifiers, MouseHandler handler, MouseAction action, bool withConstraint)
{
	//#CONNECTION# ManipulatedFrame::wheelEvent and ManipulatedCameraFrame::wheelEvent switches
	if ((action != ZOOM) && (action != MOVE_FORWARD) && (action != MOVE_BACKWARD) && (action != NO_MOUSE_ACTION)) {
		qWarning("Cannot bind %s to wheel", mouseActionString(action).toLatin1().constData());
		return;
	}

	if ((handler == FRAME) && (action != ZOOM) && (action != NO_MOUSE_ACTION)) {
		qWarning("Cannot bind %s to FRAME wheel", mouseActionString(action).toLatin1().constData());
		return;
	}

	MouseActionPrivate map;
	map.handler = handler;
	map.action  = action;
	map.withConstraint = withConstraint;

	WheelBindingPrivate wbp(modifiers, key);
	if (action == NO_MOUSE_ACTION)
		wheelBinding_.remove(wbp);
	else
		wheelBinding_[wbp] = map;
}

/*! Clears all the default mouse bindings.

After this call, you will have to use setMouseBinding() and setWheelBinding() to restore the mouse bindings you are interested in.
*/
void QGLViewer::clearMouseBindings() {
	mouseBinding_.clear();
	clickBinding_.clear();
	wheelBinding_.clear();
}

/*! Clears all the default keyboard shortcuts.

After this call, you will have to use setShortcut() to define your own keyboard shortcuts.
*/
void QGLViewer::clearShortcuts() {
	keyboardBinding_.clear();
	pathIndex_.clear();
}

/*! This method is deprecated since version 2.5.0

 Use mouseAction(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButtons) instead.
*/
QGLViewer::MouseAction QGLViewer::mouseAction(unsigned int state) const {
	qWarning("mouseAction(int state,...) is deprecated. Use the modifier/button equivalent");
	return mouseAction(Qt::Key(0), keyboardModifiersFromState(state), mouseButtonFromState(state));
}

/*! Returns the MouseAction the will be triggered when the mouse \p button is pressed,
while the keyboard \p modifiers and \p key are pressed.

Returns QGLViewer::NO_MOUSE_ACTION if no action is associated with this combination. Use 0 for \p key
to indicate that no regular key needs to be pressed.

For instance, to know which motion corresponds to Alt+LeftButton, do:
\code
QGLViewer::MouseAction ma = mouseAction(0, Qt::AltModifier, Qt::LeftButton);
if (ma != QGLViewer::NO_MOUSE_ACTION) ...
\endcode

Use mouseHandler() to know which object (QGLViewer::CAMERA or QGLViewer::FRAME) will execute this
action. */
QGLViewer::MouseAction QGLViewer::mouseAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const
{
	MouseBindingPrivate mbp(modifiers, button, key);
	if (mouseBinding_.contains(mbp))
		return mouseBinding_[mbp].action;
	else
		return NO_MOUSE_ACTION;
}

/*! This method is deprecated since version 2.5.0

 Use mouseHanler(Qt::Key, Qt::KeyboardModifiers, Qt::MouseButtons) instead.
*/
int QGLViewer::mouseHandler(unsigned int state) const {
	qWarning("mouseHandler(int state,...) is deprecated. Use the modifier/button equivalent");
	return mouseHandler(Qt::Key(0), keyboardModifiersFromState(state), mouseButtonFromState(state));
}

/*! Returns the MouseHandler which will be activated when the mouse \p button is pressed, while the \p modifiers and \p key are pressed.

If no action is associated with this combination, returns \c -1. Use 0 for \p key and Qt::NoModifier for \p modifiers
to represent the lack of a key press.

For instance, to know which handler receives the Alt+LeftButton, do:
\code
int mh = mouseHandler(0, Qt::AltModifier, Qt::LeftButton);
if (mh == QGLViewer::CAMERA) ...
\endcode

Use mouseAction() to know which action (see the MouseAction enum) will be performed on this handler. */
int QGLViewer::mouseHandler(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button) const
{
	MouseBindingPrivate mbp(modifiers, button, key);
	if (mouseBinding_.contains(mbp))
		return mouseBinding_[mbp].handler;
	else
		return -1;
}


#ifndef DOXYGEN
/*! This method is deprecated since version 2.5.0

 Use mouseButtons() and keyboardModifiers() instead.
*/
int QGLViewer::mouseButtonState(MouseHandler handler, MouseAction action, bool withConstraint) const {
	qWarning("mouseButtonState() is deprecated. Use mouseButtons() and keyboardModifiers() instead");
	for (QMap<MouseBindingPrivate, MouseActionPrivate>::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it != end; ++it)
		if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) )
			return (int) it.key().modifiers | (int) it.key().button;

	return Qt::NoButton;
}
#endif

/*! Returns the keyboard state that triggers \p action on \p handler \p withConstraint using the mouse wheel.

If such a binding exists, results are stored in the \p key and \p modifiers
parameters. If the MouseAction \p action is not bound, \p key is set to the illegal -1 value.
If several keyboard states trigger the MouseAction, one of them is returned.

See also setMouseBinding(), getClickActionBinding() and getMouseActionBinding(). */
void QGLViewer::getWheelActionBinding(MouseHandler handler, MouseAction action, bool withConstraint,
									  Qt::Key& key, Qt::KeyboardModifiers& modifiers) const
{
	for (QMap<WheelBindingPrivate, MouseActionPrivate>::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it != end; ++it)
		if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) {
			key = it.key().key;
			modifiers = it.key().modifiers;
			return;
		}

	key = Qt::Key(-1);
	modifiers = Qt::NoModifier;
}

/*! Returns the mouse and keyboard state that triggers \p action on \p handler \p withConstraint.

If such a binding exists, results are stored in the \p key, \p modifiers and \p button
parameters. If the MouseAction \p action is not bound, \p button is set to \c Qt::NoButton.
If several mouse and keyboard states trigger the MouseAction, one of them is returned.

See also setMouseBinding(), getClickActionBinding() and getWheelActionBinding(). */
void QGLViewer::getMouseActionBinding(MouseHandler handler, MouseAction action, bool withConstraint,
									  Qt::Key& key, Qt::KeyboardModifiers& modifiers, Qt::MouseButton& button) const
{
	for (QMap<MouseBindingPrivate, MouseActionPrivate>::ConstIterator it=mouseBinding_.begin(), end=mouseBinding_.end(); it != end; ++it) {
		if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) ) {
			key = it.key().key;
			modifiers = it.key().modifiers;
			button = it.key().button;
			return;
		}
	}

	key = Qt::Key(0);
	modifiers = Qt::NoModifier;
	button = Qt::NoButton;
}

/*! Returns the MouseAction (if any) that is performed when using the wheel, when the \p modifiers and \p key keyboard keys are pressed.

Returns NO_MOUSE_ACTION if no such binding has been defined using setWheelBinding().

Same as mouseAction(), but for the wheel action. See also wheelHandler().
*/
QGLViewer::MouseAction QGLViewer::wheelAction(Qt::Key key, Qt::KeyboardModifiers modifiers) const
{
	WheelBindingPrivate wbp(modifiers, key);
	if (wheelBinding_.contains(wbp))
		return wheelBinding_[wbp].action;
	else
		return NO_MOUSE_ACTION;
}

/*! Returns the MouseHandler (if any) that receives wheel events when the \p modifiers and \p key keyboard keys are pressed.

  Returns -1 if no no such binding has been defined using setWheelBinding(). See also wheelAction().
*/
int QGLViewer::wheelHandler(Qt::Key key, Qt::KeyboardModifiers modifiers) const
{
	WheelBindingPrivate wbp(modifiers, key);
	if (wheelBinding_.contains(wbp))
		return wheelBinding_[wbp].handler;
	else
		return -1;
}

/*! Same as mouseAction(), but for the ClickAction set using setMouseBinding().

Returns NO_CLICK_ACTION if no click action is associated with this keyboard and mouse buttons combination. */
QGLViewer::ClickAction QGLViewer::clickAction(Qt::Key key, Qt::KeyboardModifiers modifiers, Qt::MouseButton button,
											  bool doubleClick, Qt::MouseButtons buttonsBefore) const {
	ClickBindingPrivate cbp(modifiers, button, doubleClick, buttonsBefore, key);
	if (clickBinding_.contains(cbp))
		return clickBinding_[cbp];
	else
		return NO_CLICK_ACTION;
}

#ifndef DOXYGEN
/*! This method is deprecated since version 2.5.0

	Use wheelAction(Qt::Key key, Qt::KeyboardModifiers modifiers) instead. */
QGLViewer::MouseAction QGLViewer::wheelAction(Qt::KeyboardModifiers modifiers) const {
	qWarning("wheelAction() is deprecated. Use the new wheelAction() method with a key parameter instead");
	return wheelAction(Qt::Key(0), modifiers);
}

/*! This method is deprecated since version 2.5.0

	Use wheelHandler(Qt::Key key, Qt::KeyboardModifiers modifiers) instead. */
int QGLViewer::wheelHandler(Qt::KeyboardModifiers modifiers) const {
	qWarning("wheelHandler() is deprecated. Use the new wheelHandler() method with a key parameter instead");
	return wheelHandler(Qt::Key(0), modifiers);
}

/*! This method is deprecated since version 2.5.0

  Use wheelAction() and wheelHandler() instead. */
unsigned int QGLViewer::wheelButtonState(MouseHandler handler, MouseAction action, bool withConstraint) const
{
	qWarning("wheelButtonState() is deprecated. Use the wheelAction() and wheelHandler() instead");
	for (QMap<WheelBindingPrivate, MouseActionPrivate>::ConstIterator it=wheelBinding_.begin(), end=wheelBinding_.end(); it!=end; ++it)
		if ( (it.value().handler == handler) && (it.value().action == action) && (it.value().withConstraint == withConstraint) )
			return it.key().key + it.key().modifiers;

	return -1;
}

/*! This method is deprecated since version 2.5.0

 Use clickAction(Qt::KeyboardModifiers, Qt::MouseButtons, bool, Qt::MouseButtons) instead.
*/
QGLViewer::ClickAction QGLViewer::clickAction(unsigned int state, bool doubleClick, Qt::MouseButtons buttonsBefore) const {
	qWarning("clickAction(int state,...) is deprecated. Use the modifier/button equivalent");
	return clickAction(Qt::Key(0),
					   keyboardModifiersFromState(state),
					   mouseButtonFromState(state),
					   doubleClick,
					   buttonsBefore);
}

/*! This method is deprecated since version 2.5.0

 Use getClickActionState(ClickAction, Qt::Key, Qt::KeyboardModifiers, Qt::MouseButton, bool, Qt::MouseButtons) instead.
*/
void QGLViewer::getClickButtonState(ClickAction action, unsigned int& state, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const {
	qWarning("getClickButtonState(int state,...) is deprecated. Use the modifier/button equivalent");
	Qt::KeyboardModifiers modifiers;
	Qt::MouseButton button;
	Qt::Key key;
	getClickActionBinding(action, key, modifiers, button, doubleClick, buttonsBefore);
	state = (unsigned int) modifiers | (unsigned int) button | (unsigned int) key;
}
#endif

/*! Returns the mouse and keyboard state that triggers \p action.

If such a binding exists, results are stored in the \p key, \p modifiers, \p button, \p doubleClick and \p buttonsBefore
parameters. If the ClickAction \p action is not bound, \p button is set to \c Qt::NoButton.
If several mouse buttons trigger in the ClickAction, one of them is returned.

See also setMouseBinding(), getMouseActionBinding() and getWheelActionBinding(). */
void QGLViewer::getClickActionBinding(ClickAction action, Qt::Key& key, Qt::KeyboardModifiers& modifiers, Qt::MouseButton &button, bool& doubleClick, Qt::MouseButtons& buttonsBefore) const
{
	for (QMap<ClickBindingPrivate, ClickAction>::ConstIterator it=clickBinding_.begin(), end=clickBinding_.end(); it != end; ++it)
		if (it.value() == action) {
			modifiers = it.key().modifiers;
			button = it.key().button;
			doubleClick = it.key().doubleClick;
			buttonsBefore = it.key().buttonsBefore;
			key = it.key().key;
			return;
		}

	modifiers = Qt::NoModifier;
	button = Qt::NoButton;
	doubleClick = false;
	buttonsBefore = Qt::NoButton;
	key = Qt::Key(0);
}

/*! This function should be used in conjunction with toggleCameraMode(). It returns \c true when at
least one mouse button is binded to the \c ROTATE mouseAction. This is crude way of determining
which "mode" the camera is in. */
bool QGLViewer::cameraIsInRotateMode() const
{
	//#CONNECTION# used in toggleCameraMode() and keyboardString()
	Qt::Key key;
	Qt::KeyboardModifiers modifiers;
	Qt::MouseButton button;
	getMouseActionBinding(CAMERA, ROTATE, true /*constraint*/, key, modifiers, button);
	return button != Qt::NoButton;
}

/*! Swaps between two predefined camera mouse bindings.

The first mode makes the camera observe the scene while revolving around the
qglviewer::Camera::pivotPoint(). The second mode is designed for walkthrough applications
and simulates a flying camera.

Practically, the three mouse buttons are respectively binded to:
\arg In rotate mode: QGLViewer::ROTATE, QGLViewer::ZOOM, QGLViewer::TRANSLATE.
\arg In fly mode: QGLViewer::MOVE_FORWARD, QGLViewer::LOOK_AROUND, QGLViewer::MOVE_BACKWARD.

The current mode is determined by checking if a mouse button is binded to QGLViewer::ROTATE for
the QGLViewer::CAMERA. The state key that was previously used to move the camera is preserved. */
void QGLViewer::toggleCameraMode()
{
	Qt::Key key;
	Qt::KeyboardModifiers modifiers;
	Qt::MouseButton button;
	getMouseActionBinding(CAMERA, ROTATE, true /*constraint*/, key, modifiers, button);
	bool rotateMode = button != Qt::NoButton;

	if (!rotateMode) {
		getMouseActionBinding(CAMERA, MOVE_FORWARD, true /*constraint*/, key, modifiers, button);
	}

	//#CONNECTION# setDefaultMouseBindings()
	if (rotateMode)
	{
		camera()->frame()->updateSceneUpVector();
		camera()->frame()->stopSpinning();

		setMouseBinding(modifiers, Qt::LeftButton,  CAMERA, MOVE_FORWARD);
		setMouseBinding(modifiers, Qt::MidButton,   CAMERA, LOOK_AROUND);
		setMouseBinding(modifiers, Qt::RightButton, CAMERA, MOVE_BACKWARD);

		setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, CAMERA, ROLL);

		setMouseBinding(Qt::NoModifier, Qt::LeftButton,  NO_CLICK_ACTION, true);
		setMouseBinding(Qt::NoModifier, Qt::MidButton,   NO_CLICK_ACTION, true);
		setMouseBinding(Qt::NoModifier, Qt::RightButton, NO_CLICK_ACTION, true);

		setWheelBinding(modifiers, CAMERA, MOVE_FORWARD);
	}
	else
	{
		// Should stop flyTimer. But unlikely and not easy.
		setMouseBinding(modifiers, Qt::LeftButton,  CAMERA, ROTATE);
		setMouseBinding(modifiers, Qt::MidButton,   CAMERA, ZOOM);
		setMouseBinding(modifiers, Qt::RightButton, CAMERA, TRANSLATE);

		setMouseBinding(Qt::Key_R, modifiers, Qt::LeftButton, CAMERA, SCREEN_ROTATE);

		setMouseBinding(Qt::NoModifier, Qt::LeftButton,  ALIGN_CAMERA,      true);
		setMouseBinding(Qt::NoModifier, Qt::MidButton,   SHOW_ENTIRE_SCENE, true);
		setMouseBinding(Qt::NoModifier, Qt::RightButton, CENTER_SCENE,      true);

		setWheelBinding(modifiers, CAMERA, ZOOM);
	}
}

////////////////////////////////////////////////////////////////////////////////
//              M a n i p u l a t e d   f r a m e s                           //
////////////////////////////////////////////////////////////////////////////////

/*! Sets the viewer's manipulatedFrame().

Several objects can be manipulated simultaneously, as is done the <a
href="../examples/multiSelect.html">multiSelect example</a>.

Defining the \e own viewer's camera()->frame() as the manipulatedFrame() is possible and will result
in a classical camera manipulation. See the <a href="../examples/luxo.html">luxo example</a> for an
illustration.

Note that a qglviewer::ManipulatedCameraFrame can be set as the manipulatedFrame(): it is possible
to manipulate the camera of a first viewer in a second viewer. */
void QGLViewer::setManipulatedFrame(ManipulatedFrame* frame)
{
	if (manipulatedFrame())
	{
		manipulatedFrame()->stopSpinning();

		if (manipulatedFrame() != camera()->frame())
		{
			disconnect(manipulatedFrame(), SIGNAL(manipulated()), this, SLOT(update()));
			disconnect(manipulatedFrame(), SIGNAL(spun()), this, SLOT(update()));
		}
	}

	manipulatedFrame_ = frame;

	manipulatedFrameIsACamera_ = ((manipulatedFrame() != camera()->frame()) &&
								  (dynamic_cast<ManipulatedCameraFrame*>(manipulatedFrame()) != NULL));

	if (manipulatedFrame())
	{
		// Prevent multiple connections, that would result in useless display updates
		if (manipulatedFrame() != camera()->frame())
		{
			connect(manipulatedFrame(), SIGNAL(manipulated()), SLOT(update()));
			connect(manipulatedFrame(), SIGNAL(spun()), SLOT(update()));
		}
	}
}

#ifndef DOXYGEN
////////////////////////////////////////////////////////////////////////////////
//                          V i s u a l   H i n t s                           //
////////////////////////////////////////////////////////////////////////////////
/*! Defines the mask that will be used to drawVisualHints(). The only available mask is currently 1,
corresponding to the display of the qglviewer::Camera::pivotPoint(). resetVisualHints() is
automatically called after \p delay milliseconds (default is 2 seconds). */
void QGLViewer::setVisualHintsMask(int mask, int delay)
{
	visualHint_ = visualHint_ | mask;
	QTimer::singleShot(delay, this, SLOT(resetVisualHints()));
}

/*! Reset the mask used by drawVisualHints(). Called by setVisualHintsMask() after 2 seconds to reset the display. */
void QGLViewer::resetVisualHints()
{
	visualHint_ = 0;
}
#endif

////////////////////////////////////////////////////////////////////////////////
//       S t a t i c    m e t h o d s   :  Q G L V i e w e r   P o o l        //
////////////////////////////////////////////////////////////////////////////////

/*! saveStateToFile() is called on all the QGLViewers using the QGLViewerPool(). */
void QGLViewer::saveStateToFileForAllViewers()
{
	Q_FOREACH (QGLViewer* viewer, QGLViewer::QGLViewerPool())
	{
		if (viewer)
			viewer->saveStateToFile();
	}
}

//////////////////////////////////////////////////////////////////////////
//       S a v e   s t a t e   b e t w e e n    s e s s i o n s         //
//////////////////////////////////////////////////////////////////////////

/*! Returns the state file name. Default value is \c .qglviewer.xml.

This is the name of the XML file where saveStateToFile() saves the viewer state (camera state,
widget geometry, display flags... see domElement()) on exit. Use restoreStateFromFile() to restore
this state later (usually in your init() method).

Setting this value to \c QString::null will disable the automatic state file saving that normally
occurs on exit.

If more than one viewer are created by the application, this function will return a numbered file
name (as in ".qglviewer1.xml", ".qglviewer2.xml"... using QGLViewer::QGLViewerIndex()) for extra
viewers. Each viewer will then read back its own information in restoreStateFromFile(), provided
that the viewers are created in the same order, which is usually the case. */
QString QGLViewer::stateFileName() const
{
	QString name = stateFileName_;

	if (!name.isEmpty() && QGLViewer::QGLViewerIndex(this) > 0)
	{
		QFileInfo fi(name);
		if (fi.suffix().isEmpty())
			name += QString::number(QGLViewer::QGLViewerIndex(this));
		else
			name = fi.absolutePath() + '/' + fi.completeBaseName() + QString::number(QGLViewer::QGLViewerIndex(this)) + "." + fi.suffix();
	}

	return name;
}

/*! Saves in stateFileName() an XML representation of the QGLViewer state, obtained from
domElement().

Use restoreStateFromFile() to restore this viewer state.

This method is automatically called when a viewer is closed (using Escape or using the window's
upper right \c x close button). setStateFileName() to \c QString::null to prevent this. */
void QGLViewer::saveStateToFile()
{
	QString name = stateFileName();

	if (name.isEmpty())
		return;

	QFileInfo fileInfo(name);

	if (fileInfo.isDir())
	{
		QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("State file name (%1) references a directory instead of a file.").arg(name));
		return;
	}

	const QString dirName = fileInfo.absolutePath();
	if (!QFileInfo(dirName).exists())
	{
		QDir dir;
		if (!(dir.mkdir(dirName)))
		{
			QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("Unable to create directory %1").arg(dirName));
			return;
		}
	}

	// Write the DOM tree to file
	QFile f(name);
	if (f.open(QIODevice::WriteOnly))
	{
		QTextStream out(&f);
		QDomDocument doc("QGLVIEWER");
		doc.appendChild(domElement("QGLViewer", doc));
		doc.save(out, 2);
		f.flush();
		f.close();
	}
	else
		QMessageBox::warning(this, tr("Save to file error", "Message box window title"), tr("Unable to save to file %1").arg(name) + ":\n" + f.errorString());
}

/*! Restores the QGLViewer state from the stateFileName() file using initFromDOMElement().

States are saved using saveStateToFile(), which is automatically called on viewer exit.

Returns \c true when the restoration is successful. Possible problems are an non existing or
unreadable stateFileName() file, an empty stateFileName() or an XML syntax error.

A manipulatedFrame() should be defined \e before calling this method, so that its state can be
restored. Initialization code put \e after this function will override saved values:
\code
void Viewer::init()
{
// Default initialization goes here (including the declaration of a possible manipulatedFrame).

if (!restoreStateFromFile())
showEntireScene(); // Previous state cannot be restored: fit camera to scene.

// Specific initialization that overrides file savings goes here.
}
\endcode */
bool QGLViewer::restoreStateFromFile()
{
	QString name = stateFileName();

	if (name.isEmpty())
		return false;

	QFileInfo fileInfo(name);

	if (!fileInfo.isFile())
		// No warning since it would be displayed at first start.
		return false;

	if (!fileInfo.isReadable())
	{
		QMessageBox::warning(this, tr("Problem in state restoration", "Message box window title"), tr("File %1 is not readable.").arg(name));
		return false;
	}

	// Read the DOM tree form file
	QFile f(name);
	if (f.open(QIODevice::ReadOnly))
	{
		QDomDocument doc;
		doc.setContent(&f);
		f.close();
		QDomElement main = doc.documentElement();
		initFromDOMElement(main);
	}
	else
	{
		QMessageBox::warning(this, tr("Open file error", "Message box window title"), tr("Unable to open file %1").arg(name) + ":\n" + f.errorString());
		return false;
	}

	return true;
}

/*! Returns an XML \c QDomElement that represents the QGLViewer.

Used by saveStateToFile(). restoreStateFromFile() uses initFromDOMElement() to restore the
QGLViewer state from the resulting \c QDomElement.

\p name is the name of the QDomElement tag. \p doc is the \c QDomDocument factory used to create
QDomElement.

The created QDomElement contains state values (axisIsDrawn(), FPSIsDisplayed(), isFullScreen()...),
viewer geometry, as well as camera() (see qglviewer::Camera::domElement()) and manipulatedFrame()
(if defined, see qglviewer::ManipulatedFrame::domElement()) states.

Overload this method to add your own attributes to the state file:
\code
QDomElement Viewer::domElement(const QString& name, QDomDocument& document) const
{
// Creates a custom node for a light
QDomElement de = document.createElement("Light");
de.setAttribute("state", (lightIsOn()?"on":"off"));
// Note the include of the ManipulatedFrame domElement method.
de.appendChild(lightManipulatedFrame()->domElement("LightFrame", document));

// Get default state domElement and append custom node
QDomElement res = QGLViewer::domElement(name, document);
res.appendChild(de);
return res;
}
\endcode
See initFromDOMElement() for the associated restoration code.

\attention For the manipulatedFrame(), qglviewer::Frame::constraint() and
qglviewer::Frame::referenceFrame() are not saved. See qglviewer::Frame::domElement(). */
QDomElement QGLViewer::domElement(const QString& name, QDomDocument& document) const
{
	QDomElement de = document.createElement(name);
	de.setAttribute("version", QGLViewerVersionString());

	QDomElement stateNode = document.createElement("State");
	// hasMouseTracking() is not saved
	stateNode.appendChild(DomUtils::QColorDomElement(foregroundColor(), "foregroundColor", document));
	stateNode.appendChild(DomUtils::QColorDomElement(backgroundColor(), "backgroundColor", document));
	DomUtils::setBoolAttribute(stateNode, "stereo", displaysInStereo());
	// Revolve or fly camera mode is not saved
	de.appendChild(stateNode);

	QDomElement displayNode = document.createElement("Display");
	DomUtils::setBoolAttribute(displayNode, "axisIsDrawn", axisIsDrawn());
	DomUtils::setBoolAttribute(displayNode, "gridIsDrawn", gridIsDrawn());
	DomUtils::setBoolAttribute(displayNode, "FPSIsDisplayed", FPSIsDisplayed());
	DomUtils::setBoolAttribute(displayNode, "cameraIsEdited", cameraIsEdited());
	// textIsEnabled() is not saved
	de.appendChild(displayNode);

	QDomElement geometryNode = document.createElement("Geometry");
	DomUtils::setBoolAttribute(geometryNode, "fullScreen", isFullScreen());
	if (isFullScreen())
	{
		geometryNode.setAttribute("prevPosX", QString::number(prevPos_.x()));
		geometryNode.setAttribute("prevPosY", QString::number(prevPos_.y()));
	}
	else
	{
		QWidget* tlw = topLevelWidget();
		geometryNode.setAttribute("width",  QString::number(tlw->width()));
		geometryNode.setAttribute("height", QString::number(tlw->height()));
		geometryNode.setAttribute("posX",   QString::number(tlw->pos().x()));
		geometryNode.setAttribute("posY",   QString::number(tlw->pos().y()));
	}
	de.appendChild(geometryNode);

	// Restore original Camera zClippingCoefficient before saving.
	if (cameraIsEdited())
		camera()->setZClippingCoefficient(previousCameraZClippingCoefficient_);
	de.appendChild(camera()->domElement("Camera", document));
	if (cameraIsEdited())
		// #CONNECTION# 5.0 from setCameraIsEdited()
		camera()->setZClippingCoefficient(5.0);

	if (manipulatedFrame())
		de.appendChild(manipulatedFrame()->domElement("ManipulatedFrame", document));

	return de;
}

/*! Restores the QGLViewer state from a \c QDomElement created by domElement().

Used by restoreStateFromFile() to restore the QGLViewer state from a file.

Overload this method to retrieve custom attributes from the QGLViewer state file. This code
corresponds to the one given in the domElement() documentation:
\code
void Viewer::initFromDOMElement(const QDomElement& element)
{
// Restore standard state
QGLViewer::initFromDOMElement(element);

QDomElement child=element.firstChild().toElement();
while (!child.isNull())
{
if (child.tagName() == "Light")
{
if (child.hasAttribute("state"))
setLightOn(child.attribute("state").lower() == "on");

// Assumes there is only one child. Otherwise you need to parse child's children recursively.
QDomElement lf = child.firstChild().toElement();
if (!lf.isNull() && lf.tagName() == "LightFrame")
lightManipulatedFrame()->initFromDomElement(lf);
}
child = child.nextSibling().toElement();
}
}
\endcode

See also qglviewer::Camera::initFromDOMElement(), qglviewer::ManipulatedFrame::initFromDOMElement().

\note The manipulatedFrame() \e pointer is not modified by this method. If defined, its state is
simply set from the \p element values. */
void QGLViewer::initFromDOMElement(const QDomElement& element)
{
	const QString version = element.attribute("version");
	// if (version != QGLViewerVersionString())
	if (version[0] != '2')
		// Patches for previous versions should go here when the state file syntax is modified.
		qWarning("State file created using QGLViewer version %s may not be correctly read.", version.toLatin1().constData());

	QDomElement child=element.firstChild().toElement();
	bool tmpCameraIsEdited = cameraIsEdited();
	while (!child.isNull())
	{
		if (child.tagName() == "State")
		{
			// #CONNECTION# default values from defaultConstructor()
			// setMouseTracking(DomUtils::boolFromDom(child, "mouseTracking", false));
			setStereoDisplay(DomUtils::boolFromDom(child, "stereo", false));
			//if ((child.attribute("cameraMode", "revolve") == "fly") && (cameraIsInRevolveMode()))
			//	toggleCameraMode();

			QDomElement ch=child.firstChild().toElement();
			while (!ch.isNull())
			{
				if (ch.tagName() == "foregroundColor")
					setForegroundColor(DomUtils::QColorFromDom(ch));
				if (ch.tagName() == "backgroundColor")
					setBackgroundColor(DomUtils::QColorFromDom(ch));
				ch = ch.nextSibling().toElement();
			}
		}

		if (child.tagName() == "Display")
		{
			// #CONNECTION# default values from defaultConstructor()
			setAxisIsDrawn(DomUtils::boolFromDom(child, "axisIsDrawn", false));
			setGridIsDrawn(DomUtils::boolFromDom(child, "gridIsDrawn", false));
			setFPSIsDisplayed(DomUtils::boolFromDom(child, "FPSIsDisplayed", false));
			// See comment below.
			tmpCameraIsEdited = DomUtils::boolFromDom(child, "cameraIsEdited", false);
			// setTextIsEnabled(DomUtils::boolFromDom(child, "textIsEnabled", true));
		}

		if (child.tagName() == "Geometry")
		{
			setFullScreen(DomUtils::boolFromDom(child, "fullScreen", false));

			if (isFullScreen())
			{
				prevPos_.setX(DomUtils::intFromDom(child, "prevPosX", 0));
				prevPos_.setY(DomUtils::intFromDom(child, "prevPosY", 0));
			}
			else
			{
				int width  = DomUtils::intFromDom(child, "width",  600);
				int height = DomUtils::intFromDom(child, "height", 400);
				topLevelWidget()->resize(width, height);
				camera()->setScreenWidthAndHeight(this->width(), this->height());

				QPoint pos;
				pos.setX(DomUtils::intFromDom(child, "posX", 0));
				pos.setY(DomUtils::intFromDom(child, "posY", 0));
				topLevelWidget()->move(pos);
			}
		}

		if (child.tagName() == "Camera")
		{
			connectAllCameraKFIInterpolatedSignals(false);
			camera()->initFromDOMElement(child);
			connectAllCameraKFIInterpolatedSignals();
		}

		if ((child.tagName() == "ManipulatedFrame") && (manipulatedFrame()))
			manipulatedFrame()->initFromDOMElement(child);

		child = child.nextSibling().toElement();
	}

	// The Camera always stores its "real" zClippingCoef in domElement(). If it is edited,
	// its "real" coef must be saved and the coef set to 5.0, as is done in setCameraIsEdited().
	// BUT : Camera and Display are read in an arbitrary order. We must initialize Camera's
	// "real" coef BEFORE calling setCameraIsEdited. Hence this temp cameraIsEdited and delayed call
	cameraIsEdited_ = tmpCameraIsEdited;
	if (cameraIsEdited_)
	{
		previousCameraZClippingCoefficient_ = camera()->zClippingCoefficient();
		// #CONNECTION# 5.0 from setCameraIsEdited.
		camera()->setZClippingCoefficient(5.0);
	}
}

#ifndef DOXYGEN
/*! This method is deprecated since version 1.3.9-5. Use saveStateToFile() and setStateFileName()
instead. */
void QGLViewer::saveToFile(const QString& fileName)
{
	if (!fileName.isEmpty())
		setStateFileName(fileName);

	qWarning("saveToFile() is deprecated, use saveStateToFile() instead.");
	saveStateToFile();
}

/*! This function is deprecated since version 1.3.9-5. Use restoreStateFromFile() and
setStateFileName() instead. */
bool QGLViewer::restoreFromFile(const QString& fileName)
{
	if (!fileName.isEmpty())
		setStateFileName(fileName);

	qWarning("restoreFromFile() is deprecated, use restoreStateFromFile() instead.");
	return restoreStateFromFile();
}
#endif

/*! Makes a copy of the current buffer into a texture.

Creates a texture (when needed) and uses glCopyTexSubImage2D() to directly copy the buffer in it.

Use \p internalFormat and \p format to define the texture format and hence which and how components
of the buffer are copied into the texture. See the glTexImage2D() documentation for details.

When \p format is c GL_NONE (default), its value is set to \p internalFormat, which fits most
cases. Typical \p internalFormat (and \p format) values are \c GL_DEPTH_COMPONENT and \c GL_RGBA.
Use \c GL_LUMINANCE as the \p internalFormat and \c GL_RED, \c GL_GREEN or \c GL_BLUE as \p format
to capture a single color component as a luminance (grey scaled) value. Note that \c GL_STENCIL is
not supported as a format.

The texture has dimensions which are powers of two. It is as small as possible while always being
larger or equal to the current size of the widget. The buffer image hence does not entirely fill
the texture: it is stuck to the lower left corner (corresponding to the (0,0) texture coordinates).
Use bufferTextureMaxU() and bufferTextureMaxV() to get the upper right corner maximum u and v
texture coordinates. Use bufferTextureId() to retrieve the id of the created texture.

Here is how to display a grey-level image of the z-buffer:
\code
copyBufferToTexture(GL_DEPTH_COMPONENT);

glMatrixMode(GL_TEXTURE);
glLoadIdentity();

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glEnable(GL_TEXTURE_2D);

startScreenCoordinatesSystem(true);

glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);                                 glVertex2i(0, 0);
glTexCoord2f(bufferTextureMaxU(), 0.0);                 glVertex2i(width(), 0);
glTexCoord2f(bufferTextureMaxU(), bufferTextureMaxV()); glVertex2i(width(), height());
glTexCoord2f(0.0, bufferTextureMaxV());                 glVertex2i(0, height());
glEnd();

stopScreenCoordinatesSystem();

glDisable(GL_TEXTURE_2D);
\endcode

Use glReadBuffer() to select which buffer is copied into the texture. See also \c
glPixelTransfer(), \c glPixelZoom() and \c glCopyPixel() for pixel color transformations during
copy.

Call makeCurrent() before this method to make the OpenGL context active if needed.

\note The \c GL_DEPTH_COMPONENT format may not be supported by all hardware. It may sometimes be
emulated in software, resulting in poor performances.

\note The bufferTextureId() texture is binded at the end of this method. */
void QGLViewer::copyBufferToTexture(GLint internalFormat, GLenum format)
{
	int h = 16;
	int w = 16;
	// Todo compare performance with qt code.
	while (w < width())
		w <<= 1;
	while (h < height())
		h <<= 1;

	bool init = false;

	if ((w != bufferTextureWidth_) || (h != bufferTextureHeight_))
	{
		bufferTextureWidth_ = w;
		bufferTextureHeight_ = h;
		bufferTextureMaxU_ = width()  / qreal(bufferTextureWidth_);
		bufferTextureMaxV_ = height() / qreal(bufferTextureHeight_);
		init = true;
	}

	if (bufferTextureId() == 0)
	{
		glGenTextures(1, &bufferTextureId_);
		glBindTexture(GL_TEXTURE_2D, bufferTextureId_);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		init = true;
	}
	else
		glBindTexture(GL_TEXTURE_2D, bufferTextureId_);

	if ((format != previousBufferTextureFormat_) ||
		(internalFormat != previousBufferTextureInternalFormat_))
	{
		previousBufferTextureFormat_ = format;
		previousBufferTextureInternalFormat_ = internalFormat;
		init = true;
	}

	if (init)
	{
		if (format == GL_NONE)
			format = GLenum(internalFormat);

		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, bufferTextureWidth_, bufferTextureHeight_, 0, format, GL_UNSIGNED_BYTE, NULL);
	}

	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width(), height());
}

/*! Returns the texture id of the texture created by copyBufferToTexture().

Use glBindTexture() to use this texture. Note that this is already done by copyBufferToTexture().

Returns \c 0 is copyBufferToTexture() was never called or if the texure was deleted using
glDeleteTextures() since then. */
GLuint QGLViewer::bufferTextureId() const
{
	if (glIsTexture(bufferTextureId_))
		return bufferTextureId_;
	else
		return 0;
}
