//$Id: form-win-widgettree.cc,v 1.14 2006/10/06 22:35:40 cactus Exp $ -*- c++ -*-

/* Guikachu Copyright (C) 2001-2006 ÉRDI Gergõ <cactus@cactus.rulez.org>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 * 
 * 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
 */

#include "form-win-widgettree.h"

#include "form-editor/widget-util.h"
#include "form-editor/widget-util-gui.h"
#include "resource-util-gui.h"

#include "cellrenderer-icontext.h"
#include "cellrenderer-indent.h"

#include <gdk/gdkkeysyms.h>

using namespace Guikachu::GUI::FormWindow_Helpers;

WidgetTreeWrapper::WidgetTreeWrapper (Gtk::TreeView    &treeview_,
				      Resources::Form  *form_):
    treeview (treeview_),
    form (form_),
    selection_block (false),
    menu_block (false)
{
    Gtk::TreeModel::ColumnRecord cols;
    cols.add (col_widget);
    
    treestore = Gtk::TreeStore::create (cols);
    treeview.set_model (treestore);

    treeview.set_headers_visible (false);

    CellRendererIndent<CellRendererIconText>::setup_view (
        treeview, "", sigc::mem_fun (*this, &WidgetTreeWrapper::cell_label_cb));
    
    // Create root item: the form itself
    root_tree = *(treestore->append ());
    root_tree[col_widget] = 0;

    form->changed.connect (sigc::mem_fun (*this, &WidgetTreeWrapper::form_changed_cb));
    form_changed_cb ();
    
    form->widget_created.connect (sigc::mem_fun (*this, &WidgetTreeWrapper::widget_created_cb));
    form->widget_removed.connect (sigc::mem_fun (*this, &WidgetTreeWrapper::widget_removed_cb));

    treeview.get_selection ()->set_select_function (
	sigc::mem_fun (*this, &WidgetTreeWrapper::selection_changed_cb));
    
    // Create tree items for existing widgets
    const std::set<Guikachu::Widget*> widgets = form->get_widgets ();
    std::for_each (widgets.begin (), widgets.end (),
		   sigc::mem_fun (*this, &WidgetTreeWrapper::widget_created_cb));
    treeview.expand_all ();

    // Set selection mode
    treeview.get_selection ()->set_mode (Gtk::SELECTION_MULTIPLE);

    treeview.add_events (Gdk::BUTTON_PRESS_MASK | Gdk::KEY_PRESS_MASK);
    treeview.signal_button_press_event ().connect_notify (
	sigc::mem_fun (*this, &WidgetTreeWrapper::button_press_cb));
    treeview.signal_key_press_event ().connect (
	sigc::bind_return (sigc::mem_fun (*this, &WidgetTreeWrapper::key_press_cb), false));        
}

void WidgetTreeWrapper::cell_label_cb (Gtk::CellRenderer *cell, const Gtk::TreeModel::iterator &iter) const
{
    CellRendererIconText *cell_icontext = dynamic_cast<CellRendererIconText*> (cell);
    g_return_if_fail (cell_icontext);
    
    Glib::ustring label;
    Glib::RefPtr<Gdk::Pixbuf> pixbuf;
    
    Guikachu::Widget *widget = (*iter)[col_widget];

    if (widget)
    {
        label = widget->id;
        pixbuf = Widgets::get_type_pixbuf (widget->get_type ());
    } else {
        label = form->id;
        pixbuf = Resources::get_type_pixbuf (Resources::RESOURCE_FORM);
    }

    cell_icontext->property_text () = label;
    cell_icontext->property_pixbuf () = pixbuf;
}

void WidgetTreeWrapper::widget_removed_cb (Guikachu::Widget *widget)
{
    row_map_t::iterator row_found = row_map.find (widget);
    g_return_if_fail (row_found != row_map.end ());
    
    treestore->erase (row_found->second);
    row_map.erase (row_found);

    treeview.expand_all ();
}

bool WidgetTreeWrapper::compare_treerow (const Gtk::TreeRow &row, Guikachu::Widget *widget) const
{
    Widget *curr_widget = row[col_widget];
    if (!curr_widget)
        return true;
    
    return IDManager::NoCase () (curr_widget->id, widget->id);
}

Gtk::TreeStore::iterator WidgetTreeWrapper::get_place (Guikachu::Widget *widget) const
{
    return std::lower_bound (root_tree.children ().begin (), root_tree.children ().end (),
                             widget, sigc::mem_fun (*this, &WidgetTreeWrapper::compare_treerow));    
}

