/* 
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 * 
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_treenodeview.h"
#include "wf_grid.h"
#include "wf_grttreeview.h"

#include "ConvUtils.h"

using namespace MySQL::Forms;
using namespace mforms;

using namespace System::Windows::Forms::VisualStyles;
using namespace System::Globalization;
using namespace System::Drawing::Html;

using namespace Aga::Controls;

//----------------- GridTree -----------------------------------------------------------------------

void GridTree::DrawRow(PaintEventArgs^ e, DrawContext% context, int row, System::Drawing::Rectangle rowRect)
{
  TreeNodeAdv^ node = RowMap[row];
  GridNode^ gridNode = dynamic_cast<GridNode^>(node->Tag);

  rowRect.Width = ClientRectangle.Width;

  switch (gridNode->CellType[0])
  {
  case mforms::CellHeader:
    {
      // Make the entire row showing the selection color if the node is selected (not just the cells).
      if (node->IsSelected && Focused)
      {
        e->Graphics->FillRectangle(SystemBrushes::Highlight, rowRect);
        context.DrawSelection = DrawSelectionMode::Active; // To adjust text colors.
      }
      else
      {
        // Cell headers span an entire row, cannot be selected, but expanded/collapsed.
        SolidBrush^ brush = gcnew SolidBrush(Color::FromArgb(255, 225, 225, 225));
        e->Graphics->FillRectangle(brush, rowRect);
        delete brush;

        context.DrawSelection = DrawSelectionMode::None;
      }

      context.DrawFocus = false;

      DrawNode(node, context);
    }
    break;
  case mforms::CellGroupHeader:
    {
      // Make the entire row showing the selection color if the node is selected (not just the cells).
      if (node->IsSelected && Focused)
      {
        e->Graphics->FillRectangle(SystemBrushes::Highlight, rowRect);
        context.DrawSelection = DrawSelectionMode::Active;
      }
      else
      {
        // Cell group headers span an entire row, cannot be selected and have a bold caption.
        SolidBrush^ brush = gcnew SolidBrush(BackColor);
        e->Graphics->FillRectangle(brush, rowRect);
        delete brush;

        context.DrawSelection = DrawSelectionMode::None;
      }

      context.DrawFocus = false;

      DrawNode(node, context);
    }
    break;
  default:
    // Make the entire row showing the selection color if the node is selected (not just the cells).
    if (node->IsSelected && Focused)
      e->Graphics->FillRectangle(SystemBrushes::Highlight, rowRect);

    TreeViewAdv::DrawRow(e, context, row, rowRect);
    break;
  }
}

//----------------- GridNode -----------------------------------------------------------------------

GridNode::GridNode()
  : Node()
{
}

//--------------------------------------------------------------------------------------------------

GridNode::~GridNode()
{
}

//--------------------------------------------------------------------------------------------------

/**
 * Ensures our value list has at least as many entries as given by index.
 */
void GridNode::ensureIndexes(int index)
{
  while (index >= values.Count)
    values.Add(gcnew CellValues());
}

//--------------------------------------------------------------------------------------------------

bool GridNode::IsEditable::get(int index)
{
  ensureIndexes(index);
  return values[index]->editable;
}

//--------------------------------------------------------------------------------------------------

void GridNode::IsEditable::set(int index, bool flag)
{
  ensureIndexes(index);
  if (index >= 0)
    values[index]->editable = flag;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->editable = flag;
  }
}

//--------------------------------------------------------------------------------------------------

String^ GridNode::Text::get(int index)
{
  ensureIndexes(index);
  return values[index]->text;
}

//--------------------------------------------------------------------------------------------------

void GridNode::Text::set(int index, String^ text)
{
  ensureIndexes(index);
  if (index >= 0)
    values[index]->text = text;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->text = text;
  }
}

//--------------------------------------------------------------------------------------------------

mforms::CellType GridNode::CellType::get(int index)
{
  ensureIndexes(index);
  return values[index]->type;
}

//--------------------------------------------------------------------------------------------------

void GridNode::CellType::set(int index, mforms::CellType type)
{
  ensureIndexes(index);

  // Special condition: if index is < 0 then all cells are affected.
  if (index >= 0)
    values[index]->type = type;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->type = type;
  }
}

