/* === S Y N F I G ========================================================= */
/*!	\file cellrenderer_value.cpp
**	\brief Template File
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**  Copyright (c) 2011 Carlos López
**
**	This package 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 package 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.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include <gtkmm/label.h>
#include <ETL/stringf>
#include <gtkmm/celleditable.h>
#include <gtkmm/editable.h>
#include <gtkmm/entry.h>
#include <gtkmm/eventbox.h>

#include "app.h"
#include "widgets/widget_value.h"
#include "widgets/widget_vector.h"
#include "widgets/widget_filename.h"
#include "widgets/widget_enum.h"
#include "widgets/widget_color.h"
#include "widgets/widget_canvaschooser.h"
#include "widgets/widget_time.h"

#include "cellrenderer_gradient.h"
#include "cellrenderer_value.h"

#include <synfig/valuenodes/valuenode_bone.h>
#include <synfig/transformation.h>

#include "widgets/widget_gradient.h"
#include "dialogs/dialog_gradient.h"
#include "dialogs/dialog_color.h"
#include <gtkmm/textview.h>

#include <gdkmm/general.h>

#include <gui/localization.h>

#include <gui/exception_guard.h>

#endif

using namespace synfig;
using namespace etl;
using namespace std;
using namespace studio;

/* === M A C R O S ========================================================= */

#define DIGITS		15

/* === G L O B A L S ======================================================= */

class studio::ValueBase_Entry : public Gtk::CellEditable, public Gtk::EventBox
{
	Glib::ustring     path;
	Widget_ValueBase *valuewidget;
	bool              edit_done_called;
	Gtk::Widget      *parent;
public:
	ValueBase_Entry():
		Glib::ObjectBase(typeid(ValueBase_Entry))
	{
		parent           = 0;
		edit_done_called = false;
/*
		  Gtk::HBox *const hbox = new Gtk::HBox(false, 0);
		  add(*Gtk::manage(hbox));

		  Gtk::Entry *entry_ = new Gtk::Entry();
			entry_->set_text("bleh");
		  hbox->pack_start(*Gtk::manage(entry_), Gtk::PACK_EXPAND_WIDGET);
		  entry_->set_has_frame(false);
		  entry_->gobj()->is_cell_renderer = true; // XXX

*/
		valuewidget = manage(new class Widget_ValueBase());
		valuewidget->inside_cellrenderer();
		add(*valuewidget);
		valuewidget->show();

		//set_can_focus(true);
		//set_events(Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);

		/*
		set_events(//(Gdk::ALL_EVENTS_MASK)
		~(	Gdk::EXPOSURE_MASK
			| Gdk::ENTER_NOTIFY_MASK
			| Gdk::LEAVE_NOTIFY_MASK
			| Gdk::FOCUS_CHANGE_MASK
			| Gdk::STRUCTURE_MASK
			| Gdk::PROPERTY_CHANGE_MASK
			| Gdk::VISIBILITY_NOTIFY_MASK
			| Gdk::PROXIMITY_IN_MASK
			| Gdk::PROXIMITY_OUT_MASK
			| Gdk::SUBSTRUCTURE_MASK
		)
		);
		*/
		//signal_editing_done().connect(sigc::mem_fun(*this, &studio::ValueBase_Entry::hide));
		//signal_remove_widget().connect(sigc::mem_fun(*this, &studio::ValueBase_Entry::hide));

		show_all_children();

		//signal_show().connect(sigc::mem_fun(*this, &ValueBase_Entry::grab_focus));
	}
	~ValueBase_Entry()
	{
	}

	void on_editing_done()
	{
		hide();
		if (parent) parent->grab_focus();
		if (!edit_done_called)
		{
			edit_done_called = true;
			Gtk::CellEditable::on_editing_done();
		}
		else
		{
			synfig::error("on_editing_done(): Called twice!");
		}
	}

	void set_parent(Gtk::Widget* x) { parent = x; }

	void on_remove_widget()
	{
		hide();
		edit_done_called = true;
		if (parent) parent->grab_focus();
		Gtk::CellEditable::on_remove_widget();
	}

	void start_editing_vfunc(GdkEvent */*event*/)
	{
		SYNFIG_EXCEPTION_GUARD_BEGIN()
		valuewidget->signal_activate().connect(sigc::mem_fun(*this,
			&studio::ValueBase_Entry::editing_done));
		show();
		//valuewidget->grab_focus();
		//get_window()->set_focus(*valuewidget);
		SYNFIG_EXCEPTION_GUARD_END()
	}

