/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki SEKIYAMA <sekiyama@yahoo.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/



#include <string.h>

#include "v/VTreeView.h"
#include "machine/v_object.h"
extern "C"{
#include "memory_debug.h"
#include "custom-cell-renderer-progressbar.h"
}

struct VTreeSelectedRow
{
	GtkTreeIter	iter;
	VTreeSelectedRow	*next;
};

struct VTreeSelectedRows
{
	int		n;
	GtkTreeModel *model;
	VTreeSelectedRow *top, *last;
};

void
v_tree_selected_row_get_sub(
	GtkTreeModel *model,
	GtkTreePath *path,
	GtkTreeIter *iter,
	gpointer data)
{
	VTreeSelectedRows *sel = (VTreeSelectedRows*)data;
	VTreeSelectedRow *s = new VTreeSelectedRow;
	s->iter = *iter;
	s->next = 0;
	if ( sel->last )
		sel->last->next = s;
	sel->last = s;
	if ( sel->top == 0 ) {
		sel->top = s;
		sel->model = model;
	}
	sel->n++;
}

VTreeSelectedRows *
v_tree_selected_row_get(GtkTreeSelection *selection)
{
	VTreeSelectedRows *ret = new VTreeSelectedRows;
	ret->n = 0;
	ret->top = ret->last = 0;
	gtk_tree_selection_selected_foreach(selection, v_tree_selected_row_get_sub, ret);
	return ret;
}

void
v_tree_selected_row_free(VTreeSelectedRows *sel)
{
	VTreeSelectedRow *next;
	for ( VTreeSelectedRow *s = sel->top ; s ; s = next ) {
		next = s->next;
		delete s;
	}
	delete sel;
}

void
VTreeViewImp::selection_changed(
		GtkTreeSelection *selection,
		gpointer data)
{
	VTreeSelectedRows *list = v_tree_selected_row_get(selection);
	GtkTreeModel *model = list->model;
	gint n = list->n ? gtk_tree_model_get_n_columns(model) : 0;
	
	int n_sels = list->n;
	VTreeView::Selection *sel = new VTreeView::Selection;
	sel->n = n_sels;
	
	if ( n_sels ) {
		int i = 0;
		sel->keys = new void*[n_sels];
		for ( VTreeSelectedRow *l = list->top ; l ; l = l->next ) {
			VTreeNode *node;
			gtk_tree_model_get(model, &l->iter, n-1, &node, -1);
			sel->keys[i++] = node->key;
		}
	}
	v_tree_selected_row_free(list);
	
	VTreeView::select_event((VTreeView*)data, sel);
}

void
VTreeViewImp::edited_get_info(
		GObject *cell,
		gchar *path_str,
		gpointer data,
		GtkTreeStore **out_store,
		GtkTreeIter	*out_iter,
		gint *out_n,
		gint *out_column,
		VTreeNode **out_node)
{
	VTreeView *vobj = (VTreeView*)data;
	GtkTreeView *view = GTK_TREE_VIEW(GTK_BIN(vobj->get_info_this())->child);
	GtkTreeModel *model = gtk_tree_view_get_model(
		GTK_TREE_VIEW(view));
	*out_store = GTK_TREE_STORE(model);
	*out_n = gtk_tree_model_get_n_columns(model);
	*out_column = GPOINTER_TO_INT(g_object_get_data(cell, "column"));
	GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
	gtk_tree_model_get_iter(model, out_iter, path);
	gtk_tree_path_free(path);
	gtk_tree_model_get(model, out_iter, *out_n-1, out_node, -1);
}

void
VTreeViewImp::text_edited(
		GtkCellRendererText *cell,
		gchar *path_str,
		gchar *new_text,
		gpointer data)
{
	GtkTreeStore *store;
	GtkTreeIter iter;
	gint n, column;
	VTreeNode *node;
	VTreeViewImp::edited_get_info(G_OBJECT(cell), path_str,
		data, &store, &iter, &n, &column, &node);
	
	gchar *old;
	gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, column*2, &old, -1);
	if ( strcmp(old, new_text) != 0 ) {
		gtk_tree_store_set(store, &iter, column*2, new_text, -1);
	
		VTreeView::RowEdit arg = {node, column, nl_copy_str(std_cm, new_text)};
		VTreeView::row_edit_event((VTreeView*)data, &arg);
	}
}

