// -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* $Id: treeview.ccg,v 1.24 2006/06/19 17:28:12 murrayc Exp $ */

/* Copyright 2002 The gtkmm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <gtkmm/treeviewcolumn.h>
#include <gtkmm/treeview_private.h>
#include <gtkmm/treemodel.h>
#include <gtkmm/treemodelfilter.h>
#include <gtkmm/entry.h>
#include <gtk/gtktreeview.h>

namespace
{

//This target name is used in the GTK+ implementation:
static const char treeview_target_row[]   = "GTK_TREE_MODEL_ROW";

} // anonymous namespace


static void SignalProxy_Mapping_gtk_callback(GtkTreeView* tree_view, GtkTreePath* path, void* data)
{
  Gtk::TreeView::SlotMapping* the_slot = static_cast<Gtk::TreeView::SlotMapping*>(data);

  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
  #endif //GLIBMM_EXCEPTIONS_ENABLED
    (*the_slot)(Glib::wrap(tree_view), Gtk::TreePath(path, true));
  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  }
  catch(...)
  {
    Glib::exception_handlers_invoke();
  }
  #endif //GLIBMM_EXCEPTIONS_ENABLED
}

static gboolean SignalProxy_SearchEqual_gtk_callback(GtkTreeModel* model, int column, const char* key,
                                               GtkTreeIter* iter, void* data)
{
  Gtk::TreeView::SlotSearchEqual* the_slot = static_cast<Gtk::TreeView::SlotSearchEqual*>(data);

  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
  #endif //GLIBMM_EXCEPTIONS_ENABLED
    return (*the_slot)(Glib::wrap(model, true), column, key, Gtk::TreeIter(model, iter));
  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  }
  catch(...)
  {
    Glib::exception_handlers_invoke();
  }

  return 0; // arbitrary value
  #endif //GLIBMM_EXCEPTIONS_ENABLED
}

static void SignalProxy_SearchEqual_gtk_callback_destroy(void* data)
{
  delete static_cast<Gtk::TreeView::SlotSearchEqual*>(data);
}

static gboolean SignalProxy_ColumnDrop_gtk_callback(GtkTreeView* tree_view, GtkTreeViewColumn* column,
                                              GtkTreeViewColumn* prev_column,
                                              GtkTreeViewColumn* next_column, void* data)
{
  Gtk::TreeView::SlotColumnDrop* the_slot = static_cast<Gtk::TreeView::SlotColumnDrop*>(data);

  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
  #endif //GLIBMM_EXCEPTIONS_ENABLED
    return (*the_slot)(Glib::wrap(tree_view), Glib::wrap(column),
                         Glib::wrap(prev_column), Glib::wrap(next_column));
  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  }
  catch(...)
  {
    Glib::exception_handlers_invoke();
  }
  #endif //GLIBMM_EXCEPTIONS_ENABLED

  return 0; // arbitrary value
}

static void SignalProxy_ColumnDrop_gtk_callback_destroy(void* data)
{
  delete static_cast<Gtk::TreeView::SlotColumnDrop*>(data);
}


static void SignalProxy_SearchPosition_gtk_callback(GtkTreeView* /* tree_view */, GtkWidget* search_dialog, gpointer user_data)
{
  Gtk::TreeView::SlotSearchPosition* the_slot = static_cast<Gtk::TreeView::SlotSearchPosition*>(user_data);

  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
  #endif //GLIBMM_EXCEPTIONS_ENABLED
    (*the_slot)(Glib::wrap(search_dialog));
  #ifdef GLIBMM_EXCEPTIONS_ENABLED
  }
  catch(...)
  {
    Glib::exception_handlers_invoke();
  }
  #endif //GLIBMM_EXCEPTIONS_ENABLED
}

static void SignalProxy_SearchPosition_gtk_callback_destroy(void* data)
{
  delete static_cast<Gtk::TreeView::SlotSearchPosition*>(data);
}

