// 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 k3d::chooser classes, which provide a model-view-controller UI for enumerated (fixed set of choices) types
		\author Tim Shead (tshead@k-3d.com)
*/

#include "chooser.h"
#include "gtkml.h"

#include <k3dsdk/application.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/istate_recorder.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/string_modifiers.h>

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

#include <iterator>

namespace
{

const std::string control_menu = "menu";
const std::string control_value = "value";
const std::string control_valuetoggled = "toggled";

} // namespace

namespace k3d
{

namespace chooser
{

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

control::control(k3d::iunknown* const CommandNodeParent, const std::string& CommandNodeName) :
	base(CommandNodeParent, CommandNodeName)
{
}

control::control(k3d::iunknown* const CommandNodeParent, const std::string& CommandNodeName, const ienumeration_property::values_t& Choices) :
	base(CommandNodeParent, CommandNodeName),
	m_choices(Choices)
{
	// Create and load our UI template ...
	std::stringstream uitemplate;

	uitemplate <<
		"<gtkml>"
			"<eventbox>"
				"<event signal=\"destroy\" name=\"destroy\"/>";

					uitemplate << "<optionmenu name=\"" << control_menu << "\"><menu name=\"actualmenu\">";
					for(ienumeration_property::values_t::const_iterator choice = m_choices.begin(); choice != m_choices.end(); ++choice)
						{
							uitemplate << "<menuitem name=\"" << choice->value << "\">" << choice->name;
							uitemplate << "<event signal=\"activate\" name=\"" << control_value << " " << choice->value << "\"/>";
							uitemplate << "</menuitem>";
						}
					uitemplate << "</menu></optionmenu>";

	uitemplate <<
			"</eventbox>"
		"</gtkml>\n";

	return_if_fail(load_gtkml(uitemplate, "chooser builtin template", *this));
}

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("k3dchooser");
}

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

	// Populate our list of choices ...
	for(sdpxml::ElementCollection::iterator element = Element.Children().begin(); element != Element.Children().end(); element++)
		{
			if(element->Name() != "k3dchoice")
				{
					std::cerr << __PRETTY_FUNCTION__ << ": unknown element <" << element->Name() << "> at " << sdpxml::FileReference(Document, *element) << " will be ignored" << std::endl;
					continue;
				}

			const std::string value = sdpxml::GetAttribute(*element, "value", std::string());
			sdpGtkMarkAttribute(Document, *element, "value");

			m_choices.push_back(ienumeration_property::value_t(sdpGtkInternationalText(Document, *element), value, ""));
		}

	// Create and load our UI template ...
	std::stringstream uitemplate;

	uitemplate <<
		"<gtkml>"
			"<eventbox>"
				"<event signal=\"destroy\" name=\"destroy\"/>";

					uitemplate << "<optionmenu name=\"" << control_menu << "\"><menu name=\"actualmenu\">";
					for(ienumeration_property::values_t::const_iterator choice = m_choices.begin(); choice != m_choices.end(); ++choice)
						{
							uitemplate << "<menuitem name=\"" << choice->value << "\">" << choice->name;
							uitemplate << "<event signal=\"activate\" name=\"" << control_value << " " << choice->value << "\"/>";
							uitemplate << "</menuitem>";
						}
					uitemplate << "</menu></optionmenu>";

	uitemplate <<
			"</eventbox>"
		"</gtkml>\n";

	return_val_if_fail(load_gtkml(uitemplate, "chooser builtin template", *this), false);

	RootWidget().Show();

	return true;
}

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

	// Take ownership of the data source ...
	m_data = Data;

	// Complete our own initialization ...
	return_val_if_fail(base::Attach(StateRecorder, StateChangeName), false);

	// Update the display ...
	update();

	// We want to be notified if the data source changes ...
	m_data->changed_signal().connect(SigC::slot(*this, &control::update));

	return true;
}

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

	// Get the current value ...
	const std::string value = m_data->value();

	// Block events for a moment ...
	BlockAllEvents();

	// Display the current value ...
	for(ienumeration_property::values_t::iterator choice = m_choices.begin(); choice != m_choices.end(); ++choice)
		{
			if(choice->value == value)
				{
					OptionMenu(control_menu).SetHistory(std::distance(m_choices.begin(), choice));
					break;
				}
		}

	// Restore events ...
	UnblockAllEvents();
}

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

	if(0 == Event->Name().find(control_value))
		on_value(Event);
	else if(0 == Event->Name().find(control_valuetoggled))
		on_value_toggled(Event);
	else if(Event->Name() == "destroy")
		on_destroy();
	else
		base::OnEvent(Event);
}

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