void
VTreeViewImp::bool_edited(
		GtkCellRendererToggle *cell,
		gchar *path_str,
		gpointer data)
{
	GtkTreeStore *store;
	GtkTreeIter iter;
	gint n, column;
	VTreeNode *node;
	VTreeView *vobj = (VTreeView*)data;
	edited_get_info(G_OBJECT(cell), path_str,
		data, &store, &iter, &n, &column, &node);
	
	if ( ! node->editable )
		return;
	gboolean flag;
	gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, column*2, &flag, -1);
	gtk_tree_store_set(store, &iter, column*2, !flag, -1);
	
	VTreeView::RowEdit arg = {node, column, flag ? VTF_FALSE : VTF_TRUE};
	VTreeView::row_edit_event(vobj, &arg);
}

VInfo*
VTreeViewImp::create_do(VTreeView *vobj, VTreeView::tree_set *set, int attr)
{
	VInfo *info, *view;
	
	info = gtk_scrolled_window_new(0,0);
	gtk_scrolled_window_set_policy(
		GTK_SCROLLED_WINDOW(info),
		(attr & VTreeView::scrollbar_h) ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER,
		(attr & VTreeView::scrollbar_v) ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER);
	gtk_scrolled_window_set_shadow_type(
		GTK_SCROLLED_WINDOW(info), GTK_SHADOW_ETCHED_IN);
	
	int i, cols=set->cols;
	GType *types = new GType[cols*2+1];
	for ( i = 0 ; i < cols ; i++ ) {
		if ( (set->types[i] & VTT_TYPE_MASK) == VTT_TEXT )
			types[i*2] = G_TYPE_STRING;
		else if ( (set->types[i] & VTT_TYPE_MASK) == VTT_BOOL )
			types[i*2] = G_TYPE_BOOLEAN;
		else if ( (set->types[i] & VTT_TYPE_MASK) == VTT_PERC )
			types[i*2] = G_TYPE_DOUBLE;
		else
			er_panic("VTreeView::unknown type");
		types[i*2+1] = G_TYPE_BOOLEAN; /* editable flag */
	}
	types[cols*2] = G_TYPE_POINTER; /* VTreeNode* */
	GtkTreeStore *store = gtk_tree_store_newv(cols*2+1, types);
	view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
	g_object_unref(G_OBJECT(store));
	gtk_container_add(GTK_CONTAINER(info), view);
	
	GtkTreeSelection *selection = 
		gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
	int sel = attr & VTreeView::selection_mask;
	gtk_tree_selection_set_mode(selection,
		sel == VTreeView::select_none ? GTK_SELECTION_NONE
			: sel == VTreeView::select_single ? GTK_SELECTION_SINGLE
			: GTK_SELECTION_MULTIPLE);
	g_signal_connect(selection, "changed",
		G_CALLBACK(selection_changed), vobj);
	
	for ( i = 0 ; i < cols ; i++ ) {
		GtkTreeViewColumn *column;
		GtkCellRenderer *renderer;
		if ( (set->types[i] & VTT_TYPE_MASK) == VTT_TEXT ) {
			renderer = gtk_cell_renderer_text_new();
			g_signal_connect(renderer, "edited", 
				G_CALLBACK(text_edited), vobj);
			column = gtk_tree_view_column_new_with_attributes(
				set->title[i] ? ucd_n_string(set->title[i]) : 0, renderer,
				"text", i*2, "editable", i*2+1, NULL);
		}
		else if ( (set->types[i] & VTT_TYPE_MASK) == VTT_BOOL ) {
			renderer = gtk_cell_renderer_toggle_new();
			column = gtk_tree_view_column_new_with_attributes(
				set->title[i] ? ucd_n_string(set->title[i]) : 0,
				renderer, "active", i*2, "activatable", i*2+1, NULL);
			g_signal_connect(renderer, "toggled",
				G_CALLBACK(VTreeViewImp::bool_edited), vobj);
		}
		else if ( (set->types[i] & VTT_TYPE_MASK) == VTT_PERC ) {
			renderer = custom_cell_renderer_progress_new();
			column = gtk_tree_view_column_new_with_attributes(
				set->title[i] ? ucd_n_string(set->title[i]) : 0,
				renderer, "percentage", i*2, NULL);
		}
		g_object_set_data(G_OBJECT(renderer), "column", (gint*)i);
		gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
		gtk_tree_view_column_set_fixed_width(column, i ? set->widths[i] : set->widths[i] + 64);
		gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
	}
	gtk_widget_show(view);
	
	g_object_ref(info);
	delete types;
	return info;
}