namespace Gtk
{

void TreeView::unset_hadjustment()
{
  gtk_tree_view_set_hadjustment(gobj(), 0);
}

void TreeView::unset_vadjustment()
{
  gtk_tree_view_set_vadjustment(gobj(), 0);
}

int TreeView::insert_column_with_data_func(int position, const Glib::ustring& title, CellRenderer& cell, const SlotCellData& slot)
{
  //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter.
  //It will be deleted when TreeView_Private::SignalProxy_CellData_gtk_callback_destroy() is called.
  SlotCellData* slot_copy = new SlotCellData(slot);

  return gtk_tree_view_insert_column_with_data_func(
      gobj(), position, title.c_str(), cell.gobj(),
      &TreeView_Private::SignalProxy_CellData_gtk_callback, slot_copy,
      &TreeView_Private::SignalProxy_CellData_gtk_callback_destroy);
}

void TreeView::set_cursor(const TreeModel::Path& path)
{
  gtk_tree_view_set_cursor(gobj(), const_cast<GtkTreePath*>(path.gobj()), 0, false);
}

void TreeView::get_cursor(TreeModel::Path& path, TreeViewColumn*& focus_column)
{
  GtkTreePath* pTreePath = 0;
  GtkTreeViewColumn* pTreeViewColumn = 0;
  gtk_tree_view_get_cursor(gobj(), &pTreePath, &pTreeViewColumn);

  path = TreeModel::Path(pTreePath, false); /* Use the existing underlying GtkTreePath instance without copying and freeing, because gtk_tree_view_get_cursor() gives us ownernship. */
  focus_column = Glib::wrap(pTreeViewColumn);
}


void TreeView::enable_model_drag_source(const ArrayHandle_TargetEntry& targets,
                              Gdk::ModifierType start_button_mask,
                              Gdk::DragAction actions)
{
  gtk_tree_view_enable_model_drag_source(
      gobj(), (GdkModifierType) start_button_mask,
      targets.data(), targets.size(), (GdkDragAction) actions);
}

void TreeView::enable_model_drag_source(Gdk::ModifierType start_button_mask, Gdk::DragAction actions)
{
  std::list<TargetEntry> listTargets;
  listTargets.push_back(TargetEntry(treeview_target_row));
  enable_model_drag_source(listTargets, start_button_mask, actions);
}    

void TreeView::enable_model_drag_dest(const ArrayHandle_TargetEntry& targets, Gdk::DragAction actions)
{
  gtk_tree_view_enable_model_drag_dest(
      gobj(), targets.data(), targets.size(), (GdkDragAction) actions);
}

void TreeView::enable_model_drag_dest(Gdk::DragAction actions)
{
  std::list<TargetEntry> listTargets;
  listTargets.push_back(TargetEntry(treeview_target_row));
  enable_model_drag_dest(listTargets, actions);
}

_DEPRECATE_IFDEF_START
bool TreeView::get_path_at_pos(int x, int y, TreeModel::Path& path, TreeViewColumn*& column, int& cell_x, int& cell_y)
{
  GtkTreePath* pTreePath = 0;
  GtkTreeViewColumn* pTreeViewColumn = 0;
  bool result = gtk_tree_view_get_path_at_pos(gobj(), x, y, &pTreePath, &pTreeViewColumn, &cell_x, &cell_y);

  path = TreeModel::Path(pTreePath, false /* don't take a copy, because the gtk_tree_view_get_path_at_pos() docs say that we must free the path */ );
  column = Glib::wrap(pTreeViewColumn);
  return result;
}
_DEPRECATE_IFDEF_END

bool TreeView::get_path_at_pos(int x, int y, TreeModel::Path& path, TreeViewColumn*& column, int& cell_x, int& cell_y) const
{
  GtkTreePath* pTreePath = 0;
  GtkTreeViewColumn* pTreeViewColumn = 0;
  bool result = gtk_tree_view_get_path_at_pos(const_cast<GtkTreeView*>(gobj()), x, y, &pTreePath, &pTreeViewColumn, &cell_x, &cell_y);

  path = TreeModel::Path(pTreePath, false /* don't take a copy, because the gtk_tree_view_get_path_at_pos() docs say that we must free the path */ );
  column = Glib::wrap(pTreeViewColumn);
  return result;
}

int TreeView::insert_column(const Glib::ustring& title, CellRenderer& cell, int position)
{
  return gtk_tree_view_insert_column_with_attributes(
      gobj(), position, const_cast<char*>(title.c_str()), cell.gobj(), (void*)0); //Note that some compilers need the extra (void*) clue.
}

int TreeView::append_column(const Glib::ustring& title, CellRenderer& cell)
{
  return insert_column(title, cell, -1 /* at the end */);
}

void TreeView::get_drag_dest_row(TreeModel::Path& path, TreeViewDropPosition& pos) const
{
  GtkTreePath* pTreePath = 0;
  gtk_tree_view_get_drag_dest_row(const_cast<GtkTreeView*>(gobj()), &pTreePath, (GtkTreeViewDropPosition*) &pos);
  path = TreeModel::Path(pTreePath, true); //true = take_copy.
}

bool TreeView::get_dest_row_at_pos(int drag_x, int drag_y, TreeModel::Path& path, TreeViewDropPosition& pos) const
{
  GtkTreePath* pTreePath = 0;
  const bool bResult = gtk_tree_view_get_dest_row_at_pos(
      const_cast<GtkTreeView*>(gobj()), drag_x, drag_y, &pTreePath, (GtkTreeViewDropPosition*) &pos);

  path = TreeModel::Path(pTreePath, true); //true = take_copy.
  return bResult;
}

void TreeView::map_expanded_rows(const SlotMapping& slot)
{
  gtk_tree_view_map_expanded_rows(gobj(), &SignalProxy_Mapping_gtk_callback, const_cast<SlotMapping*>(&slot));
}

void TreeView::set_search_equal_func(const SlotSearchEqual& slot)
{
  //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter.
  //It will be deleted when SignalProxy_SearchEqual_gtk_callback_destroy() is called.
  SlotSearchEqual* slot_copy = new SlotSearchEqual(slot);

  gtk_tree_view_set_search_equal_func(gobj(),
      &SignalProxy_SearchEqual_gtk_callback, slot_copy,
      &SignalProxy_SearchEqual_gtk_callback_destroy);
}

void TreeView::set_column_drag_function(const SlotColumnDrop& slot)
{
  //Create a copt of the slot. A pointer to this will be passed through the callback's data parameter.
  //It will be deleted when SignalProxy_ColumnDrop_gtk_callback_destroy() is called.
   SlotColumnDrop* slot_copy = new SlotColumnDrop(slot);

  gtk_tree_view_set_column_drag_function(gobj(),
      &SignalProxy_ColumnDrop_gtk_callback, slot_copy,
      &SignalProxy_ColumnDrop_gtk_callback_destroy);

}

void TreeView::unset_column_drag_function()
{
  gtk_tree_view_set_column_drag_function(gobj(),
      0, 0, 0); /* See GTK+ docs about the 0s. */
}

void TreeView::scroll_to_cell(const TreeModel::Path& path, TreeViewColumn& column, float row_align, float col_align)
{
  gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), column.gobj(), TRUE, row_align, col_align);
}

