// 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
		\author Tim Shead (tshead@k-3d.com)
*/

#include "auto_dialog.h"
#include "bitmap_preview.h"
#include "button.h"
#include "check_button.h"
#include "chooser.h"
#include "color_chooser.h"
#include "combo_box.h"
#include "edit_control.h"
#include "object_chooser.h"
#include "orientation.h"
#include "path_chooser.h"
#include "scale.h"
#include "keyboard.h"
#include "position.h"
#include "property_button.h"
#include "script_editor.h"
#include "spin_button.h"
#include "viewport_window.h"

#include <k3dsdk/application.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/frames.h>
#include <k3dsdk/ianimation_render_engine.h>
#include <k3dsdk/ienumeration_property.h>
#include <k3dsdk/ilist_property.h>
#include <k3dsdk/imeasurement_property.h>
#include <k3dsdk/imesh_sink.h>
#include <k3dsdk/imesh_source.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/iproperty.h>
#include <k3dsdk/iproperty_collection.h>
#include <k3dsdk/iproperty_group_collection.h>
#include <k3dsdk/iscript_property.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/mesh.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/string_cast.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/viewport.h>

#include <sdpgtk/sdpgtkevents.h>

#include <boost/scoped_ptr.hpp>

#include <set>

// We have an unfortunate clash with X
#ifdef RootWindow
#undef RootWindow
#endif