void control::on_value(sdpGtkEvent* Event)
{
	// Make sure we've got some storage, first!
	return_if_fail(m_data.get());

	// Get the new value ...
	const std::string event_value = Event->Name();
	const std::string value = k3d::right(event_value, event_value.size() - control_value.size() - 1);

	// Record the command for posterity (tutorials) ...
	k3d::application().command_signal().emit(this, k3d::icommand_node::command_t::USER_INTERFACE, control_value, value);

	// If the value hasn't changed, we're done ...
	if(value == m_data->value())
		return;

	// Find the matching choice record for this value ...
	ienumeration_property::values_t::const_iterator choice;
	for(choice = m_choices.begin(); choice != m_choices.end(); ++choice)
		if(choice->value == value)
			break;
	return_if_fail(choice != m_choices.end());

	// Turn this into an undo/redo -able event ...
	if(m_StateRecorder)
		m_StateRecorder->start_recording(k3d::create_state_change_set());

	// Store the new value ...
	m_data->set_value(value);

	// Turn this into an undo/redo -able event ...
	if(m_StateRecorder)
		m_StateRecorder->commit_change_set(m_StateRecorder->stop_recording(), m_StateChangeName + ' ' + '"' + choice->name + '"');
}

void control::on_value_toggled(sdpGtkEvent* Event)
{
	// Make sure we've got some storage, first!
	return_if_fail(m_data.get());

	// Make sure we're picking-up the button that was toggled "on" ... not the one toggled "off" ...
	const std::string event_value = Event->Name();
	const std::string value = k3d::right(event_value, event_value.size() - control_valuetoggled.size());
	if(!ToggleButton(value.c_str()).GetState())
		return;

	// Record the command for posterity (tutorials) ...
	k3d::application().command_signal().emit(this, k3d::icommand_node::command_t::USER_INTERFACE, control_value, value);

	// If the value hasn't changed, we're done ...
	if(value == m_data->value())
		return;

	// Find the matching choice record for this value ...
	ienumeration_property::values_t::const_iterator choice;
	for(choice = m_choices.begin(); choice != m_choices.end(); ++choice)
		if(choice->value == value)
			break;
	return_if_fail(choice != m_choices.end());

	// Turn this into an undo/redo -able event ...
	if(m_StateRecorder)
		m_StateRecorder->start_recording(k3d::create_state_change_set());

	// Store the new value ...
	m_data->set_value(value);

	// Turn this into an undo/redo -able event ...
	if(m_StateRecorder)
		m_StateRecorder->commit_change_set(m_StateRecorder->stop_recording(), m_StateChangeName + ' ' + '"' + choice->name + '"');
}

bool control::execute_command(const std::string& Command, const std::string& Arguments)
{
	// Sanity checks ...
	return_val_if_fail(m_data.get(), false);

	if(Command == control_value)
		{
			// Convert the arguments string into the desired value ...
			const std::string value = Arguments;

			// Find the matching choice record ...
			ienumeration_property::values_t::const_iterator choice;
			for(choice = m_choices.begin(); choice != m_choices.end(); choice++)
				if(choice->value == value)
					break;
			if(choice == m_choices.end())
				{
					std::cerr << __PRETTY_FUNCTION__ << ": could not find choice [" << value << "]" << std::endl;
					return false;
				}

			InteractiveMoveTo(control_menu, k3d::application().options().tutorial_speed(), true);

			GdkEventButton event;
			memset(&event, 0, sizeof(event));
			event.type = GDK_BUTTON_PRESS;
			event.button = 1;
			event.time = GDK_CURRENT_TIME;
			gtk_widget_event(Widget(control_menu), reinterpret_cast<GdkEvent*>(&event));

			MenuItem(sdpToString(value)).InteractiveWarpPointer(k3d::application().options().tutorial_speed(), true, false);
			MenuItem(sdpToString(value)).InteractiveActivate();

			gtk_menu_popdown(Menu("actualmenu"));

			return true;
		}

	return base::execute_command(Command, Arguments);
}

} // namespace chooser

} // namespace k3d