void TreeView::scroll_to_cell(const TreeModel::Path& path, TreeViewColumn& column)
{
  gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), column.gobj(), FALSE, 0.0, 0.0);
}

void TreeView::scroll_to_row(const TreeModel::Path& path, float row_align)
{
  gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), 0, TRUE, row_align, 0.0);
}

void TreeView::scroll_to_row(const TreeModel::Path& path)
{
  gtk_tree_view_scroll_to_cell(gobj(), const_cast<GtkTreePath*>(path.gobj()), 0, FALSE, 0.0, 0.0);
}

void TreeView::scroll_to_column(TreeViewColumn& column, float col_align)
{
  gtk_tree_view_scroll_to_cell(gobj(), 0, column.gobj(), TRUE, 0.0, col_align);
}

void TreeView::scroll_to_column(TreeViewColumn& column)
{
  gtk_tree_view_scroll_to_cell(gobj(), 0, column.gobj(), FALSE, 0.0, 0.0);
}

void TreeView::remove_all_columns()
{
  //This method is not in GTK+, but it seems useful.

  //Remove all View columns:
  typedef std::list< Gtk::TreeView::Column* > type_vecViewColumns;
  type_vecViewColumns vecViewColumns = get_columns();
  for(type_vecViewColumns::iterator iter = vecViewColumns.begin(); iter != vecViewColumns.end(); ++iter)
  {
    Gtk::TreeView::Column* pViewColumn = *iter;
    if(pViewColumn)
      remove_column(*pViewColumn);
  }
}


CellRenderer* TreeView::get_column_cell_renderer(int n)
{
  TreeViewColumn* pColumn = get_column(n);
  if(pColumn)
    return pColumn->get_first_cell_renderer();
  else
    return 0;
}


const CellRenderer* TreeView::get_column_cell_renderer(int n) const
{
   //Do some const_cast-ing to avoid repetition of code:
  CellRenderer* pRenderer = const_cast<TreeView*>(this)->get_column_cell_renderer(n);
  return pRenderer;
}

void TreeView::reset_expander_column()
{
  gtk_tree_view_set_expander_column(gobj(), 0 /* see C docs */);
}

Glib::RefPtr<Gtk::TreeModel> TreeView::_get_base_model()
{
  Glib::RefPtr<TreeModel> refModel = get_model();

  bool get_child = true;
  while(get_child && refModel)
  {
    Glib::RefPtr<Gtk::TreeModelFilter> refModelFilter = Glib::RefPtr<Gtk::TreeModelFilter>::cast_dynamic(refModel);
    if(refModelFilter)
      refModel = refModelFilter->get_model();
    else
      get_child = false;
  }

  return refModel;
}