namespace
{

const std::string event_configure("event_configure");
const std::string event_delete("event_delete");
const std::string event_key_press("event_key_press");

std::set<std::string> skip_properties()
{
	static std::set<std::string> results;
	if(results.empty())
		{
			results.insert("position");
			results.insert("orientation");
			results.insert("scale");
			results.insert("matrix");
		}
	
	return results;
}

/////////////////////////////////////////////////////////////////////////////
// auto_controls

/// Performs the heavy-lifting of automatically generating a user-interface for a generic object by examining its interfaces & properties
class auto_controls :
	public k3dUserInterfaceElement
{
	typedef k3dUserInterfaceElement base;

public:
	typedef enum
	{
		WITH_UNDO,
		NO_UNDO
	} undo_t;

	auto_controls(k3d::icommand_node* Parent, const std::string& Name, k3d::iobject& Object, const undo_t Undo, sdpGtkContainer& Container) :
		base(Parent, Name),
		m_object(Object)
	{
		// Cache the state recorder to pass to controls, based on whether undos are enabled or not ...
		k3d::istate_recorder* const state_recorder = (WITH_UNDO == Undo) ? &m_object.document().state_recorder() : 0;

		sdpGtkVBox container;
		container.Create(false, 0);
		container.Show();
		Container.Attach(container);
		
		sdpGtkToolbar toolbar;
		toolbar.Create(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_ICONS);
		toolbar.Show();
		container.PackStart(toolbar, false, false);

		// Add buttons for viewports ...
		k3d::iviewport* const viewport = dynamic_cast<k3d::iviewport*>(&m_object);
		if(viewport)
			{
				k3d::button::control* const control = new k3d::button::control(this, "attach_viewport_window", "New Viewport Window");
				control->signal_activate().connect(SigC::slot(*this, &auto_controls::on_attach_viewport_window));
				MapCustomObject("attach_viewport_window", control);
				toolbar.Append(control->RootWidget(), "Create a new viewport window", "");
			}

		// Add buttons for viewport hosts ...
		k3d::iviewport_host* const viewport_host = dynamic_cast<k3d::iviewport_host*>(&m_object);
		if(viewport_host && !viewport)
			{
				k3d::button::control* const control = new k3d::button::control(this, "attach_viewport", "New Viewport");
				control->signal_activate().connect(SigC::slot(*this, &auto_controls::on_attach_viewport));
				MapCustomObject("attach_viewport", control);
				toolbar.Append(control->RootWidget(), "Create a new viewport", "");
			}

		// Add buttons for render engines ...
		k3d::istill_render_engine* const still_render_engine = dynamic_cast<k3d::istill_render_engine*>(&m_object);
		if(still_render_engine)
			{
				k3d::button::control* const preview_control = new k3d::button::control(this, "render_preview", "Render Preview", "renderpreview.xpm");
				preview_control->signal_activate().connect(SigC::slot(*this, &auto_controls::on_render_preview));
				MapCustomObject("render_preview", preview_control);
				toolbar.Append(preview_control->RootWidget(), "Render Preview", "");

				k3d::button::control* const frame_control = new k3d::button::control(this, "render_frame", "Render Frame", "renderframe.xpm");
				frame_control->signal_activate().connect(SigC::slot(*this, &auto_controls::on_render_frame));
				MapCustomObject("render_frame", frame_control);
				toolbar.Append(frame_control->RootWidget(), "Render Frame", "");
			}
		
		k3d::ianimation_render_engine* const animation_render_engine = dynamic_cast<k3d::ianimation_render_engine*>(&m_object);
		if(animation_render_engine)
			{
				k3d::button::control* const animation_control = new k3d::button::control(this, "render_animation", "Render Animation", "renderanimation.xpm");
				animation_control->signal_activate().connect(SigC::slot(*this, &auto_controls::on_render_animation));
				MapCustomObject("render_animation", animation_control);
				toolbar.Append(animation_control->RootWidget(), "Render Animation", "");
			}
		
		// Get the object properties, and group them so we can create a tabbed interface ...
		k3d::iproperty_group_collection::groups_t property_groups;

		k3d::iproperty_collection* const property_collection = dynamic_cast<k3d::iproperty_collection*>(&m_object);
		if(property_collection)
			{
				k3d::iproperty_collection::properties_t all_properties = property_collection->properties();
				k3d::iproperty_group_collection::groups_t groups;
			
				k3d::iproperty_group_collection* const property_group_collection = dynamic_cast<k3d::iproperty_group_collection*>(&m_object);
				if(property_group_collection)
					{
						groups = property_group_collection->property_groups();
						for(k3d::iproperty_group_collection::groups_t::const_iterator group = groups.begin(); group != groups.end(); ++group)
							{
								for(k3d::iproperty_collection::properties_t::const_iterator property = group->properties.begin(); property != group->properties.end(); ++property)
									all_properties.erase(std::remove(all_properties.begin(), all_properties.end(), *property), all_properties.end());
							}
					}
			
				property_groups.insert(property_groups.end(), k3d::iproperty_group_collection::group(m_object.factory().name(), all_properties));
				property_groups.insert(property_groups.end(), groups.begin(), groups.end());
			}
			
		sdpGtkNotebook notebook;
		notebook.Create();
		notebook.Show();
		container.PackStart(notebook, true, true);
		
		// Create a tab for each group ...
		for(k3d::iproperty_group_collection::groups_t::const_iterator property_group = property_groups.begin(); property_group != property_groups.end(); ++property_group)
			{
				if(property_group->properties.empty())
					continue;
				
				sdpGtkLabel tab_label;
				tab_label.Create(property_group->name);
				tab_label.Show();
					
				sdpGtkVBox tab_container;
				tab_container.Create(false, 0);
				tab_container.Show();
				
				notebook.AppendPage(tab_container, tab_label);
				
				sdpGtkTable table;
				table.Create(property_group->properties.size(), 3, false);
				table.Show();
				tab_container.PackStart(table, false, false, 0);
			
				// For each property within the group ...
				unsigned int row = 0;
				for(unsigned int i = 0; i != property_group->properties.size(); ++i)
					{
						k3d::iproperty& property = *property_group->properties[i];

						// Skip certain well-known properties ...
						if(skip_properties().count(property.name()))
							continue;	

						const std::type_info& property_type = property.type();
						const std::string tooltip(property.description().empty() ? property.name() : property.description());
					
						// Label the property ...
						sdpGtkLabel label;
						label.Create(property.name());
						label.SetPadding(4, 4);
						label.Show();
						table.Attach(label, 2, 3, row, row+1);
						
						// Provide a property button for the property ...
						const std::string property_control_name = property.name() + "_property";
						k3d::property_button::control* const property_control = new k3d::property_button::control(this, property_control_name);
						MapCustomObject(property_control_name.c_str(), property_control);
						table.Attach(property_control->RootWidget(), 1, 2, row, row+1, static_cast<GtkAttachOptions>(0), static_cast<GtkAttachOptions>(0), 0, 0);
						property_control->attach(k3d::property_button::proxy(m_object.document(), property), state_recorder, property.name());

						if(property_type == typeid(bool))
							{
								k3d::check_button::control* const control = new k3d::check_button::control(this, property.name(), property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								control->attach(k3d::check_button::proxy(property), state_recorder, property.name());
							}
						else if(property_type == typeid(double) || property_type == typeid(float) || property_type == typeid(long) || property_type == typeid(unsigned long) || property_type == typeid(int) || property_type == typeid(unsigned int))
							{
								k3d::spin_button::control* const control = new k3d::spin_button::control(this, property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								k3d::imeasurement_property* const measurement_property = dynamic_cast<k3d::imeasurement_property*>(&property);
								if(measurement_property)
									{
										control->set_precision(measurement_property->precision());
										control->set_step_increment(measurement_property->step_increment());
										control->set_units(measurement_property->units());
									}
								control->attach(k3d::spin_button::proxy(property), state_recorder, property.name());
							}
						else if(property_type == typeid(k3d::color))
							{
								k3d::color_chooser::control* const control = new k3d::color_chooser::control(this, property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								control->attach(k3d::color_chooser::proxy(property), state_recorder, property.name());
							}
						else if(property_type == typeid(std::string))
							{
								k3d::ienumeration_property* const enumeration_property = dynamic_cast<k3d::ienumeration_property*>(&property);
								k3d::iscript_property* const script_property = dynamic_cast<k3d::iscript_property*>(&property);
								k3d::ilist_property<std::string>* const list_property = dynamic_cast<k3d::ilist_property<std::string>*>(&property);

								if(enumeration_property)
									{
										const k3d::ienumeration_property::values_t values(enumeration_property->values());
										if(values.empty())
											std::cerr << warning << "Enumeration property without any values [" << property.name() << "]" << std::endl;

										k3d::chooser::control* const control = new k3d::chooser::control(this, property.name(), values);
										MapCustomObject(property.name().c_str(), control);
										table.Attach(control->RootWidget(), 0, 1, row, row+1);
										control->attach(k3d::chooser::proxy(property), state_recorder, property.name());
									}
								else if(script_property)
									{
										k3d::button::control* const control = new k3d::button::control(this, property.name(), "Edit Script");
										MapCustomObject(property.name().c_str(), control);
										table.Attach(control->RootWidget(), 0, 1, row, row+1);
										control->signal_activate().connect(SigC::bind(SigC::slot(*this, &auto_controls::new_inline_script_editor), &property));
									}
								else if(list_property)
									{
										k3d::combo_box::control* const control = new k3d::combo_box::control(this, property.name());
										MapCustomObject(property.name().c_str(), control);
										table.Attach(control->RootWidget(), 0, 1, row, row+1);
										control->attach(k3d::combo_box::proxy(property), state_recorder, property.name());
										control->set_values(list_property->values());
									}
								else
									{
										k3d::edit_control::control* const control = new k3d::edit_control::control(this, property.name());
										MapCustomObject(property.name().c_str(), control);
										table.Attach(control->RootWidget(), 0, 1, row, row+1);
										control->attach(k3d::edit_control::proxy(property), state_recorder, property.name());
									}
							}
						else if(property_type == typeid(k3d::iobject*))
							{
								k3d::object_chooser::control* const control = new k3d::object_chooser::control(this, property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								control->attach(k3d::object_chooser::filter(property), k3d::object_chooser::proxy(property, m_object.document()), state_recorder, property.name());
							}
						else if(property_type == typeid(k3d::bitmap*))
							{
								k3d::bitmap_preview::control* const control = new k3d::bitmap_preview::control(this, property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								control->attach(k3d::bitmap_preview::proxy(property), state_recorder, property.name());
							}
						else if(property_type == typeid(boost::filesystem::path))
							{
								k3d::path_chooser::control* const control = new k3d::path_chooser::control(this, property.name());
								MapCustomObject(property.name().c_str(), control);
								table.Attach(control->RootWidget(), 0, 1, row, row+1);
								control->attach(k3d::path_chooser::proxy(property), state_recorder, property.name());
							}
						else
							{
								//std::cerr << __PRETTY_FUNCTION__ << "Unknown property type: " << property.name() << std::endl;
							}
						
						++row;
					}
			}

		// If the object is transformable, add appropriate tabs ...
		k3d::iproperty* const position = k3d::get_property(m_object, "position");
		if(position && (position->type() == typeid(k3d::vector3)))
			{
				sdpGtkLabel label;
				label.Create("Position");
				label.Show();
				
				k3d::position::control* const control = new k3d::position::control(this, "position_control");
				MapCustomObject("position_control", control);
				
				notebook.AppendPage(control->RootWidget(), label);

				control->attach(k3d::position::proxy(*position), m_object.document(), state_recorder, "Position");
			}

		k3d::iproperty* const orientation = k3d::get_property(m_object, "orientation");
		if(orientation && (orientation->type() == typeid(k3d::angle_axis)))						
			{
				sdpGtkLabel label;
				label.Create("Orientation");
				label.Show();
				
				k3d::orientation::control* const control = new k3d::orientation::control(this, "orientation_control");
				MapCustomObject("orientation_control", control);
				
				notebook.AppendPage(control->RootWidget(), label);
				
				control->attach(k3d::orientation::proxy(*orientation), m_object.document(), state_recorder, "Orientation");
			}

		k3d::iproperty* const scale = k3d::get_property(m_object, "scale");
		if(scale && (scale->type() == typeid(k3d::vector3)))
			{
				sdpGtkLabel label;
				label.Create("Scale");
				label.Show();
				
				k3d::scale::control* const control = new k3d::scale::control(this, "scale_control");
				MapCustomObject("scale_control", control);

				notebook.AppendPage(control->RootWidget(), label);
								
				control->attach(k3d::scale::proxy(*scale), m_object.document(), state_recorder, "Scale");
			}
			
		notebook.SetPage(0);
	}

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

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

		// Get rid of our widget pointers ...
		Clear();
	}

private:
	void on_attach_viewport_window()
	{
		k3d::iviewport* const viewport = dynamic_cast<k3d::iviewport*>(&m_object);
		return_if_fail(viewport);

		k3d::viewport::window* const viewport_window = new k3d::viewport::window(m_object.document());
		viewport_window->attach(*viewport);
	}

	void on_attach_viewport()
	{
		k3d::iviewport_host* const viewport_host = dynamic_cast<k3d::iviewport_host*>(&m_object);
		return_if_fail(viewport_host);


		k3d::iobject* const viewport_object = k3d::create_document_plugin("Viewport", m_object.document(), k3d::unique_name(m_object.document().objects(), m_object.name() + " Viewport"));
		if(viewport_object)
			{
				k3d::iviewport* const viewport = dynamic_cast<k3d::iviewport*>(viewport_object);
				if(viewport)
					{
						viewport->set_host(viewport_host);
						
						k3d::iproperty* const output_mesh = k3d::get_typed_property<k3d::matrix4>(*viewport_host, "output_matrix");
						k3d::iproperty* const input_mesh = k3d::get_typed_property<k3d::matrix4>(*viewport, "input_matrix");
						if(input_mesh && output_mesh)
							{
								k3d::idag::dependencies_t dependencies;
								dependencies.insert(std::make_pair(input_mesh, output_mesh));
								m_object.document().dag().set_dependencies(dependencies);
							}
						
						k3d::viewport::window* const window = new k3d::viewport::window(m_object.document());
						window->attach(*viewport);
					}
			}
	}
	
	void on_render_preview()
	{
		k3d::istill_render_engine* const render_engine = dynamic_cast<k3d::istill_render_engine*>(&m_object);
		return_if_fail(render_engine);

		assert_warning(render_engine->render_preview());
	}
	
	void on_render_frame()
	{
		k3d::istill_render_engine* const render_engine = dynamic_cast<k3d::istill_render_engine*>(&m_object);
		return_if_fail(render_engine);

		boost::filesystem::path file;
		if(!k3d::get_file_path("render_frame", "Render Frame:", true, boost::filesystem::path(), file))
			return;

		assert_warning(render_engine->render_frame(file, true));
	}
	
	void on_render_animation()
	{
		k3d::ianimation_render_engine* const render_engine = dynamic_cast<k3d::ianimation_render_engine*>(&m_object);
		return_if_fail(render_engine);

		// Ensure that the document has animation capabilities, first ...
		k3d::iproperty* const start_time_property = k3d::get_start_time(m_object.document());
		k3d::iproperty* const end_time_property = k3d::get_end_time(m_object.document());
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(m_object.document());
		return_if_fail(start_time_property && end_time_property && frame_rate_property);

		// Prompt the user for a base filename ...
		boost::filesystem::path file;
		if(!k3d::get_file_path("renderanimation", "Choose Animation Base Filepath:", false, boost::filesystem::path(), file))
			return;

		// Make sure the supplied filepath has enough digits to render the entire animation ...
		const double start_time = boost::any_cast<double>(k3d::get_property_value(m_object.document().dag(), *start_time_property));
		const double end_time = boost::any_cast<double>(k3d::get_property_value(m_object.document().dag(), *end_time_property));
		const double frame_rate = boost::any_cast<double>(k3d::get_property_value(m_object.document().dag(), *frame_rate_property));
		
		const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
		const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));

		k3d::frames frames(file, start_frame, end_frame);
		if(frames.max_frame() < end_frame)
			{
				std::string message = "The Base Filepath doesn't contain enough digits to render the entire animation.\n"
					"Try a filepath of the form [ myanim0000.tif ] ... the placement of digits is flexible,\n"
					"and any prefix / postfix / file extension is optional, but the path must contain\n"
					"enough consecutive digits to enumerate all of the frames in the animation.";

				k3d::error_message(message, "Render Animation:");
				return;
			}

		// See if the user wants to view frames as they're completed ...
		std::vector<std::string> buttons;
		buttons.push_back("Yes");
		buttons.push_back("No");
		buttons.push_back("Cancel");

		const unsigned long result = k3d::query_message("Do you want to see rendered frames as they're completed?", "Render Animation:", 1, buttons);
		if(0 == result || 3 == result)
			return;

		const bool viewcompleted = (1 == result);

		assert_warning(render_engine->render_animation(file, viewcompleted));
	}

	void new_inline_script_editor(k3d::iproperty* Property)
	{
		k3d::icommand_node* const command_node = dynamic_cast<k3d::icommand_node*>(&m_object);
		return_if_fail(command_node);
		
		display_inline_script_editor(*Property, *command_node, m_object.name() + "." + Property->name());
	}

	k3d::iobject& m_object;
};

/////////////////////////////////////////////////////////////////////////////
// auto_dialog

/// Handes common functionality for Automatic Dialog Generation (ADG) dialogs
class auto_dialog :
	public k3dUserInterfaceElement
{
	typedef k3dUserInterfaceElement base;
	
public:
	auto_dialog(k3d::iobject& Object) :
		base(&Object, "properties"),
		m_object(Object),
		m_geometry_store(new k3d::options_window_geometry_store()),
		m_idle_handler(0)
	{
		// We want to be notified if the owning object's properties are changed
		k3d::iproperty_collection* const property_collection = dynamic_cast<k3d::iproperty_collection*>(&m_object);
		if(property_collection)
			property_collection->properties_changed_signal().connect(SigC::slot(*this, &auto_dialog::on_properties_changed));
		
		// We want to be notified if the owning object is deleted
		m_object.deleted_signal().connect(SigC::slot(*this, &auto_dialog::on_object_deleted));
	}
	
	virtual ~auto_dialog()
	{
		if(m_idle_handler)
			gtk_idle_remove(m_idle_handler);
			
		// If our object has the mouse focus, release it ...
		k3d::release_mouse_focus(m_object.document(), m_object);

		// Make sure we're not modal ...
		if(IsModal())
			CancelModal();

		// No more events from this point forward ...
		DisconnectAllEvents();

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

		// Get rid of our widget pointers ...
		Clear();
	}

	k3d::iobject& object()
	{
		return m_object;
	}

protected:
	/// Loads a GTKML user interface, and handles some common functionality
	void load_user_interface(sdpxml::Document& GTKML)
	{
		// Load GTKML ...
		assert_warning(Load(GTKML));

		gdk_window_set_role(static_cast<GtkWidget*>(RootWidget())->window, k3d::command_node_path(*this).c_str());
		
		// Set the window title ...
		on_object_renamed();
						
		// Set our screen position and dimensions ...
		restore_geometry();

		// We want to be notified if the owning object is renamed
		m_object.name_changed_signal().connect(SigC::slot(*this, &auto_dialog::on_object_renamed));
		
		// If our object is a mouse event observer, grab the focus ...
		k3d::set_mouse_focus(m_object.document(), m_object);
	}
	
private:
	void restore_geometry()
	{
		int left = 0;
		int top = 0;
		unsigned int width = 0;
		unsigned int height = 0;

		if(m_geometry_store.get() && m_geometry_store->get_window_geometry(k3d::command_node_path(*this), left, top, width, height))
			{
				RootWindow().SetDefaultSize(width, height);
				RootWidget().Show();
#ifdef SDPWIN32
				// Prevent minimized windows not to be restored
				// on Win95 and gtk/gdk ver 2002.03.10 minimized windows
				// top and left are 3000
				//
				int orgleft = 0;
				int orgtop = 0;
				gdk_window_get_root_origin(static_cast<GtkWidget*>(RootWidget())->window, &orgleft, &orgtop);

				int orgwidth = 0;
				int orgheight = 0;
				gdk_window_get_size(static_cast<GtkWidget*>(RootWidget())->window, &orgwidth, &orgheight);

				// Check/correct position/size against visible values
				if(left   > GetSystemMetrics(SM_CXSCREEN) - 20)
					left   = orgleft;
				if(top    > GetSystemMetrics(SM_CYSCREEN) - 20)
					top    = orgtop;
				if(width  > (unsigned int) GetSystemMetrics(SM_CXSCREEN))
					width  = orgwidth;
				if(height > (unsigned int) GetSystemMetrics(SM_CYSCREEN))
					height = orgheight;

				if(left < 20 - (int) width)
					left = 20 - (int) width;
				if(top < 20 - (int) height)
					top = 20 - (int) height;
#endif // SDPWIN32
				gdk_window_move_resize(static_cast<GtkWidget*>(RootWidget())->window, left, top, width, height);
			}
		else
			{
				RootWidget().Show();
			}
	}

	// Event-handling ...
	void OnEvent(sdpGtkEvent* Event)
	{
		// Sanity checks ...
		assert_warning(Event);

		if(Event->Name() == event_configure)
			on_configure();
		else if(Event->Name() == event_key_press)
			on_key_press_event(Event);
		else if(Event->Name() == event_delete)
			on_delete(Event);
		else
			base::OnEvent(Event);
	}

	/// Called by the event system when the user hits the WM close button (template design pattern)
	virtual void on_close() = 0;

	/// Called by the event system when the user hits the WM close button
	void on_delete(sdpGtkEvent* Event)
	{
		((sdpGtkEventWidgetDeleteEvent*)Event)->SetResult(true);
		on_close();
	}
	
	/// Called by the event system when the top-level window is resized
	void on_configure()
	{
		// Serialize our screen geometry (but not in batch mode, since it has a bad habit of trashing the user's stored settings) ...
		if(m_geometry_store.get() && k3d::application().user_interface() && (!k3d::application().user_interface()->batch_mode()))
			{
				int left = 0;
				int top = 0;
				gdk_window_get_root_origin(static_cast<GtkWidget*>(RootWidget())->window, &left, &top);

				int width = 0;
				int height = 0;
				gdk_window_get_size(static_cast<GtkWidget*>(RootWidget())->window, &width, &height);

				m_geometry_store->set_window_geometry(k3d::command_node_path(*this), left, top, width, height);
			}
	}

	/// Called by the event system when the top-level window gets keyboard input ...
	void on_key_press_event(sdpGtkEvent* Event)
	{
		sdpGtkEventWidgetKeyPressEvent& event = static_cast<sdpGtkEventWidgetKeyPressEvent&>(*Event);
		event.SetResult(k3d::keyboard().event_signal().emit(*this, k3d::convert(static_cast<GdkModifierType>(event.Event()->state)), event.Event()->keyval));
	}

	/// Called by the signal system if the owning object gains-or-loses any properties
	void on_properties_changed()
	{
		if(!m_idle_handler)
			m_idle_handler = gtk_idle_add(raw_update_properties, this);
	}

	static gint raw_update_properties(gpointer Data)
	{
		return reinterpret_cast<auto_dialog*>(Data)->update_properties();
	}

	/// Called once by the event system if the owning object has gained-or-lost any properties since the GUI was last idle
	gint update_properties()
	{
		on_update_properties();
		
		m_idle_handler = 0;
		return false;
	}

	/// Override this in derived classes to respond when the owning object has gained-or-lost any properties since the GUI was last idle
	virtual void on_update_properties()
	{
	}

	/// Called by the signal system if the owning object is deleted
	void on_object_deleted()
	{
		delete this;
	}
	
	/// Called by the signal system if the owning object is renamed
	void on_object_renamed()
	{
		return_if_fail(Root());
		RootWindow().SetTitle(m_object.name().c_str());
	}
	
	/// Stores a reference to the owning object
	k3d::iobject& m_object;
	/// Stores an object that can serialize our window configuration
	boost::scoped_ptr<k3d::window_geometry_store> m_geometry_store;
	/// Keeps track of whether we have an idle handler in-effect or not
	guint m_idle_handler;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// auto_object_dialog

class auto_object_dialog :
	public auto_dialog
{
	typedef auto_dialog base;
	
public:
	auto_object_dialog(k3d::iobject& Object) :
		base(Object)
	{
		// Create the user interface GTKML ...
		sdpxml::Document gtkml("gtkml");
		sdpxml::Element& window = gtkml.Append(sdpxml::Element("window", "", sdpxml::Attribute("type", "toplevel"), sdpxml::Attribute("show", "true")));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "delete-event"), sdpxml::Attribute("name", event_delete)));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "configure-event"), sdpxml::Attribute("name", event_configure)));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "key-press-event"), sdpxml::Attribute("name", event_key_press)));
		sdpxml::Element& vbox = window.Append(sdpxml::Element("vbox", "", sdpxml::Attribute("homogeneous", "false")));
		vbox.Append(sdpxml::Element("eventbox", "", sdpxml::Attribute("name", "auto_controls"), sdpxml::Attribute("expand", "true"), sdpxml::Attribute("fill", "true")));
		sdpxml::Element& hbuttonbox = vbox.Append(sdpxml::Element("hbuttonbox", "", sdpxml::Attribute("layout", "end"), sdpxml::Attribute("expand", "false")));
		hbuttonbox.Append(sdpxml::Element("k3dbutton", "Close", sdpxml::Attribute("name", "close")));

		// Load the user interface ...
		load_user_interface(gtkml);

		// Bind the close button ...
		k3d::button::control* const close_button = get_button("close");
		return_if_fail(close_button);
		close_button->signal_activate().connect(SigC::slot(*this, &auto_object_dialog::on_close));

		on_update_properties();
	}