	bool on_event(GdkEvent *event)
	{
		SYNFIG_EXCEPTION_GUARD_BEGIN()
		if (event->any.type == GDK_BUTTON_PRESS
		 || event->any.type == GDK_2BUTTON_PRESS
		 || event->any.type == GDK_KEY_PRESS
		 || event->any.type == GDK_KEY_RELEASE
		 || event->any.type == GDK_SCROLL
		 || event->any.type == GDK_3BUTTON_PRESS )
			return true;
		return Gtk::EventBox::on_event(event);
		SYNFIG_EXCEPTION_GUARD_END_BOOL(true)
	}

	void on_grab_focus()
	{
		Gtk::EventBox::on_grab_focus();
		if (valuewidget)
			valuewidget->grab_focus();
	}

	void set_path(const Glib::ustring &p)
	{
		path = p;
	}

	void set_value(const synfig::ValueBase &data)
	{
		if (valuewidget)
			valuewidget->set_value(data);
		//valuewidget->grab_focus();
	}

	void set_canvas(const etl::handle<synfig::Canvas> &data)
	{
		assert(data);
		if (valuewidget)
			valuewidget->set_canvas(data);
	}

	void set_param_desc(const synfig::ParamDesc &data)
	{
		if (valuewidget)
			valuewidget->set_param_desc(data);
	}

	void set_value_desc(const synfigapp::ValueDesc &data)
	{
		if (valuewidget)
			valuewidget->set_value_desc(data);
	}

	void set_child_param_desc(const synfig::ParamDesc &data)
	{
		if (valuewidget)
			valuewidget->set_child_param_desc(data);
	}

	const synfig::ValueBase &get_value()
	{
		if (valuewidget)
			return valuewidget->get_value();

		warning("%s:%d this code shouldn't be reached", __FILE__, __LINE__);
		return *(new synfig::ValueBase());
	}

	const Glib::ustring &get_path()
	{
		return path;
	}

};

/* === P R O C E D U R E S ================================================= */

bool get_paragraph(synfig::String& text)
{
	return App::dialog_paragraph(_("Text Paragraph"), _("Enter text here:"), text);
}

/* === M E T H O D S ======================================================= */

CellRenderer_ValueBase::CellRenderer_ValueBase():
	Glib::ObjectBase          (typeid(CellRenderer_ValueBase)),
	property_value_	          (*this, "value",                   synfig::ValueBase()),
	property_canvas_          (*this, "canvas",      etl::handle<synfig::Canvas>()),
	property_param_desc_      (*this, "param_desc",              synfig::ParamDesc()),
	property_value_desc_      (*this, "value_desc",           synfigapp::ValueDesc()),
	property_child_param_desc_(*this, "child_param_desc",        synfig::ParamDesc()),
	edit_value_done_called    (false),
	value_entry()
{
	CellRendererText::signal_edited().connect(sigc::mem_fun(*this,
		&CellRenderer_ValueBase::string_edited_));

	Pango::AttrList attr_list;
	{
		Pango::AttrInt pango_size(Pango::Attribute::create_attr_size(Pango::SCALE*8));
		pango_size.set_start_index(0);
		pango_size.set_end_index(64);
		attr_list.change(pango_size);
	}
	property_attributes()   = attr_list;

	property_foreground()   = Glib::ustring("#7f7f7f");
	property_inconsistent() = false;
}

CellRenderer_ValueBase::~CellRenderer_ValueBase()
{
	if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
		synfig::info("CellRenderer_ValueBase::~CellRenderer_ValueBase(): Deleted");
}

void
CellRenderer_ValueBase::string_edited_(const Glib::ustring& path, const Glib::ustring& str)
{
	ValueBase old_value = property_value_.get_value();
	ValueBase value;

	if (old_value.get_type() == type_time)
	{
		value = ValueBase( Time( (String)str, get_canvas()->rend_desc().get_frame_rate() ) );
	}
	else
		value = ValueBase( (String)str );

	if (old_value != value)
		signal_edited_(path, value);
}