void TreeView::_auto_store_on_cellrenderer_toggle_edited(const Glib::ustring& path_string,
                                                         int model_column)
{
  _auto_store_on_cellrenderer_toggle_edited_with_model(path_string, model_column, _get_base_model());
}

void TreeView::_auto_store_on_cellrenderer_toggle_edited_with_model(const Glib::ustring& path_string,
                                                         int model_column, const Glib::RefPtr<Gtk::TreeModel>& model)
{
  Gtk::TreePath path (path_string);

  //Get the row from the path:
  if(model)
  {
    Gtk::TreeModel::iterator iter = model->get_iter(path);
    if(iter)
    {
      Gtk::TreeRow row = *iter;

      //Get the new value:
      //This seems to get the old value, not the new one,
      //so we will just NOT the model value ourselves.
      //bool bActive = cell_renderer->get_active();
      bool bActive = false;
      row.get_value(model_column, bActive);
      bActive = !bActive;

      //Store the user's new text in the model:
      row.set_value(model_column, bActive);
    }
  }
}

void TreeView::move_column_to_start(TreeViewColumn& column)
{
  gtk_tree_view_move_column_after(gobj(), (column).gobj(), 0 /* See C docs */);
}

void TreeView::set_row_separator_func(const SlotRowSeparator& slot)
{
  //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter.
  //It will be deleted when SignalProxy_RowSeparator_gtk_callback_destroy() is called.
  SlotRowSeparator* slot_copy = new SlotRowSeparator(slot);

  gtk_tree_view_set_row_separator_func(gobj(),
      &TreeView_Private::SignalProxy_RowSeparator_gtk_callback, slot_copy,
      &TreeView_Private::SignalProxy_RowSeparator_gtk_callback_destroy);
}

void TreeView::set_search_position_func(const SlotSearchPosition& slot)
{
  //Create a copy of the slot. A pointer to this will be passed through the callback's data parameter.
  //It will be deleted when SignalProxy_SearchPosition_gtk_callback_destroy() is called.
  SlotSearchPosition* slot_copy = new SlotSearchPosition(slot);

  gtk_tree_view_set_search_position_func(gobj(),
      &SignalProxy_SearchPosition_gtk_callback, slot_copy,
      &SignalProxy_SearchPosition_gtk_callback_destroy);
}

bool TreeView::get_visible_range(TreeModel::Path& start_path, TreeModel::Path& end_path) const
{
  GtkTreePath* pTreePathStart = 0;
  GtkTreePath* pTreePathEnd = 0;
  bool result = gtk_tree_view_get_visible_range(const_cast<GtkTreeView*>(gobj()), &pTreePathStart, &pTreePathEnd);

  start_path = TreeModel::Path(pTreePathStart, false /* don't take a copy, because the gtk_tree_view_get_visible_range() docs say that we must free the path */ );
  end_path = TreeModel::Path(pTreePathEnd, false /* don't take a copy, because the gtk_tree_view_get_visible_range() docs say that we must free the path */ );
  return result;
}

void TreeView::unset_model()
{
  gtk_tree_view_set_model(gobj(), 0);
}

bool
TreeView::get_tooltip_context_path(int& x, int& y,
                                   bool keyboard_tip,
                                   TreeModel::Path& path)
{
  //It's cleaner to use a temporary C++ object and get a C pointer to it,
  //because GtkTreePath is a simple struct, not a GtkObject, so
  //gtk_tree_path_new() would be necessary. markoa.
  TreeModel::Path tmp_path;
  GtkTreePath* cpath = tmp_path.gobj();

  gboolean result =
    gtk_tree_view_get_tooltip_context(gobj(),
                                      &x, &y,
                                      keyboard_tip,
                                      0,
                                      &cpath,
                                      0);

  path = Glib::wrap(cpath, false /* take_copy=false */);

  return result;
}

bool
TreeView::get_tooltip_context_iter(int& x, int& y,
                                   bool keyboard_tip,
                                   Gtk::TreeModel::iterator& iter)
{
  GtkTreeIter src_iter;

  gboolean result =
    gtk_tree_view_get_tooltip_context(gobj(),
                                      &x, &y,
                                      keyboard_tip,
                                      0,
                                      0,
                                      &src_iter);

  iter = TreeIter(gtk_tree_view_get_model(this->gobj()), &src_iter);

  return result;
}

} // namespace Gtk