void WidgetTreeWrapper::widget_created_cb (Guikachu::Widget *widget)
{
    Gtk::TreeRow row = *(treestore->insert (get_place (widget)));
    row[col_widget] = widget;

    row_map[widget] = row;
    
    widget->changed.connect (sigc::bind (
	sigc::mem_fun (*this, &WidgetTreeWrapper::widget_changed_cb), widget));
    widget->selected.connect (sigc::bind (
	sigc::mem_fun (*this, &WidgetTreeWrapper::widget_selected_cb), widget));

    treeview.expand_all ();
}

void WidgetTreeWrapper::widget_changed_cb (Guikachu::Widget *widget)
{
    g_return_if_fail (widget);

    Gtk::TreeRow row = row_map[widget];
    bool selected = treeview.get_selection ()->is_selected (row);

    treestore->erase (row);            
    row = *(treestore->insert (get_place (widget)));
    row[col_widget] = widget;
    row_map[widget] = row;

    treeview.expand_all ();
    
    if (selected)
        treeview.get_selection ()->select (row);
}

void WidgetTreeWrapper::form_changed_cb ()
{
    treestore->row_changed (treestore->get_path (root_tree), root_tree);
}

void WidgetTreeWrapper::widget_selected_cb (bool selected,
					    Guikachu::Widget *widget)
{
    row_map_t::const_iterator row_iter = row_map.find (widget);
    if (row_iter == row_map.end ())
	return;
	
    selection_block = true;
    Glib::RefPtr<Gtk::TreeSelection> tree_selection = treeview.get_selection ();
    Gtk::TreeRow row = row_iter->second;

    if (selected)
    {
	tree_selection->select (row);
	tree_selection->unselect (root_tree);
    } else {
	tree_selection->unselect (row);
    }

    selection_block = false;
}

bool WidgetTreeWrapper::selection_changed_cb (const Glib::RefPtr<Gtk::TreeModel> &model,
					      const Gtk::TreeModel::Path         &path,
					      bool                                previously_selected)
{
    if (menu_block)
        return false;
    
    if (selection_block)
	return true;

    bool selected = !previously_selected;
    
    if (path.get_depth () < 2)
    {
	if (selected)
	    form_selected.emit ();
	return true;
    }
    
    Gtk::TreeModel::iterator iter = model->get_iter (path);
    Guikachu::Widget *widget = (*iter)[col_widget];

    widget_selected.emit (widget, selected);

    return false;
}

namespace
{
    void reset_menu_block (bool *menu_block)
    {
        *menu_block = false;
    }
} // anonymous namespace

void WidgetTreeWrapper::button_press_cb (GdkEventButton *e)
{
    if (e->button != 3)
	return;

    int cell_x, cell_y;
    Gtk::TreeModel::Path  path;
    Gtk::TreeViewColumn  *column;
    Guikachu::Widget *widget = 0;

    // FIXME: e->x and e->y are doubles
    if (treeview.get_path_at_pos (e->x, e->y, path, column, cell_x, cell_y))
    {
        if (treeview.get_selection ()->is_selected (path))
        {
            menu_block = true;
            Glib::signal_idle ().connect (
                sigc::bind_return (sigc::bind (sigc::ptr_fun (reset_menu_block), &menu_block), false));
        }
            
	Gtk::TreeModel::iterator iter = treestore->get_iter (path);
	widget = (*iter)[col_widget];
    }

    if (widget)
	widget_menu.emit (e->button, e->time, widget);
    else
        form_menu.emit (e->button, e->time);
}

namespace
{
    template<typename T, typename Container = std::list<T> >
    class SelectionCollector: public sigc::trackable
    {
    public:
        typedef T                               element_t;
        typedef Container                       container_t;
        typedef Gtk::TreeModelColumn<element_t> column_t;
        
    private:
        column_t    column;
        container_t items;
        
    public:
        SelectionCollector (const column_t &column_): column (column_) {};
        container_t get_items () const { return items; };
        void selection_cb (const Gtk::TreeModel::iterator &iter) { items.push_back ((*iter)[column]); };
    };
} // anonymous namespace

void WidgetTreeWrapper::key_press_cb (GdkEventKey *e)
{
    Guikachu::Widget *widget = 0;

    Gtk::TreeView::Selection::ListHandle_Path items = treeview.get_selection ()->get_selected_rows ();
    
    // It doesn't really matter which widget we select when more than
    // one are selected
    if (items.size ())
    {
        const Gtk::TreePath &path = *(items.begin ());
        widget = (*treestore->get_iter (path))[col_widget];
    }

    switch (e->keyval)
    {
    case GDK_Delete:
    case GDK_KP_Delete:
        if (widget)
            widget_remove.emit (widget);
        break;
        
    case GDK_Menu:
        if (widget)
            widget_menu.emit (0, e->time, widget);
        else
            form_menu.emit (0, e->time);
        break;
        
    default:
        break;
    }
}