void
CellRenderer_ValueBase::render_vfunc(
	const    ::Cairo::RefPtr< ::Cairo::Context>& cr,
	      Gtk::Widget&           widget,
	const Gdk::Rectangle&        background_area,
	const Gdk::Rectangle&        cell_area,
	      Gtk::CellRendererState flags)
{
	if (!cr)
		return;

	Gdk::Rectangle aligned_area;
	get_aligned_area(widget, flags, cell_area, aligned_area);

	/*
	TODO: is widget state equals this state variable?
	      for checkbox only
	Gtk::StateType state = Gtk::STATE_INSENSITIVE;
	if(property_editable())
		state = Gtk::STATE_NORMAL;
	if((flags & Gtk::CELL_RENDERER_SELECTED) != 0)
		state = (widget.has_focus()) ? Gtk::STATE_SELECTED : Gtk::STATE_ACTIVE;
	*/

	ValueBase data = property_value_.get_value();

	Type &type(data.get_type());

	if (type == type_real)
	{
		if ( ((synfig::ParamDesc)property_param_desc_).get_is_distance() )
		{
			Distance x( data.get(Real()), Distance::SYSTEM_UNITS);
			x.convert( App::distance_system, get_canvas()->rend_desc() );
			property_text() = (Glib::ustring) x.get_string(6).c_str();
		}
		else
			property_text() = (Glib::ustring) strprintf("%.6f", data.get(Real()));
	}
	else
	if (type == type_time)
	{
		property_text() =
			(Glib::ustring) data.get(Time()).get_string( get_canvas()->rend_desc().get_frame_rate(),
				                                         App::get_time_format());
	}
	else
	if (type == type_angle)
	{
		property_text() = (Glib::ustring) strprintf( "%.2fᵒ", (Real) Angle::deg( data.get(Angle()) ).get() );
	}
	else
	if (type == type_integer)
	{
		String param_hint, child_param_hint;
		param_hint       =       get_param_desc().get_hint();
		child_param_hint = get_child_param_desc().get_hint();
		if ( param_hint != "enum" && child_param_hint != "enum" )
		{
			property_text() = (Glib::ustring) strprintf("%i", data.get(int()));
		}
		else
		{
			property_text() = (Glib::ustring) strprintf("(%i)",data.get(int()));

			std::list<synfig::ParamDesc::EnumData> enum_list;
			if (param_hint == "enum")
				enum_list = ((synfig::ParamDesc) property_param_desc_).get_enum_list();
			else if (child_param_hint == "enum")
				enum_list = ((synfig::ParamDesc) property_child_param_desc_).get_enum_list();

			std::list<synfig::ParamDesc::EnumData>::iterator iter;
			for (iter = enum_list.begin(); iter != enum_list.end(); iter++)
				if (iter->value == data.get(int()))
				{
					// don't show the key_board s_hortcut under_scores
					String local_name = iter->local_name;
					String::size_type pos = local_name.find_first_of('_');
					if (pos != String::npos)
						property_text() = local_name.substr(0, pos) + local_name.substr(pos+1);
					else
						property_text() = local_name;
					break;
				}
		}
	}
	else
	if (type == type_vector)
	{
		Vector vector = data.get(Vector());
		Distance x( vector[0], Distance::SYSTEM_UNITS ), y( vector[1], Distance::SYSTEM_UNITS );
		x.convert( App::distance_system, get_canvas()->rend_desc() );
		y.convert( App::distance_system, get_canvas()->rend_desc() );
		property_text() = static_cast<Glib::ustring>(strprintf("%s,%s",
		                                                       x.get_string(6).c_str(),
		                                                       y.get_string(6).c_str()) );
	}
	else
	if (type == type_transformation)
	{
		const Transformation &transformation = data.get(Transformation());
		const Vector         &offset         = transformation.offset;
		const Angle::deg     angle            (transformation.angle);
		const Vector         &scale          = transformation.scale;

		Distance x( offset[0], Distance::SYSTEM_UNITS ), y( offset[1], Distance::SYSTEM_UNITS );
		x.convert( App::distance_system, get_canvas()->rend_desc() );
		y.convert( App::distance_system, get_canvas()->rend_desc() );

		Distance sx( scale[0], Distance::SYSTEM_UNITS ), sy( scale[1], Distance::SYSTEM_UNITS );
		sx.convert( App::distance_system, get_canvas()->rend_desc() );
		sy.convert( App::distance_system, get_canvas()->rend_desc() );

		property_text() = static_cast<Glib::ustring>(strprintf(
			"%s,%s,%.2fᵒ,%s,%s",
			x.get_string(6).c_str(),
			y.get_string(6).c_str(),
			(Real) angle.get(),
			sx.get_string(6).c_str(),
			sy.get_string(6).c_str()
		));
	}
	else
	if (type == type_string)
	{
		if ( !data.get(synfig::String()).empty() )
			property_text() = static_cast<Glib::ustring>( data.get(synfig::String()) );
		else
			property_text() = Glib::ustring("<empty>");
	}
	else
	if (type == type_canvas)
	{
		if ( data.get(etl::handle<synfig::Canvas>()) )
		{
			if (data.get( etl::handle<synfig::Canvas>())->is_inline() )
				property_text() = _("<Group>");
			else
				property_text() = (Glib::ustring) data.get(etl::handle<synfig::Canvas>())->get_id();
		}
		else
			property_text() = _("<No Image Selected>");
	}
	else
	if (type == type_color)
	{
		render_color_to_window(cr, cell_area, data.get(Color()));
		return;
	}
	else
	if (type == type_bool)
	{
		Glib::RefPtr<Gtk::StyleContext> context = widget.get_style_context();
		context->context_save();
		Gtk::StateFlags state = get_state(widget, flags);
#if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 14)
		state &= ~(Gtk::STATE_FLAG_INCONSISTENT | Gtk::STATE_FLAG_ACTIVE);
#else
		state &= ~(Gtk::STATE_FLAG_INCONSISTENT | Gtk::STATE_FLAG_ACTIVE | Gtk::STATE_FLAG_CHECKED);
#endif
		if ((flags & Gtk::CELL_RENDERER_SELECTED) != 0 && widget.has_focus())
			state |= Gtk::STATE_FLAG_SELECTED;
		if (!property_editable())
			state |= Gtk::STATE_FLAG_INSENSITIVE;
		if (data.get(bool()))
#if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 14)
			state |= Gtk::STATE_FLAG_ACTIVE;
#else
			state |= Gtk::STATE_FLAG_CHECKED;
#endif

		cr->save();
		Gdk::Cairo::add_rectangle_to_path(cr, cell_area);
		cr->clip();

		context->add_class("check");
		context->set_state(state);
		context->render_check(
			cr,
			aligned_area.get_x(),
			aligned_area.get_y(),
			aligned_area.get_height(),
			aligned_area.get_height()
		);
		cr->restore();
		context->context_restore();
		return;
	}
	else
	if (type == type_nil)
	{
		//property_text()=(Glib::ustring)" ";
		return;
	}
	else
	if (type == type_gradient)
	{
		render_gradient_to_window(cr, cell_area, data.get(Gradient()));
		return;
	}
	else
	if (type == type_bone_object
	 || type == type_segment
	 || type == type_list
	 || type == type_bline_point
	 || type == type_width_point
	 || type == type_dash_item)
	{
		property_text() = (Glib::ustring)(data.get_type().description.local_name);
	}
	else
	if (type == type_bone_valuenode)
	{
		ValueNode_Bone::Handle bone_node(data.get(ValueNode_Bone::Handle()));
		String name(_("No Parent"));

		if (!bone_node->is_root())
		{
			name = (*(bone_node->get_link("name")))(get_canvas()->get_time()).get(String());
			if (name.empty())
				name = bone_node->get_guid().get_string();
		}

		property_text() = (Glib::ustring)(name);
	}
	else
	{
		property_text() = static_cast<Glib::ustring>(type.description.local_name);
	}

	CellRendererText::render_vfunc(cr, widget, background_area, cell_area, flags);
}