void
VTreeViewImp::set(
			VInfo *info,
			int cols,
			char *types,
			VTreeNode *node,
			VTreeNode *ring)
{
	GtkTreeView *view = GTK_TREE_VIEW(GTK_BIN(info)->child);
	GtkTreeModel *model = gtk_tree_view_get_model(view);
	GtkTreeStore *store = GTK_TREE_STORE(model);
	for ( int i = 0 ; i < cols ; i++ ) {
		if ( (types[i] & VTT_TYPE_MASK) == VTT_TEXT )
			gtk_tree_store_set(store, &node->info, 
				i*2, ucd_n_string((L_CHAR*)node->data[i]),
				i*2+1, node->editable && (types[i] & VTT_EDITABLE),
				-1);
		else if ( (types[i] & VTT_TYPE_MASK) == VTT_PERC )
		gtk_tree_store_set(store, &node->info, 
				i*2, (gdouble)(int)node->data[i]/100,
				i*2+1, node->editable && (types[i] & VTT_EDITABLE),
				-1);
		else
			gtk_tree_store_set(store, &node->info,
				i*2, node->data[i],
				i*2+1, node->editable && (types[i] & VTT_EDITABLE),
				-1);
	}
	gtk_tree_store_set(store, &node->info, cols*2, node, -1);
}

void
VTreeViewImp::add(
			VInfo *info,
			int cols,
			char *types,
			VTreeNode *node,
			VTreeNode *ring)
{
	GtkTreeView *view = GTK_TREE_VIEW(GTK_BIN(info)->child);
	GtkTreeModel *model = gtk_tree_view_get_model(view);
	GtkTreeStore *store = GTK_TREE_STORE(model);
	VTreeNode *parent = node->parent;
	gtk_tree_store_insert_before(store, &node->info, 
		parent ? &parent->info : 0,
		node->next == (parent ? parent->children : ring) ? 0 : &node->next->info);
	set(info, cols, types, node, ring);
}

void
VTreeViewImp::remove(VInfo *info, VTreeNode *node)
{
	GtkTreeView *view = GTK_TREE_VIEW(GTK_BIN(info)->child);
	GtkTreeStore *store = GTK_TREE_STORE(
		gtk_tree_view_get_model(view));
	gtk_tree_store_remove(store, &node->info);
}




VExError
VTreeView::create_do(const VObjectStatus* s, int flags, 
			VObject * nmp,void * arg)
{
	selection = new Selection;
	selection->n = 0;
	select_event_handler = 0;
	row_edit_event_handler = 0;
	tree_set *set = (tree_set*)arg;
	cols = set->cols;
	types = new char[cols];
	memcpy(types, set->types, cols);
	
	ring = 0;
	hash = new VTreeNode*[v_tree_view_hash_size];
	for ( int key = 0 ; key < v_tree_view_hash_size ; key++ )
		hash[key] = 0;
	
	info = v_serialized_exec_func(VTreeViewImp::create_do, 
		this, set, (flags & VSF_ATTR) ? s->attr : 0);
	
	return return_create_do(this,nmp,&sts,s,flags);
}

void
VTreeView::destroy_do(VObject * nmp)
{
	nmp->remove_child_do(this);
	v_serialized_exec_sub(gtk_widget_destroy, info);
	v_serialized_exec_sub(g_object_unref, info);
	
	for ( int key = 0 ; key < v_tree_view_hash_size ; key++ )
		while( hash[key] )
			remove_node(hash[key]);
	delete hash;
	delete types;
	delete selection;
}

