// 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 Provides an implementation of k3d::iuser_interface
		\author Tim Shead (tshead@k-3d.com)
*/

#include "auto_dialog.h"
#include "clipboard.h"
#include "color_bezier_channel_properties.h"
#include "gtkurl/gtkurl.h"
#include "k3ddialog.h"
#include "scalar_bezier_channel_properties.h"
#include "user_interface.h"
#include "viewport_window.h"

#include <k3dsdk/application.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/iprivate_user_interface.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/result.h>
#include <k3dsdk/string_cast.h>
#include <k3dsdk/icommand_node.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iplugin_factory.h>
#include <k3dsdk/iscript_engine.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/keyboard.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/system_functions.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/xml_utility.h>

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

#include <boost/filesystem/operations.hpp>

#include <iomanip>
#include <iostream>

#include <gdk/gdkkeysyms.h>

using k3d::xml::safe_element;

// We seem to have a clash with X ...
#ifdef RootWindow
#undef RootWindow
#endif // RootWindow

namespace
{

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// mouse_diagram

std::string mouse_diagram(const k3d::iuser_interface::mouse_action_t Action)
{
	switch(Action)
		{
			case k3d::iuser_interface::MOUSE_DONE:
				return "mouse_dim";
			case k3d::iuser_interface::MB_NONE:
				return "mouse";
			case k3d::iuser_interface::LMB_DRAG:
				return "mouse_lmb";
			case k3d::iuser_interface::LMB_CLICK:
				return "mouse_lmb_1";
			case k3d::iuser_interface::LMB_DOUBLE_CLICK:
				return "mouse_lmb_2";
			case k3d::iuser_interface::MMB_DRAG:
				return "mouse_mmb";
			case k3d::iuser_interface::MMB_CLICK:
				return "mouse_mmb_1";
			case k3d::iuser_interface::MMB_DOUBLE_CLICK:
				return "mouse_mmb_2";
			case k3d::iuser_interface::RMB_DRAG:
				return "mouse_rmb";
			case k3d::iuser_interface::RMB_CLICK:
				return "mouse_rmb_1";
			case k3d::iuser_interface::RMB_DOUBLE_CLICK:
				return "mouse_rmb_2";
			case k3d::iuser_interface::LMBRMB_DRAG:
				return "mouse_lmb_rmb";
		}

	return std::string();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// modifiers_diagram

std::string modifiers_diagram(const k3d::key_modifiers Modifiers)
{
	return "modifiers_" + k3d::string_cast(Modifiers & k3d::key_modifiers().set_shift().set_lock().set_control());
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// mouse_description

std::string mouse_description(const k3d::iuser_interface::mouse_action_t Action)
{
	std::string result;

	switch(Action)
		{
			case k3d::iuser_interface::MOUSE_DONE:
				break;
			case k3d::iuser_interface::MB_NONE:
				result = "Drag the mouse.\n";
				break;
			case k3d::iuser_interface::LMB_DRAG:
				result = "Hold down the Left Mouse Button (LMB) and drag.\n";
				break;
			case k3d::iuser_interface::LMB_CLICK:
				result = "Click the Left Mouse Button (LMB).\n";
				break;
			case k3d::iuser_interface::LMB_DOUBLE_CLICK:
				result = "Double-click the Left Mouse Button (LMB).\n";
				break;
			case k3d::iuser_interface::MMB_DRAG:
				result = "Hold down the Middle Mouse Button (MMB) and drag.\n";
				break;
			case k3d::iuser_interface::MMB_CLICK:
				result = "Click the Middle Mouse Button (MMB).\n";
				break;
			case k3d::iuser_interface::MMB_DOUBLE_CLICK:
				result = "Double-click the Middle Mouse Button (MMB).\n";
				break;
			case k3d::iuser_interface::RMB_DRAG:
				result = "Hold down the Right Mouse Button (RMB) and drag.\n";
				break;
			case k3d::iuser_interface::RMB_CLICK:
				result = "Click the Right Mouse Button (RMB).\n";
				break;
			case k3d::iuser_interface::RMB_DOUBLE_CLICK:
				result = "Double-click the Right Mouse Button (RMB).\n";
				break;
			case k3d::iuser_interface::LMBRMB_DRAG:
				result = "Hold the the Left (LMB) and Right (RMB) mouse buttons and drag.\n";
				break;
			default:
				return_val_if_fail(0, result);	// Did you add a mouse action without telling me?
		}

	return result;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// modifiers_description

std::string modifiers_description(const k3d::key_modifiers Modifiers)
{
	std::string result;

	if(Modifiers.lock())
		result += "Turn on CAPS LOCK.\n";
		
	if(Modifiers.shift())
		result += "Hold down SHIFT.\n";
		
	if(Modifiers.control())
		result += "Hold down CTRL.\n";
		
	if(Modifiers.mod1())
		result += "Hold down ALT.\n";

	return result;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// action_description

std::string action_description(const std::string Message, const k3d::iuser_interface::mouse_action_t Action, const k3d::key_modifiers Modifiers)
{
	std::string result;

	if(Message.size())
		result += Message + "\n";

	const std::string modifiersdescription = modifiers_description(Modifiers);
	if(modifiersdescription.size())
		result += modifiersdescription;

	const std::string mousedescription = mouse_description(Action);
	if(mousedescription.size())
		result += mousedescription;

	return result;
}

/////////////////////////////////////////////////////////////////////////////
// tutorial_message_implementation

const std::string control_close = "close";
const std::string control_cruisecontrol = "cruisecontrol";
const std::string control_speed = "speed";
const std::string control_currentspeed = "currentspeed";

/// Creates a modal message box that can be displayed by interactive tutorial scripts
class tutorial_message_implementation :
	public k3dDialog
{
public:
	tutorial_message_implementation() :
		base(dynamic_cast<k3d::icommand_node*>(&k3d::application()), "tutorial_message", new k3d::options_window_geometry_store()),
		m_CurrentSpeed(k3d::application().options().tutorial_speed()),
		m_cancelled(0)
	{
		// Load the UI ...
		return_if_fail(LoadGTKMLTemplate("tutorial_message.gtkml"));

		// Set default appearance ...
		SetMouseDiagram("mouse_dim");
		SetModifiersDiagram("modifiers_dim");

		// Enable hot URLs in the message area ...
		gtkurl_attach(Text("text"));

		BlockAllEvents();

		// If we're in batch mode, enable cruise control ...
		ToggleButton(control_cruisecontrol).SetState(k3d::application().user_interface() && k3d::application().user_interface()->batch_mode());

		// Setup the tutorial speed control ...
		Adjustment(control_speed).SetValue(log10(m_CurrentSpeed));

		std::stringstream buffer;
		buffer << "x " << std::setprecision(2) << m_CurrentSpeed;
		Label(control_currentspeed).SetText(buffer.str().data());

		UnblockAllEvents();

		// We want to catch all keyboard input ...
		k3d::keyboard().event_signal().connect(SigC::slot(*this, &tutorial_message_implementation::on_key_event));

		gtk_window_set_position(RootWindow(), GTK_WIN_POS_CENTER);
		Show();
	}

	~tutorial_message_implementation()
	{
		m_deleted_signal.emit();
	}

	void update(const std::string& Message, bool& Cancelled)
	{
		// Reset the cancelled flag ...
		m_cancelled = &Cancelled;

		// Make sure we're visible and focused ...
		RootWidget().Realize();

		GdkWindow* window = GTK_WIDGET(RootWidget().Object())->window;
		return_if_fail(window);

		gdk_window_show(window);
		gdk_window_raise(window);
	//	XSetInputFocus(GDK_DISPLAY(), GDK_WINDOW_XWINDOW(window), RevertToNone, GDK_CURRENT_TIME);

		SetMessage(Message);
		SetMouseDiagram("mouse_dim");
		SetModifiersDiagram("modifiers_dim");

		Widget("continue").SetSensitive(true);
		Widget("quit").SetSensitive(true);

		Widget("continue").InteractiveShow(m_CurrentSpeed, true);
		Widget("continue").InteractiveWarpPointer(m_CurrentSpeed, false, false);

		if(ToggleButton(control_cruisecontrol).GetState())
			Button("continue").InteractiveActivate();
		else
			DoModal();

		m_cancelled = 0;
	}

	void update(const std::string& Message, const k3d::iuser_interface::mouse_action_t Action, const k3d::key_modifiers Modifiers)
	{
		gdk_window_show(GTK_WIDGET(RootWidget().Object())->window);
		gdk_window_raise(GTK_WIDGET(RootWidget().Object())->window);

		SetMessage(action_description(Message, Action, Modifiers));
		SetMouseDiagram(mouse_diagram(Action));
		SetModifiersDiagram(modifiers_diagram(Modifiers));

		Widget("continue").SetSensitive(false);
		Widget("quit").SetSensitive(false);

		switch(Action)
			{
				case k3d::iuser_interface::MOUSE_DONE:
				case k3d::iuser_interface::MB_NONE:
				case k3d::iuser_interface::LMB_DRAG:
				case k3d::iuser_interface::MMB_DRAG:
				case k3d::iuser_interface::RMB_DRAG:
				case k3d::iuser_interface::LMBRMB_DRAG:
					break;
				case k3d::iuser_interface::LMB_CLICK:
				case k3d::iuser_interface::LMB_DOUBLE_CLICK:
				case k3d::iuser_interface::MMB_CLICK:
				case k3d::iuser_interface::MMB_DOUBLE_CLICK:
				case k3d::iuser_interface::RMB_CLICK:
				case k3d::iuser_interface::RMB_DOUBLE_CLICK:
					{
	//					sdpGtkEvent* timeout = MapEvent("timeout", "reset", RootWidget(), false);
	//					static_cast<sdpGtkEventTimeout*>(timeout)->SetDuration(2000);
	//					timeout->Connect();
					}
					break;
				default:
					return_if_fail(0);	// Did you add a mouse action without telling me?
			}
	}

	// k3d::icommand_node implementation
	bool execute_command(const std::string& Command, const std::string& Arguments)
	{
		if(Command == control_close)
			{
				delete this;
				return true;
			}

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

	SigC::Signal0<void> m_deleted_signal;

private:
	typedef k3dDialog base;

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

		if(Event->Name() == "continue")
			OnContinue();
		else if(Event->Name() == "quit")
			OnQuit();
		else if(Event->Name().substr(0, 5) == "mouse")
			OnMouseEvent(Event);
		else if(Event->Name() == control_cruisecontrol)
			OnCruiseControl();
		else if(Event->Name() == control_speed)
			OnSpeed();
		else
			base::OnEvent(Event);
	}

	void OnDelete(sdpGtkEvent* const Event)
	{
		// Sanity checks ...
		assert_warning(Event);

		// Kill the application ...
		((sdpGtkEventWidgetDeleteEvent*)Event)->SetResult(true);

		delete this;
	}

	bool on_key_event(k3d::icommand_node&, k3d::key_modifiers, k3d::ikeyboard::key_value_t Key)
	{
		// We're only interested in the Escape key ...
		if(Key != GDK_Escape)
			return false;

		// If we're in cruise control, turn it off ...
		if(ToggleButton(control_cruisecontrol).GetState())
			{
				ToggleButton(control_cruisecontrol).SetState(false);
				k3d::message("Cruise Control disabled", "Tutorial:");

				return true;
			}

/*
		// Otherwise, give the user a chance to cancel the tutorial ...
		std::vector<std::string> buttons;
		buttons.push_back("Yes");
		buttons.push_back("No");
		if(1 == k3d::query_message("Stop Tutorial?", "Tutorial:", 1, buttons))
			{
				// \todo We shouldn't assume that we're using JavaScript here, but all our tutorials are JavaScript for the time being
				if(k3d::application().script_engine(k3d::classes::JavaScriptEngine()))
					k3d::application().script_engine(k3d::classes::JavaScriptEngine())->halt();

				k3d::message("Tutorial stopped", "Tutorial:");
				delete this;
			}
*/
		return true;
	}

	void OnMouseEvent(sdpGtkEvent* const Event)
	{
		static_cast<sdpGtkEventTimeout*>(Event)->SetResult(false);

		SetMouseDiagram(Event->Name());
	}

	void OnSpeed()
	{
		m_CurrentSpeed = pow(10.0, Adjustment(control_speed).Value());
		std::stringstream buffer;
		buffer << "x " << std::setprecision(2) << m_CurrentSpeed;
		Label(control_currentspeed).SetText(buffer.str().data());

		k3d::application().options().set_tutorial_speed(m_CurrentSpeed);
	}

	void OnCruiseControl()
	{
		// If the user turned-off cruise control, we don't care ...
		if(!ToggleButton(control_cruisecontrol).GetState())
			return;

		// Give the user a chance to cancel ...
		std::vector<std::string> buttons;
		buttons.push_back("Yes");
		buttons.push_back("No");
		unsigned int result = k3d::query_message("Enable Cruise Control?  Once you hit \"Continue\" the tutorial will run automatically without stopping.\nYou may hit the ESC key at any time during the tutorial to cancel Cruise Control.", "Tutorial:", 1, buttons);
		if(1 != result)
			ToggleButton(control_cruisecontrol).SetState(false);
	}

	void OnContinue()
	{
		// To prevent the user from clicking too soon ...
		if(!IsModal())
			return;

		// Sanity checks ...
		assert_warning(m_cancelled);
		*m_cancelled = false;

		Widget("continue").SetSensitive(false);
		Widget("quit").SetSensitive(false);

		CancelModal();
	}

	void OnQuit()
	{
		if(!IsModal())
			return;

		// Sanity checks ...
		assert_warning(m_cancelled);
		*m_cancelled = true;

		CancelModal();
		delete this;
	}

	void SetMessage(std::string Message)
	{
		// Prevent needless updates!
		if(m_CurrentMessage == Message)
			return;

		m_CurrentMessage = Message;

		gtkurl_uncheck_all(Text("text"));
		Text("text").DeleteText(0, -1);

	// The Win32 GTK+ port has a little glitch with clearing text from the text widget ...
#ifdef SDPWIN32
		Text("text").QueueClear();
		sdpGtkHandlePendingEvents();
#endif

		int position = 0;
		Text("text").InsertText(Message.c_str(), Message.size(), &position);

		// Highlight URLs ...
		gtkurl_check_all(Text("text"));
		
		// Ensure we're looking at the top of the text, in case it can't all be displayed at once ...
		Text("text").SetPosition(0);
	}

	void SetMouseDiagram(std::string mouse_diagram)
	{
		// Sanity checks ...
		assert_warning(mouse_diagram.size());

		// Avoid unnecessary updates ...
		if(m_Currentmouse_diagram == mouse_diagram)
			return;

		Pixmap(mouse_diagram.c_str()).Show();

		if(m_Currentmouse_diagram.size())
			Pixmap(m_Currentmouse_diagram.c_str()).Hide();

		m_Currentmouse_diagram = mouse_diagram;
	}

	void SetModifiersDiagram(std::string modifiers_diagram)
	{
		// Sanity checks ...
		return_if_fail(modifiers_diagram.size());

		// Avoid unnecessary updates ...
		if(m_Currentmodifiers_diagram == modifiers_diagram)
			return;

		Pixmap(modifiers_diagram.c_str()).Show();

		if(m_Currentmodifiers_diagram.size())
			Pixmap(m_Currentmodifiers_diagram.c_str()).Hide();

		m_Currentmodifiers_diagram = modifiers_diagram;
	}

	std::string m_CurrentMessage;
	std::string m_Currentmouse_diagram;
	std::string m_Currentmodifiers_diagram;

	double m_CurrentSpeed;

	bool* m_cancelled;
};

/////////////////////////////////////////////////////////////////////////////
// message_box_implementation

/// Creates an (optionally) modal message box for displaying simple messages
class message_box_implementation :
	public k3dDialog
{
public:

	/**
		 \brief Create a message box with a caption and any number of buttons.
		 \param DefaultButton One-based index of the button selected by default.  If DefaultButton is 0, then no button is selected by default.
	*/
	message_box_implementation(const std::string Message, const std::string Title, const unsigned int DefaultButton, const std::vector<std::string> Buttons, const std::string DialogTemplate) :
		base(dynamic_cast<k3d::icommand_node*>(&k3d::application()), "messagebox", 0),
		m_Result(0),
		m_LastButton(0)
	{
		// Sanity checks ...
		assert_warning(DialogTemplate.size());

		// create the dialog box
		return_if_fail(LoadGTKMLTemplate(DialogTemplate));

		// Set our window title
		RootWindow().SetTitle(Title.c_str());
		// Set the dialog message
		Label("message").SetText(Message.c_str());

		// Center it ...
		gtk_window_set_position(RootWindow(), GTK_WIN_POS_CENTER);

		add_buttons(Buttons);

		// if there was a default button specified, then make it default
		if(DefaultButton)
			{
				std::string defaultname = "button";
				defaultname += sdpToString(DefaultButton);
				Button(defaultname.c_str()).GrabDefault();
			}

		RootWidget().ShowAll();
	}

	/**
		 \brief Make the message box modal and return the button clicked.
		 \return the number of the button pressed.  0 is returned if the dialog box is closed by the user.
	*/
	unsigned int run()
	{
		DoModal();
		unsigned int result = m_Result;
		delete this;
		return result;
	}

private:
	void OnEvent(sdpGtkEvent* Event)
	{
		// Sanity checks ...
		assert_warning(Event);
		const std::string name(Event->Name());
		const std::string signal(Event->Signal());

		// if the name of the command starts with "button", then the user has clicked one of our buttons
		if(0 == name.find("button"))
			{
				// set m_Result to be the button number that was clicked
				m_Result = k3d::from_string(k3d::right(name, name.size()-6), 0);

				// no longer modal.  we can return a value from Run() now.
				if(IsModal())
					CancelModal();
				else
					delete this;
			}
		else
			base::OnEvent(Event);
	}

	void OnDelete(sdpGtkEvent* Event)
	{
		// we want Run() to return 0
		m_Result = 0;

		if(IsModal())
			CancelModal();
		else
			delete this;
	}

	/**
		 \brief Add a button
		 \param Label the text of the button to be added.
		 \return the number of the button added.
	*/
	unsigned int add_button(std::string Label)
	{
		// if there is no label specified, then just return
		if(!Label.size())
			return 0;

		// the name for the button will be "button" + the button number.  button1, button2, etc.
		std::string name = "button";
		name += sdpToString(++m_LastButton);

		sdpGtkButton button;
		button.Create(Label.c_str());
		// make sure the button can default
		GTK_WIDGET_SET_FLAGS(button.Object(), GTK_CAN_DEFAULT);
		Container("buttonbox").Attach(button);
		MapObject(name.c_str(), button.Object());

		// if the button is clicked, issue an event with the button's name as the event name
		MapEvent("clicked", name.c_str(), false, button, true);

		button.Show();
		return m_LastButton;
	}

	/**
		 \brief Add any number of buttons.
		 \param Buttons a collection of buttons to be added.
	 */
	void add_buttons(const std::vector<std::string> Buttons)
	{
		// Create the buttons
		for(std::vector<std::string>::const_iterator button = Buttons.begin(); button != Buttons.end(); button++)
			add_button(*button);
	}

	/**
		 \brief the number of the button pressed.  This value is returned by Run().
	*/
	unsigned int m_Result;
	/**
		 \brief the number of the last button to be added to the dialog box.
	*/
	unsigned int m_LastButton;

	typedef k3dDialog base;
};

/////////////////////////////////////////////////////////////////////////////
// user_interface_implementation

const std::string control_tutorialmessage = "tutorialmessage";

class user_interface_implementation :
	public k3d::command_node,
	public k3d::iuser_interface,
	public k3d::ideletable,
	public SigC::Object
{
public:
	user_interface_implementation(const bool BatchMode) :
		base("ui"),
		m_batch_mode(BatchMode),
		m_tutorial_message(0)
	{
	}

	~user_interface_implementation()
	{
//		delete m_tutorial_message;
	}

	bool batch_mode()
	{
		return m_batch_mode;
	}

	void browser_navigate(const std::string& URL)
	{
		// Get the user's preferred HTML viewer ...
		std::string commandline = k3d::application().options().html_viewer();
		return_if_fail(commandline.size());

		k3d::formatted_replace(commandline, '%', "p", URL);

		popen(commandline.c_str(), "r");
	}

	void message(const std::string& Message, const std::string& Title)
	{
		std::vector<std::string> buttons;
		buttons.push_back("OK");

		message_box_implementation* const dialog = new message_box_implementation(Message, Title, 1, buttons, "message.gtkml");
		if(!m_batch_mode)
			dialog->run();
	}

	void error_message(const std::string& Message, const std::string& Title)
	{
		std::vector<std::string> buttons;
		buttons.push_back("OK");

		message_box_implementation* const dialog = new message_box_implementation(Message, Title, 1, buttons, "error.gtkml");
		if(!m_batch_mode)
			dialog->run();
	}

	unsigned int query_message(const std::string& Message, const std::string& Title, const unsigned int DefaultButton, const std::vector<std::string> Buttons)
	{
		message_box_implementation* const dialog = new message_box_implementation(Message, Title, DefaultButton, Buttons, "query.gtkml");
		if(!m_batch_mode)
			return dialog->run();

		return 0;
	}

	bool tutorial_message(const std::string& Message)
	{
		k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_tutorialmessage, Message);

		bool cancelled = false;
		tutorial_message().update(Message, cancelled);
		return !cancelled;
	}

	void tutorial_mouse_message(const std::string& Message, const mouse_action_t Action, const k3d::key_modifiers Modifiers)
	{
		tutorial_message().update(Message, Action, Modifiers);
	}

	bool get_file_path(const std::string& Type, const std::string& Prompt, const bool PromptOverwrite, const boost::filesystem::path& OldPath, boost::filesystem::path& Result)
	{
		// Sanity checks ...
		return_val_if_fail(!Type.empty(), false);
		return_val_if_fail(!Prompt.empty(), false);

		// Get the user's path option ...
		boost::filesystem::path startpath = k3d::application().options().most_recent_path(Type);
		if(!OldPath.empty())
			startpath = OldPath;

		// GtkFileSelector is mighty picky, so ensure that our start path is a path (not a file) and end it with a slash ...
		if(boost::filesystem::exists(startpath))
			{
			 	if(!boost::filesystem::is_directory(startpath))
					startpath = startpath.branch_path();
			}
		else
			{
				startpath = startpath.branch_path();
			}
			
		const std::string gtk_start_path = startpath.native_file_string() + G_DIR_SEPARATOR;

		// Prompt the user for a filepath (we append a slash because GtkFileSelector gets confused without it) ...
		sdpGtkFileSelector fileselector(Prompt.c_str(), gtk_start_path.c_str());
		fileselector.DoModal();
		if(!fileselector.OK())
			return false;

		// Make sure we're not overwriting Precious Memories of The Way We Were ...
		Result = boost::filesystem::path(fileselector.FilePath(), boost::filesystem::native);
		if(PromptOverwrite)
			{
				if(boost::filesystem::exists(Result))
					{
						std::vector<std::string> buttons;
						buttons.push_back("Yes");
						buttons.push_back("No");
						if(1 != query_message("Overwrite " + Result.native_file_string() + "?", Prompt, 1, buttons))
							return false;
					}
			}

		// Store the path for posterity ...
		k3d::application().options().set_most_recent_path(Type, Result.branch_path());

		return true;
	}

	void set_clipboard(const std::string& Text)
	{
		m_clipboard.set_text(Text);
		return_if_fail(m_clipboard.grab_selection());
	}

	bool show(k3d::iunknown& Object)
	{
		// See if it's an object that we can special-case ...
		k3d::iobject* const object = dynamic_cast<k3d::iobject*>(&Object);
		if(object && k3d::classes::ScalarBezierChannel() == object->factory().class_id())
			{
				create_scalar_bezier_channel_properties(*object);
				return true;
			}
		if(object && k3d::classes::ColorBezierChannel() == object->factory().class_id())
			{
				create_color_bezier_channel_properties(*object);
				return true;
			}
	
		// See if it supplies its own UI ...
		k3d::iprivate_user_interface* const private_user_interface = dynamic_cast<k3d::iprivate_user_interface*>(&Object);
		if(private_user_interface)
			{
				private_user_interface->show_user_interface();
				return true;
			}
			
		// See if it's a document object ...
		if(object)
			{
				create_auto_object_dialog(*object);
				return true;
			}

		return false;
	}

	bool show_viewport(k3d::iviewport& Viewport)
	{
		// Get the document (required) ...
		k3d::iobject* const object = dynamic_cast<k3d::iobject*>(&Viewport);
		return_val_if_fail(object, false);
		
		// Create a viewport window and attach it ...
		k3d::viewport::window* const window = new k3d::viewport::window(object->document());
		window->attach(Viewport);
		
		return true;
	}

	bool execute_command(const std::string& Command, const std::string& Arguments)
	{
		if(Command == control_tutorialmessage)
			return tutorial_message(Arguments);

		return k3d::command_node::execute_command(Command, Arguments);
	}

private:
	typedef k3d::command_node base;

	tutorial_message_implementation& tutorial_message()
	{
		if(!m_tutorial_message)
			{
				m_tutorial_message = new tutorial_message_implementation();
				m_tutorial_message->m_deleted_signal.connect(SigC::slot(*this, &user_interface_implementation::on_tutorial_message_deleted));
			}

		return *m_tutorial_message;
	}

	void on_tutorial_message_deleted()
	{
		m_tutorial_message = 0;
	}

	const bool m_batch_mode;
	tutorial_message_implementation* m_tutorial_message;
	k3d::clipboard m_clipboard;
};

} // namespace

namespace k3d
{

iuser_interface* create_user_interface(const bool BatchMode)
{
	return new user_interface_implementation(BatchMode);
}

} // namespace k3d