//--------------------------------------------------------------------------------------------------

mforms::CellAttr GridNode::CellAttributes::get(int index)
{
  ensureIndexes(index);
  return values[index]->attributes;
}

//--------------------------------------------------------------------------------------------------

void GridNode::CellAttributes::set(int index, mforms::CellAttr attr)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->attributes = attr;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->attributes = attr;
  }
}

//--------------------------------------------------------------------------------------------------

Color GridNode::ForeColor::get(int index)
{
  ensureIndexes(index);
  return values[index]->foreground;
}

//--------------------------------------------------------------------------------------------------

void GridNode::ForeColor::set(int index, Color color)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->foreground = color;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->foreground = color;
  }
}

//--------------------------------------------------------------------------------------------------

Color GridNode::BackColor::get(int index)
{
  ensureIndexes(index);
  return values[index]->background;
}

//--------------------------------------------------------------------------------------------------

void GridNode::BackColor::set(int index, Color color)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->background = color;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->background = color;
  }
}

//--------------------------------------------------------------------------------------------------

List<String^>^ GridNode::LookupValues::get(int index)
{
  ensureIndexes(index);
  return values[index]->lookupValues;
}

//--------------------------------------------------------------------------------------------------

void GridNode::LookupValues::set(int index, List<String^>^ _values)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->lookupValues = _values;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->lookupValues = _values;
  }
}

//--------------------------------------------------------------------------------------------------

String^ GridNode::IconPath::get(int index)
{
  ensureIndexes(index);
  return values[index]->iconPath;
}

//--------------------------------------------------------------------------------------------------

void GridNode::IconPath::set(int index, String^ path)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->iconPath = path;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->iconPath = path;
  }
}

//--------------------------------------------------------------------------------------------------

mforms::IconPos GridNode::IconAlignment::get(int index)
{
  ensureIndexes(index);
  return values[index]->iconAlignment;
}

//--------------------------------------------------------------------------------------------------

void GridNode::IconAlignment::set(int index, mforms::IconPos alignment)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->iconAlignment = alignment;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->iconAlignment = alignment;
  }
}

//--------------------------------------------------------------------------------------------------

mforms::IconVisibility GridNode::IconVisiblity::get(int index)
{
  ensureIndexes(index);
  return values[index]->iconVisibility;
}

//--------------------------------------------------------------------------------------------------

void GridNode::IconVisiblity::set(int index, mforms::IconVisibility visiblity)
{
  ensureIndexes(index);

  if (index >= 0)
    values[index]->iconVisibility = visiblity;
  else
  {
    for (int i = 0; i < values.Count; ++i)
      values[i]->iconVisibility = visiblity;
  }
}

//----------------- MainNodeControl ----------------------------------------------------------------

MainNodeControl::MainNodeControl()
{
  check = gcnew Bitmap("images/ui/check.png", true);
  uncheck = gcnew Bitmap("images/ui/uncheck.png", true);
  unknown = gcnew Bitmap("images/ui/unknown.png", true);
}

//--------------------------------------------------------------------------------------------------

CheckState MainNodeControl::GetCheckState(TreeNodeAdv^ node)
{
  Object^ obj = GetValue(node);
  if (is<bool>(obj))
  {
    if ((bool)obj)
      return CheckState::Checked;
    return CheckState::Unchecked;
  }

  if (is<CheckState>(obj))
    return (CheckState)obj;

  if (is<String^>(obj))
  {
    String^ s = (String^)obj;
    if (s == "1" || s->ToUpper() == "YES")
      return CheckState::Checked;
    return CheckState::Unchecked;
  }
  else
    return CheckState::Unchecked;
}

//--------------------------------------------------------------------------------------------------

CheckState MainNodeControl::GetNewState(CheckState state)
{
  switch (state)
  {
  case CheckState::Indeterminate:
    return CheckState::Unchecked;
  case CheckState::Unchecked:
    return CheckState::Checked;
  default:
    return CheckState::Unchecked;
  }
}

//--------------------------------------------------------------------------------------------------