private:
	void on_update_properties()
	{
		if(m_frame.Attached())
			m_frame.Destroy();

		m_auto_controls.reset();

		m_frame.Create();
		m_frame.Show();
		Container("auto_controls").Attach(m_frame);
		m_auto_controls.reset(new auto_controls(this, "auto_controls", object(), auto_controls::WITH_UNDO, m_frame));
	}

	void on_close()
	{
		// Delete the UI, leaving the object behind
		delete this;
	}

	sdpGtkFrame m_frame;
	std::auto_ptr<auto_controls> m_auto_controls;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// auto_tool_dialog

class auto_tool_dialog :
	public auto_dialog
{
	typedef auto_dialog base;
	
public:
	auto_tool_dialog(k3d::iobject& Object, k3d::iproperty& Output, k3d::iproperty& ToolInput, k3d::iproperty& ToolOutput, k3d::iproperty& Input) :
		base(Object),
		m_output(Output),
		m_tool_input(ToolInput),
		m_tool_output(ToolOutput),
		m_input(Input),
		m_change_set(k3d::create_state_change_set())
	{
		// Create the user interface GTKML ...
		sdpxml::Document gtkml("gtkml");
		sdpxml::Element& window = gtkml.Append(sdpxml::Element("window", "", sdpxml::Attribute("type", "toplevel"), sdpxml::Attribute("show", "true")));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "delete-event"), sdpxml::Attribute("name", event_delete)));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "configure-event"), sdpxml::Attribute("name", event_configure)));
		window.Append(sdpxml::Element("event", "", sdpxml::Attribute("signal", "key-press-event"), sdpxml::Attribute("name", event_key_press)));
		sdpxml::Element& vbox = window.Append(sdpxml::Element("vbox", "", sdpxml::Attribute("homogeneous", "false")));
		vbox.Append(sdpxml::Element("eventbox", "", sdpxml::Attribute("name", "auto_controls"), sdpxml::Attribute("expand", "true"), sdpxml::Attribute("fill", "true")));
		sdpxml::Element& hbuttonbox = vbox.Append(sdpxml::Element("hbuttonbox", "", sdpxml::Attribute("layout", "end"), sdpxml::Attribute("expand", "false")));
		hbuttonbox.Append(sdpxml::Element("k3dbutton", "Cancel", sdpxml::Attribute("name", "cancel")));
		hbuttonbox.Append(sdpxml::Element("k3dbutton", "OK", sdpxml::Attribute("name", "ok")));

		// Load the user interface ...
		load_user_interface(gtkml);

		// Bind the cancel button ...
		k3d::button::control* const cancel_button = get_button("cancel");
		return_if_fail(cancel_button);
		cancel_button->signal_activate().connect(SigC::slot(*this, &auto_tool_dialog::on_close));

		// Bind the OK button ...
		k3d::button::control* const ok_button = get_button("ok");
		return_if_fail(ok_button);
		ok_button->signal_activate().connect(SigC::slot(*this, &auto_tool_dialog::on_ok));

		// Hook the tool into the construction chain ...
		k3d::idag::dependencies_t dependencies;
		dependencies.insert(std::make_pair(&m_tool_input, &m_output));
		dependencies.insert(std::make_pair(&m_input, &m_tool_output));
		object().document().dag().set_dependencies(dependencies);
		
		// Setup auto controls
		on_update_properties();
	}

	void on_update_properties()
	{
		if(m_frame.Attached())
			m_frame.Destroy();

		m_auto_controls.reset();
									
		m_frame.Create();
		m_frame.Show();		
		Container("auto_controls").Attach(m_frame);
		m_auto_controls.reset(new auto_controls(this, "auto_controls", object(), auto_controls::NO_UNDO, m_frame));
	}

	void on_close()
	{
		// Restore the original construction chain ...
		k3d::idag::dependencies_t dependencies;
		dependencies.insert(std::make_pair(&m_input, &m_output));
		object().document().dag().set_dependencies(dependencies);

		// Schedule a display update ...
		k3d::viewport::redraw_all(object().document(), k3d::iviewport::ASYNCHRONOUS);

		// Delete the object (which will delete the UI automatically)
		k3d::delete_objects(object().document(), k3d::make_collection<k3d::objects_t>(&object()));
	}

	void on_ok()
	{
		// This gets a little convoluted ... turn-on state recording momentarily while we create a FrozenMesh object ...
		object().document().state_recorder().start_recording(m_change_set);
		k3d::iobject* const frozen_mesh = k3d::create_document_plugin(k3d::classes::FrozenMesh(), object().document(), object().name());
		m_change_set = object().document().state_recorder().stop_recording();
		return_if_fail(frozen_mesh);

		k3d::imesh_sink* const mesh_sink = dynamic_cast<k3d::imesh_sink*>(frozen_mesh);
		return_if_fail(mesh_sink);
		k3d::iproperty* const frozen_mesh_input = &mesh_sink->mesh_sink_input();
		
		k3d::imesh_source* const mesh_source = dynamic_cast<k3d::imesh_source*>(frozen_mesh);
		return_if_fail(mesh_source);
		k3d::iproperty* const frozen_mesh_output = &mesh_source->mesh_source_output();

		// Insert the frozen mesh into the construction chain and grab a copy of the modified output ...
		k3d::idag::dependencies_t dependencies;
		dependencies.insert(std::make_pair(frozen_mesh_input, &m_tool_output));
		object().document().dag().set_dependencies(dependencies);
		
		// Reading the frozen mesh output forces it to create a copy of its input ...
		frozen_mesh_output->value();
						
		// Restore the original construction chain, removing the frozen mesh and the tool from the picture ...
		dependencies.clear();
		dependencies.insert(std::make_pair(&m_input, &m_output));
		dependencies.insert(std::make_pair(&m_tool_input, static_cast<k3d::iproperty*>(0)));
		dependencies.insert(std::make_pair(frozen_mesh_input, static_cast<k3d::iproperty*>(0)));
		object().document().dag().set_dependencies(dependencies);
	
		// Turn on state recording again, and insert the frozen mesh into the construction chain ...
		object().document().state_recorder().start_recording(m_change_set);
		dependencies.clear();
		dependencies.insert(std::make_pair(frozen_mesh_input, &m_output));
		dependencies.insert(std::make_pair(&m_input, frozen_mesh_output));
		object().document().dag().set_dependencies(dependencies);
		object().document().state_recorder().commit_change_set(object().document().state_recorder().stop_recording(), object().name());
	
		// Delete the tool (which will delete the UI automatically)
		k3d::delete_objects(object().document(), k3d::make_collection<k3d::objects_t>(&object()));
	}
	
private:
	k3d::iproperty& m_output;
	k3d::iproperty& m_tool_input;
	k3d::iproperty& m_tool_output;
	k3d::iproperty& m_input;

	std::auto_ptr<k3d::istate_change_set> m_change_set;

	sdpGtkFrame m_frame;
	std::auto_ptr<auto_controls> m_auto_controls;
};

} // namespace

namespace k3d
{

void create_auto_object_dialog(k3d::iobject& Object)
{
	new auto_object_dialog(Object);
}

void create_auto_tool_dialog(k3d::iobject& Object, iproperty& Output, iproperty& ToolInput, iproperty& ToolOutput, iproperty& Input)
{
	new auto_tool_dialog(Object, Output, ToolInput, ToolOutput, Input);
}

} // namespace k3d


