// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the control class, which provides a UI for manually placing objects that support k3dITransform
		\author Tim Shead (tshead@k-3d.com)
*/

#include "button.h"
#include "gtkml.h"
#include "orientation.h"
#include "spin_button.h"

#include <k3dsdk/iwritable_property.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/transform.h>
#include <k3dsdk/vectors.h>
#include <k3dsdk/viewport.h>

#include <sdpgtk/sdpgtkloopevents.h>
#include <sdpgtk/sdpgtkutility.h>

namespace k3d
{

namespace orientation
{

namespace detail
{

/// Adapts a k3d::iproperty object for use with k3d::orientation::control
class property_proxy :
	public k3d::orientation::idata_proxy
{
public:
	property_proxy(iproperty& Property) :
		m_property(Property)
	{
		assert(Property.type() == typeid(angle_axis));
	}

	const angle_axis value()
	{
		return boost::any_cast<angle_axis>(m_property.value());
	}

	void set_value(const angle_axis& Value)
	{
		iwritable_property* const writable_property = dynamic_cast<iwritable_property*>(&m_property);
		return_if_fail(writable_property);

		writable_property->set_value(boost::any(Value));
	}

	changed_signal_t& changed_signal()
	{
		return m_property.changed_signal();
	}
	
private:
	iproperty& m_property;
};

/// Adapts a spin button to control a single orientation coordinate (indentified by index)
class spin_button_proxy_t :
	public k3d::spin_button::idata_proxy
{
public:
	spin_button_proxy_t(k3d::orientation::idata_proxy& Data, const unsigned int Index) :
		m_data(Data),
		m_index(Index)
	{
		// Sanity checks ...
		assert_warning(m_index < 3);
	}

	bool writable()
	{
		return true;
	}
	
	double value()
	{
		k3d::quaternion quaternion(m_data.value());
		return k3d::euler_angles(quaternion, k3d::euler_angles::XYZstatic)[m_index];
	}

	void set_value(const double Value)
	{
		k3d::quaternion quaternion(m_data.value());
		k3d::euler_angles eulerangles(quaternion, k3d::euler_angles::XYZstatic);
		eulerangles[m_index] = Value;
		m_data.set_value(k3d::angle_axis(k3d::quaternion(eulerangles)));	
	}

	changed_signal_t& changed_signal()
	{
		return m_data.changed_signal();
	}

private:
	k3d::orientation::idata_proxy& m_data;
	const unsigned int m_index;
};

/// Convenience factory method for creating spin_button_proxy_t objects
std::auto_ptr<k3d::spin_button::idata_proxy> spin_button_proxy(k3d::orientation::idata_proxy& Data, const unsigned int Index)
{
	return std::auto_ptr<k3d::spin_button::idata_proxy>(new spin_button_proxy_t(Data, Index));
}

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// control

namespace
{

const std::string control_x = "x";
const std::string control_y = "y";
const std::string control_z = "z";
const std::string control_reset = "reset";

} // namespace

control::control(k3d::iunknown* CommandNodeParent, std::string CommandNodeName) :
	base(CommandNodeParent, CommandNodeName),
	m_document(0)
{
	// Create and load our UI template ...
	std::istringstream uitemplate(
		"<gtkml>"
			"<vbox homogeneous=\"false\">"
				"<event signal=\"destroy\" name=\"destroy\"/>"

				"<k3dspinbutton datatype=\"angle\" precision=\"3\" stepincrement=\"1 degree\" name=\"x\"/>"
				"<k3dspinbutton datatype=\"angle\" precision=\"3\" stepincrement=\"1 degree\" name=\"y\"/>"
				"<k3dspinbutton datatype=\"angle\" precision=\"3\" stepincrement=\"1 degree\" name=\"z\"/>"
				"<k3dbutton name=\"reset\">Reset Orientation</button>"

			"</vbox>"
		"</gtkml>\n");

	return_if_fail(k3d::load_gtkml(uitemplate, "orientation::control builtin template", *this));

	if(get_button("reset"))
		get_button("reset")->signal_activate().connect(SigC::slot(*this, &control::on_reset));

	RootWidget().Show();
}

control::~control()
{
	// No more events from this point forward ...
	DisconnectAllEvents();

	// Clean-up the GTK+ tree ...
	if(Root())
		RootWidget().Destroy();
}

const std::string control::CustomType() const
{
	return std::string("k3dorientationcontrol");
}

bool control::Create(sdpGtkIObjectContainer* ObjectContainer, sdpxml::Document& Document, sdpxml::Element& Element)
{
	// Sanity checks ...
	assert_warning(ObjectContainer);

	assert_warning(Element.Name() == "k3dorientationcontrol");

	return true;
}

bool control::attach(std::auto_ptr<idata_proxy> Data, k3d::idocument& Document, k3d::istate_recorder* const StateRecorder, std::string StateChangeName)
{
	// Sanity checks ...
	return_val_if_fail(Data.get(), false);
	return_val_if_fail(base::Attach(StateRecorder, StateChangeName), false);

	// Store references ...
	m_document = &Document;
	m_data = Data;

	connect_spin_button(control_x, detail::spin_button_proxy(*m_data, 0), StateRecorder, "X Orientation");
	connect_spin_button(control_y, detail::spin_button_proxy(*m_data, 1), StateRecorder, "Y Orientation");
	connect_spin_button(control_z, detail::spin_button_proxy(*m_data, 2), StateRecorder, "Z Orientation");

	return true;
}

void control::OnEvent(sdpGtkEvent* Event)
{
	// Sanity checks ...
	assert_warning(Event);

	if(Event->Name() == "destroy")
		on_destroy();
	else
		base::OnEvent(Event);
}

void control::on_destroy()
{
	DisconnectAllEvents();
	Clear();
}

void control::on_reset()
{
	// Sanity checks ...
	return_if_fail(m_data.get());

	k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_reset);

	k3d::start_state_change_set(*m_document);
	m_data->set_value(angle_axis(0, vector3(1, 0, 0)));
	k3d::finish_state_change_set(*m_document, "Reset Orientation");

	k3d::viewport::redraw_all(*m_document, k3d::iviewport::ASYNCHRONOUS);
}

///////////////////////////////////////////////////////////////////////////////////
// proxy

std::auto_ptr<idata_proxy> proxy(iproperty& Data)
{
	return std::auto_ptr<idata_proxy>(new detail::property_proxy(Data));
}

} // namespace orientation

} // namespace k3d