System::Drawing::Rectangle MainNodeControl::GetCheckBoxBounds(TreeNodeAdv^ node, DrawContext context)
{
  System::Drawing::Rectangle bounds = context.Bounds;
  bounds.X = bounds.Right - 13 - 3; // Right aligned checkbox. Subtract 3 as right margin.
  bounds.Y += (bounds.Height - 13) / 2; // Vertically centered.

  return bounds;
}

//--------------------------------------------------------------------------------------------------

void MainNodeControl::SetCheckState(TreeNodeAdv^ node, CheckState value)
{
  if (VirtualMode)
    SetValue(node, value);
  else
  {
    Type^ type = GetPropertyType(node);
    if (type == CheckState::typeid)
      SetValue(node, value);
    else
      if (type == bool::typeid)
        SetValue(node, value != CheckState::Unchecked);
  }
}

//--------------------------------------------------------------------------------------------------

void MainNodeControl::MouseDown(TreeNodeAdvMouseEventArgs^ args)
{
  if (!args->Handled)
  {
    if (args->Button == MouseButtons::Left)
    {
      GridNode^ gridNode = dynamic_cast<GridNode^>(args->Node->Tag);
      if (gridNode->CellType[0] == mforms::CellBool)
      {
        DrawContext context;
        context.Bounds = args->ControlBounds;
        System::Drawing::Rectangle rect = GetCheckBoxBounds(args->Node, context);
        if (rect.Contains(args->ViewLocation))
        {
          CheckState state = GetCheckState(args->Node);
          state = GetNewState(state);
          SetCheckState(args->Node, state);
          Parent->Invalidate(); // Would be better to only invalidate the checkbox rectangle but
                                // the tree does not redraw properly with that, so we need a full invalidation.
          args->Handled = true;
        }
      }
    }
  }
  NodeTextBox::MouseDown(args);
}

//--------------------------------------------------------------------------------------------------

void MainNodeControl::MouseDoubleClick(TreeNodeAdvMouseEventArgs^ args)
{
  if (!args->Handled)
  {
    GridNode^ gridNode = dynamic_cast<GridNode^>(args->Node->Tag);
    if (gridNode->CellType[0] == mforms::CellBool)
      args->Handled = true;
  }

  NodeTextBox::MouseDoubleClick(args);
}

//--------------------------------------------------------------------------------------------------

System::Drawing::Size MainNodeControl::MeasureSize(TreeNodeAdv^ node, DrawContext context)
{
  GridNode^ gridNode = dynamic_cast<GridNode^>(node->Tag);
  if (gridNode->CellType[0] == mforms::CellBool)
    return System::Drawing::Size(13, 13);

  return NodeTextBox::MeasureSize(node, context);
}

//--------------------------------------------------------------------------------------------------