/*
bool
CellRenderer_ValueBase::activate_vfunc(	GdkEvent* event,
	Gtk::Widget& widget,
	const Glib::ustring& path,
	const Gdk::Rectangle& background_area,
	const Gdk::Rectangle& cell_area,
	Gtk::CellRendererState flags)
{
	ValueBase data=(ValueBase)property_value_.get_value();

	if (data.type == type_bool)
	{
		if(property_editable())
			signal_edited_(path,ValueBase(!data.get(bool())));
    	return true;
    }
    else
    if (data.type == type_string)
    {
		return CellRendererText::activate_vfunc(event,widget,path,background_area,cell_area,flags);
	}
	return false;
}
*/

void
CellRenderer_ValueBase::gradient_edited(synfig::Gradient gradient, Glib::ustring path)
{
	ValueBase old_value(property_value_.get_value());
	ValueBase value(gradient);
	if (old_value != value)
		signal_edited_(path, value);
}

void
CellRenderer_ValueBase::color_edited(synfig::Color color, Glib::ustring path)
{
	ValueBase old_value(property_value_.get_value());
	ValueBase value(color);
	if (old_value != value)
		signal_edited_(path, value);
}

Gtk::CellEditable*
CellRenderer_ValueBase::start_editing_vfunc(
	GdkEvent*              event           __attribute__ ((unused)),
	Gtk::Widget&           widget,
	const Glib::ustring&   path,
	const Gdk::Rectangle&  background_area __attribute__ ((unused)),
	const Gdk::Rectangle&  cell_area       __attribute__ ((unused)),
	Gtk::CellRendererState flags           __attribute__ ((unused)))
{
	SYNFIG_EXCEPTION_GUARD_BEGIN()
	edit_value_done_called = false;
	// If we aren't editable, then there is nothing to do
	if (!property_editable())
		return 0;

	ValueBase data = property_value_.get_value();

	Type &type(data.get_type());

	if (type == type_bool)
	{
		signal_edited_( path, ValueBase(!data.get(bool())) );
		return NULL;
	}
	//else
	//if (type == type_time)
	//{
	//	property_text()=(Glib::ustring)data.get(Time()).get_string(get_canvas()->rend_desc().get_frame_rate(),App::get_time_format()|Time::FORMAT_FULL);
	//	return CellRendererText::start_editing_vfunc(event,widget,path,background_area,cell_area,flags);
	//}
	else
	if (type == type_gradient)
	{
		App::dialog_gradient->reset();
		App::dialog_gradient->set_gradient( data.get(Gradient()) );
		App::dialog_gradient->signal_edited().connect(
			sigc::bind(
				sigc::mem_fun(*this, &studio::CellRenderer_ValueBase::gradient_edited),
				path
			)
		);
		App::dialog_gradient->set_default_button_set_sensitive(true);
		App::dialog_gradient->present();
		return NULL;
	}
	else
	if (type == type_color)
	{
		App::dialog_color->reset();
		App::dialog_color->set_color( data.get(Color()) );
		App::dialog_color->signal_edited().connect(
			sigc::bind(
				sigc::mem_fun(*this, &studio::CellRenderer_ValueBase::color_edited),
				path
			)
		);
		App::dialog_color->present();
		return NULL;
	}
	else
	if (type == type_string
	 && get_param_desc().get_hint() == "paragraph")
	{
		synfig::String string;
		string = data.get(string);
		if (get_paragraph(string))
			signal_edited_(path, ValueBase(string));
		return NULL;
	}
	// if (type == type_string) && (get_param_desc().get_hint()!="filename")
		// return CellRendererText::start_editing_vfunc(event,widget,path,background_area,cell_area,flags);
	else
	{
		assert(get_canvas());
		//delete value_entry;
		saved_data = data;

		value_entry = manage(new ValueBase_Entry());
		value_entry->set_path(path);
		value_entry->set_canvas(get_canvas());
		value_entry->set_param_desc(get_param_desc());
		value_entry->set_value_desc(get_value_desc());
		value_entry->set_child_param_desc(get_child_param_desc());
		value_entry->set_value(data);
		value_entry->set_parent(&widget);
		value_entry->signal_editing_done().connect(sigc::mem_fun(*this, &CellRenderer_ValueBase::on_value_editing_done));
		return value_entry;
	}

	return NULL;
	SYNFIG_EXCEPTION_GUARD_END_NULL(nullptr)
}

void
CellRenderer_ValueBase::on_value_editing_done()
{
	if (edit_value_done_called)
	{
		synfig::error("on_value_editing_done(): Called twice!");
//		return;
	}

	edit_value_done_called = true;

	if (value_entry)
	{

		//ValueBase old_value(property_value_.get_value());
		ValueBase     value(value_entry->get_value());

		//if (old_value != value)
		if (saved_data != value)
			signal_edited_(value_entry->get_path(), value);

		//delete value_entry;
		//value_entry=0;
	}
}