VTreeView::~VTreeView()
{
}

VExError
VTreeView::get_status(VObjectStatus *s, int flags) const
{
	V_OP_START_EX
	VExError err = v_get_status_standard(s, &flags, info);
	VExError err2 = VObject::get_status(s,flags);
	if ( err2.code )
		err = merge_VExError_vstatus_type(err,err2);
	V_OP_END
	return err;
};

VExError
VTreeView::set_status(const VObjectStatus *s, int flags)
{
	V_OP_START_EX
	VExError err = v_set_status_standard(s, flags, info);
	err = VObject::set_status(s,flags);
	if ( err.code ) {
		V_OP_END
		return err;
	}
	
	if ( flags & VSF_MIN_SIZE ) {
		gtk_widget_set_size_request(info, s->min_size.w, s->min_size.h);
	}
	
	V_OP_END
	
	if ( flags & ( VSF_ALIGN | VSF_PADDING ) ) {
		sts.parent->child_status_changed(this, info);
		err.subcode1 &= ~( VSF_ALIGN | VSF_PADDING );
	}
	return err;
}

void
VTreeView::redraw(VRect* rect) const
{
}



VTreeView::Selection *
VTreeView::get_selection() const
{
	Selection *ret;
	_V_OP_START(0)
	ret = v_serialized_exec_func(copy_selection, this);
	V_OP_END
	return ret;
}

VError
VTreeViewImp::add_row(
	VTreeView *view,
	VTreeNode *node,
	void *parent_key,
	void *next_key)
{
	if ( view->add_node(&node, parent_key, next_key) ) {
		add(view->info, view->cols, view->types, node, view->ring);
		return V_ER_NO_ERR;
	}
	return V_ER_PARAM;
}

VExError
VTreeView::add_row(VTreeNode *node, void *parent_key, void *next_key)
{
	VExError err = initial_VExError(V_ER_NO_ERR,0,0);
	V_OP_START_EX
	err.code = v_serialized_exec_func(VTreeViewImp::add_row,
		this, node, parent_key, next_key);
	V_OP_END
	return err;
}

VError
VTreeViewImp::remove_row(VTreeView *view, void *key)
{
	VTreeNode *node = view->get_node(key);
	if ( node ) {
		remove(view->info, node);
		view->remove_node(node);
		return V_ER_NO_ERR;
	}
	return V_ER_PARAM;
}

VExError
VTreeView::remove_row(void *key)
{
	VExError err = initial_VExError(V_ER_NO_ERR,0,0);
	V_OP_START_EX
	err.code = v_serialized_exec_func(VTreeViewImp::remove_row, this, key);
	V_OP_END
	return err;
}

VTreeNode *
VTreeViewImp::get_row(const VTreeView *view, void *key)
{
	int n = view->cols;
	VTreeNode *_node = view->get_node(key), *node = 0;
	if ( _node ) {
		node = new VTreeNode(n, *_node);
		for ( int i = 0 ; i < n ; i++ ) {
			if ( (view->types[i] & VTT_TYPE_MASK) == VTT_TEXT ) {
				node->data[i] = ll_copy_str((L_CHAR*)node->data[i]);
				set_buffer((void*)node->data[i]);
			}
		}
	}
	return node;
}

VTreeNode*
VTreeView::get_row(void *key) const
{
	_V_OP_START(0)
		VTreeNode* node = v_serialized_exec_func(VTreeViewImp::get_row, this, key);
	V_OP_END
	return node;
}

VError
VTreeViewImp::set_row(VTreeView *view, VTreeNode *node)
{
	if ( view->set_node(&node) ) {
		set(view->info, view->cols, view->types, node, view->ring);
		return V_ER_NO_ERR;
	}
	return V_ER_PARAM;
}

VExError
VTreeView::set_row(VTreeNode *node)
{
	V_OP_START_EX
	VError e = v_serialized_exec_func(VTreeViewImp::set_row, this, node);
	VExError err = initial_VExError(e,0,0);
	V_OP_END
	return err;
}

void
VTreeView::row_edit_event_done(RowEdit* r)
{
	// do nothing
}