void MainNodeControl::Draw(TreeNodeAdv^ node, DrawContext context)
{
  if (context.CurrentEditorOwner == this && node == Parent->CurrentNode)
    return;

  GridNode^ gridNode = dynamic_cast<GridNode^>(node->Tag);

  System::Drawing::Font^ font = context.Font;
  System::Drawing::Rectangle bounds = GetBounds(node, context);
  switch (gridNode->CellType[0])
  {
  case mforms::CellGroupHeader:
    font = gcnew System::Drawing::Font(font, FontStyle::Bold);
    bounds.X -= 16; // The column is set to have expand triangles, but we don't show any for this cell type.
    // Fall through.
  case mforms::CellHeader:
    break;

  case mforms::CellBool:
    {
      // Don't draw text, but a checkbox instead.
      CheckState state = GetCheckState(node);
      System::Drawing::Rectangle bounds = GetCheckBoxBounds(node, context);
      if (Application::RenderWithVisualStyles)
      {
        VisualStyleRenderer^ renderer;
        if (state == CheckState::Indeterminate)
          renderer = gcnew VisualStyleRenderer(VisualStyleElement::Button::CheckBox::MixedNormal);
        else
          if (state == CheckState::Checked)
            renderer = gcnew VisualStyleRenderer(VisualStyleElement::Button::CheckBox::CheckedNormal);
          else
            renderer = gcnew VisualStyleRenderer(VisualStyleElement::Button::CheckBox::UncheckedNormal);
        renderer->DrawBackground(context.Graphics, System::Drawing::Rectangle(bounds.Left, bounds.Top, 13, 13));
      }
      else
      {
        Image^ img;
        if (state == CheckState::Indeterminate)
          img = unknown;
        else
          if (state == CheckState::Checked)
            img = check;
          else
            img = uncheck;
        context.Graphics->DrawImage(img, bounds.Location);
      }
    }
    return;

  default:
    NodeTextBox::Draw(node, context);
    return;
  }

  String^ label = GetLabel(node);

  // Extend the bounds to full row (we are drawing only multi cell strings here).
  bounds.Width = Parent->ClientRectangle.Width;
  context.Graphics->ResetClip();

  Color textColor = SystemColors::ControlText;
  if (context.DrawSelection == DrawSelectionMode::Active)
    textColor = SystemColors::HighlightText;

  if (UseCompatibleTextRendering)
  {
    TextFormatFlags flags = TextFormatFlags::NoClipping |
      TextFormatFlags::PreserveGraphicsTranslateTransform |
      TextHelper::TranslateAligmentToFlag(TextAlign) |
      TextHelper::TranslateTrimmingToFlag(Trimming);

    TextRenderer::DrawText(context.Graphics, label, font, bounds, textColor, flags);
  }
  else
  {
    Brush^ textBrush = gcnew SolidBrush(textColor);
    StringFormat^ format = gcnew StringFormat();
    format->Alignment = TextHelper::TranslateAligment(TextAlign);
    format->Trimming = StringTrimming::None;

    context.Graphics->DrawString(label, font, textBrush, bounds, format);
    delete textBrush;
  }
}

//----------------- EditableNodeControl ----------------------------------------------------------------

EditableNodeControl::EditableNodeControl()
{

}

//--------------------------------------------------------------------------------------------------


//----------------- GridImpl -----------------------------------------------------------------------

GridImpl::GridImpl(mforms::Grid *self)
  : ViewImpl(self)
{
  _model = gcnew SortableTreeModel();
  _current_sort_column = -1;
  _can_sort_column = false;
  _current_sort_order = SortOrder::None;
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::create(mforms::Grid* self)
{
  GridImpl^ gridImpl = gcnew GridImpl(self);

  if (gridImpl != nullptr)
  {
    // Just like in TreeViewImpl and GrtTreeViewImpl we are using a custom class derived from
    // TreeViewAdv as actual control.
    GridTree^ t= ViewImpl::create<GridTree>(self, gridImpl);
    t->LoadOnDemand = true;
    t->Model= gridImpl->_model;
//    t->SelectionChanged += gcnew System::EventHandler(&GridImpl::SelectionChanged);
//    t->NodeMouseDoubleClick += gcnew System::EventHandler<TreeNodeAdvMouseEventArgs^>(&GridImpl::NodeActivated);
    t->UseColumns= true;
//    t->MouseDown += gcnew MouseEventHandler(&GridImpl::OnMouseDown);
//    t->ColumnClicked += gcnew System::EventHandler<TreeColumnEventArgs^>(gridImpl, &GridImplGridImplOnColumnClicked);
    t->BorderStyle = BorderStyle::None;

    t->ShowPlusMinus = false; // We show our own expand icons.
    t->ShowLines = false;
    return true;
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

int GridImpl::add_column(mforms::Grid* self, const std::string& name)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    GridTree^ treeview = grid->get_control<GridTree>();

    TreeColumn^ column = gcnew TreeColumn(CppStringToNative(name), 50);
    treeview->Columns->Add(column);

    NodeTextBox^ box;

    // If this is the main column then add our expand node control first.
    if (treeview->Columns->Count == 1)
    {
      TriangleNodeControl^ control = gcnew TriangleNodeControl();
      control->ParentColumn = column;
      treeview->NodeControls->Add(control);
      box = gcnew MainNodeControl();
    }
    else
    {
      // By default we add a non-editable text column. Since this is a grid control the type of a 
      // single cell is controlled separately and so is the node control.
      box = gcnew EditableNodeControl();
    }

    box->EditEnabled = false;
    box->Trimming = StringTrimming::EllipsisCharacter;
    box->VirtualMode = true; // Means: get value for that column via event.
    box->ValueNeeded += gcnew System::EventHandler<NodeControlValueEventArgs^>(grid, &GridImpl::TreeValueNeeded);
    box->ValuePushed += gcnew System::EventHandler<NodeControlValueEventArgs^>(grid, &GridImpl::TreeValuePushed);
    box->LeftMargin = 3;
    box->ParentColumn = column;
    treeview->NodeControls->Add(box);

    return column->Index;
  }
  return -1;
}

//--------------------------------------------------------------------------------------------------

void GridImpl::clear(mforms::Grid* self)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
    grid->_model->Nodes->Clear();
}

//--------------------------------------------------------------------------------------------------

int GridImpl::get_children_count(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    TreeNodeAdv^ node = grid->visual_node_for_path(path, -1);
    if (node != nullptr)
      return node->Children->Count;
  }

  return 0;
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::is_node_expanded(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    TreeNodeAdv^ node = grid->visual_node_for_path(path, -1);
    if (node != nullptr)
      return node->IsExpanded;
  }

  return false;
}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_node_expanded(mforms::Grid* self, const mforms::GridPath& path, const bool expanded)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    TreeNodeAdv^ node = grid->visual_node_for_path(path, -1);
    if (node != nullptr)
      node->IsExpanded = expanded;
  }
}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_column_width(mforms::Grid* self, const int column, const int width)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    GridTree^ treeview = grid->get_control<GridTree>();
    treeview->Columns[column]->Width = width;
  }
}

//--------------------------------------------------------------------------------------------------

GridPath GridImpl::append_header(mforms::Grid* self, const std::string& gid)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    GridNode^ node = gcnew GridNode();
    node->Text[0] = CppStringToNative(gid);
    node->CellType[0] = mforms::CellHeader;
    grid->_model->Nodes->Add(node);

    return grid->path_for_node(node);
  }
  return GridPath();
}

//--------------------------------------------------------------------------------------------------

GridPath GridImpl::append_row(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    // Currently the design in the backend is telling us to limit search to the first level only.
    // This also applies to some of the other functions taking a path.
    // Hopefully one day this nonsense is lifted to make the grid the more generic control it should be.
    GridNode^ node = grid->model_node_for_path(path, 1);
    if (node != nullptr)
    {
      GridTree^ treeview = grid->get_control<GridTree>();
      GridNode^ newNode = gcnew GridNode();
      newNode->ForeColor[-1] = treeview->ForeColor;
      newNode->BackColor[-1] = treeview->BackColor;
      node->Nodes->Add(newNode);

      // Expand the normal rows right after creation. This way they lose their temporary expand icon
      // which would otherwise show up. They don't have children anyway.
      GridPath newPath = grid->path_for_node(newNode);
      TreeNodeAdv^ treeNode = grid->visual_node_for_path(newPath, -1);
      treeNode->IsExpanded = true;

      return newPath;
    }
  }
  return GridPath();
}

//--------------------------------------------------------------------------------------------------

GridPath GridImpl::insert_row(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    Node^ node = grid->model_node_for_path(path, 2);
    if (node != nullptr)
    {
      // "Insert" actually means append after the node addressed by path, but not deeper than 2 levels.
      int index = node->Index + 1;
      GridTree^ treeview = grid->get_control<GridTree>();
      GridNode^ newNode = gcnew GridNode();
      newNode->ForeColor[-1] = treeview->ForeColor;
      newNode->BackColor[-1] = treeview->BackColor;
      if (node->Parent == nullptr)
        grid->_model->Nodes->Insert(index, newNode);
      else
        node->Parent->Nodes->Insert(index, newNode);

      return grid->path_for_node(newNode);
    }
  }
  return GridPath();
}

//--------------------------------------------------------------------------------------------------

void GridImpl::remove_row(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      if (node->Parent != nullptr)
        node->Parent->Nodes->Remove(node);
      else
        grid->_model->Nodes->Remove(node);
    }
  }
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::set_str_value(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const std::string& cv, const bool editable)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->Text[col_id] = CppStringToNative(cv);
      node->CellType[col_id] = mforms::CellText; // Implicitly make this a text cell now.
      node->IsEditable[col_id] = editable;
      grid->get_control()->Invalidate(); // Might lead to flickering, investigate if necessary.

      return true;
    }
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::set_bool_value(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  bool cv, const bool editable)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->Text[col_id] = cv ? "1" : "0";
      node->CellType[col_id] = mforms::CellBool; // Implicitly make this a bool cell now.

      node->IsEditable[col_id] = editable;
      grid->get_control()->Invalidate();

      return true;
    }
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

std::string GridImpl::get_value(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  mforms::CellType* type)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      return NativeToCppString(node->Text[col_id]);
  }
  return "";
}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_cell_type(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const mforms::CellType type)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      node->CellType[col_id] = type;
  }
}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_cell_attr(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const int attr)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      node->CellAttributes[col_id] = (mforms::CellAttr)attr;
  }
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::set_fg(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const double r, const double g, const double b)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->ForeColor[col_id] = Color::FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
      return true;
    }
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::set_bg(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const double r, const double g, const double b)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->BackColor[col_id] = Color::FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
      return true;
    }
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::set_enum(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const std::vector<std::string>& list)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->LookupValues[col_id] = CppStringListToNative(list);
      return true;
    }
  }
  
  return false;
}

//--------------------------------------------------------------------------------------------------

// ownership of the vector @list will be passed to the GridCell
bool GridImpl::set_enum_def(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  std::vector<std::string>* list)
{
  bool result = set_enum(self, path, col_id, *list);
  delete list;

  return result;
}

//--------------------------------------------------------------------------------------------------

// ownership of the char** @list will not be passed to the GridCell
bool GridImpl::set_enum_def_c(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const char** const list)
{
  std::vector<std::string> real_list;
  const char** runner = list;
  while (*runner != NULL && real_list.size() < 100) // Just in case, check if we are not running endlessly.
  {
    real_list.push_back(*runner);
    runner++;
  }
  
  return set_enum(self, path, col_id, real_list);
}

//--------------------------------------------------------------------------------------------------

void GridImpl::shade(mforms::Grid* self, const mforms::GridPath& path, const mforms::Shade shade,
  const int col_id)
{
  // Shades are not used yet.
}

//--------------------------------------------------------------------------------------------------

void GridImpl::unshade(mforms::Grid* self, const mforms::GridPath& path, const mforms::Shade shade,
  const int col_id)
{
  // Shades are not used yet.
}

//--------------------------------------------------------------------------------------------------

bool GridImpl::has_shade(mforms::Grid* self, const mforms::GridPath& path, const int col_id,
  const mforms::Shade s)
{
  return false;
}

//--------------------------------------------------------------------------------------------------

void GridImpl::scroll_to_row(mforms::Grid* self, const mforms::GridPath& path)
{

}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_row_tag(mforms::Grid* self, const mforms::GridPath& path, const std::string& tag)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      node->Tag = CppStringToNative(tag);
  }
}

//--------------------------------------------------------------------------------------------------

std::string GridImpl::get_row_tag(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      return NativeToCppString(node->Tag->ToString());
  }

  return "";
}

//--------------------------------------------------------------------------------------------------

/**
 * Special setter for text that is to be used for group headers only. Technically it doesn't make
 * a difference here what the meaning is, though. The text is used for the cell at column 0.
 * To be on the safe side all other text is removed and the cell style is set to group header.
 */
void GridImpl::set_row_caption(mforms::Grid* self, const mforms::GridPath& path, const std::string& caption)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      GridTree^ treeview = grid->get_control<GridTree>();
      node->Text[0] = CppStringToNative(caption);
      node->CellType[0] = mforms::CellGroupHeader;
      for (int i = 1; i < treeview->Columns->Count; ++i)
      {
        node->Text[i] = "";
        node->CellType[i] = mforms::CellGroupHeader;
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------

std::string GridImpl::get_row_caption(mforms::Grid* self, const mforms::GridPath& path)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
      return NativeToCppString(node->Text[0]);
  }
  return "";
}

//--------------------------------------------------------------------------------------------------

void GridImpl::set_action_icon(mforms::Grid* self, const mforms::GridPath& path, const int col,
  const std::string& iconpath, const mforms::IconVisibility visible, const mforms::IconPos pos)
{
  GridImpl^ grid = (GridImpl^)ObjectImpl::FromUnmanaged(self);
  if (grid != nullptr && path.size() > 0)
  {
    GridNode^ node = grid->model_node_for_path(path, -1);
    if (node != nullptr)
    {
      node->IconPath[col] = CppStringToNative(iconpath);
      node->IconAlignment[col] = pos;
      node->IconVisiblity[col] = visible;
    }
  }
}

//--------------------------------------------------------------------------------------------------

void GridImpl::popup_context_menu(mforms::Grid* self)
{

}

//--------------------------------------------------------------------------------------------------

TreeNodeAdv^ GridImpl::visual_node_for_path(const mforms::GridPath& path, int limit)
{
  GridTree^ treeview = get_control<GridTree>();

  // Limit the search to a certain maximum depth if given.
  if (limit < 0 || path.size() < limit)
    limit = path.size();
  int level = 0;
  TreeNodeAdv^ result = treeview->Root->Children[path[level++]];
  while (level < limit && path[level] < result->Children->Count)
    result = result->Children[path[level++]];

  return result;
}

//--------------------------------------------------------------------------------------------------

MySQL::Forms::GridNode^ GridImpl::model_node_for_path(const mforms::GridPath& path, int limit)
{
  GridTree^ treeview = get_control<GridTree>();

  if (limit < 0 || path.size() < limit)
    limit = path.size();
  int level = 0;
  Node^ result = _model->Nodes[path[level++]];
  while (level < limit && path[level] < result->Nodes->Count)
    result = result->Nodes[path[level++]];

  return dynamic_cast<MySQL::Forms::GridNode^>(result);
}

//--------------------------------------------------------------------------------------------------

GridPath GridImpl::path_for_node(Node^ node)
{
  // We actually have MySQL::Forms::TreeNode here, but for this determination we can go with the
  // ancestor for simplicity.
  array<Object^>^ nativePath = _model->GetPath(node)->FullPath;

  GridPath result;
  for each(Object^ object in nativePath)
    result.append(((Node^)object)->Index);

  return result;
}

//--------------------------------------------------------------------------------------------------

void GridImpl::TreeValueNeeded(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  GridNode^ ourNode= dynamic_cast<GridNode^>(treeNode->Tag);

  // The passed in TreeNodeAdv can be the hidden root node, so better check it.
  if (ourNode != nullptr)
  {
    String^ value= ourNode->Text[control->ParentColumn->Index];
    if (control->GetType() == NodeCheckBox::typeid)
      e->Value = (value->ToLower() == "checked") || (value->ToLower() == "yes") || (value == "1") ? true : false;
    else
      if (control->GetType() == NodeIcon::typeid)
      {
        std::string path = mforms::App::get()->get_resource_path(NativeToCppString(value));
        e->Value = gcnew Bitmap(CppStringToNative(path));
      }
      else
        e->Value= value;
  }
}

//--------------------------------------------------------------------------------------------------

void GridImpl::TreeValuePushed(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  GridNode^ ourNode= dynamic_cast<GridNode^>(treeNode->Tag);

  // The passed in TreeNodeAdv can be the hidden root node, so better check it.
  if (ourNode != nullptr)
  {
    bool contentSet = false;
    if (is<String^>(e->Value))
    {
      ourNode->Text[control->ParentColumn->Index] = (String^)e->Value;
      contentSet = true;
    }
    else
      if (is<bool>(e->Value))
      {
        bool value = (bool)e->Value;
        ourNode->Text[control->ParentColumn->Index] = value ? "1" : "0";
        contentSet = true;
      }
      else
        if (is<CheckState>(e->Value))
        {
          CheckState value = (CheckState)e->Value;
          ourNode->Text[control->ParentColumn->Index] = value == CheckState::Checked ? "1" : "0";
          contentSet = true;
        }

      // Everything else is ignored for now.

      if (contentSet)
      {
        // Notify backend about the change.
        Grid* grid = ObjectImpl::get_backend_control<Grid>(control->Parent);
        (*grid->signal_content_edited())(path_for_node(ourNode), control->ParentColumn->Index);
      }
  }
}

//--------------------------------------------------------------------------------------------------
