/* 
 * Copyright (c) 2011, 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 "grts/structs.db.mgmt.h"

#include "base/string_utilities.h"
#include "base/geometry.h"
#include "base/drawing.h"
#include "base/log.h"

#include "mforms/utilities.h"

using namespace MySQL::Geometry;
using namespace MySQL::Drawing;

DEFAULT_LOG_DOMAIN("AdvancedSidebar");

#include "advanced_sidebar.h"

#include <math.h>

using namespace std;

using namespace wb;
using namespace mforms;


#ifdef _WIN32
  #define SIDEBAR_FONT "Segoe UI"
  #define SIDEBAR_TITLE_FONT_SIZE 11
  #define SIDEBAR_ENTRY_FONT_SIZE 11
#else
#ifdef __APPLE__
  #define SIDEBAR_FONT "Lucida Grande"
  #define SIDEBAR_TITLE_FONT_SIZE 11
  #define SIDEBAR_ENTRY_FONT_SIZE 11
#else
  #define SIDEBAR_FONT "Tahoma"
  #define SIDEBAR_TITLE_FONT_SIZE 11
  #define SIDEBAR_ENTRY_FONT_SIZE 11
#endif
#endif

#define SECTION_ENTRY_HEIGHT 20  // Height of a single section entry.
#define SECTION_ENTRY_SPACING 0  // Vertical distance between two section entries.
#define SECTION_ENTRY_INDENT 13  // Horizontal distance from the left section border to the entry icon.
#define SECTION_ENTRY_ICON_SPACING 6  // Horizontal distance between entry icon and text.

//----------------- SidebarSection -----------------------------------------------------------------

SidebarEntry::SidebarEntry(const string& title, const string& icon, const string& command, bool as_link)
{
  _title = title;
  _icon = Utilities::load_icon(icon);
  _command = command;
  _is_link = as_link;
}

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

SidebarEntry::~SidebarEntry()
{
  if (_icon!= NULL)
    cairo_surface_destroy(_icon);
}

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

void SidebarEntry::paint(cairo_t* cr, Rect bounds, bool hot, bool active, const Color& selection_color)
{
  // Fill background if the item is active.
  if (active)
  {
    cairo_set_source_rgb(cr, selection_color.red, selection_color.green, selection_color.blue);
    cairo_rectangle(cr, 2, bounds.top(), bounds.left() + bounds.width() - 4, bounds.height());
    cairo_fill(cr);
  }

  cairo_move_to(cr, bounds.left(), bounds.top());

  double offset;
  if (_icon != NULL)
  {
    offset = (bounds.height() - cairo_image_surface_get_height(_icon)) / 2;
    cairo_set_source_surface(cr, _icon, bounds.left(), bounds.top() + offset);
    cairo_paint(cr);
    cairo_rel_move_to(cr, cairo_image_surface_get_width(_icon) + SECTION_ENTRY_ICON_SPACING, 0);
  }

  cairo_select_font_face(cr, SIDEBAR_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
  cairo_set_font_size(cr, SIDEBAR_ENTRY_FONT_SIZE);

  if (active)
    cairo_set_source_rgb(cr, 1, 1, 1);
  else
    cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_rel_move_to(cr, 0, (bounds.height() + SIDEBAR_ENTRY_FONT_SIZE) / 2 - 2);
  cairo_show_text(cr, _title.c_str());

  if (hot)
  {
    // Use the color we set for the text.
    cairo_set_line_width(cr, 1);

    cairo_text_extents_t extents;
    cairo_text_extents(cr, _title.c_str(), &extents);

    double width = ceil(extents.width);
    cairo_rel_move_to(cr, -width, 2);
    cairo_rel_line_to(cr, width, 0);
    cairo_stroke(cr);
  }
}

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

bool SidebarEntry::contains(double x, double y)
{
  return false;
}

//----------------- SidebarSection -----------------------------------------------------------------

SidebarSection::SidebarSection(AdvancedSidebar* owner, const std::string& title, bool expandable,
  bool refreshable)
  : DrawBox()
{
  _owner = owner;
  _title = title;
  _selected_entry = NULL;
  _hot_entry = NULL;

  _layout_width = 0;
  _layout_height = 0;
  _expanded = true;
  _expandable = expandable;
  _expand_text_visible = false;
  _expand_text_width = 0;
  _expand_text_active = false;
  _refresh_hot = false;
  _refresh_down = false;

  if (refreshable)
  {
#ifdef __APPLE__
    _refresh_icon = Utilities::load_icon("refresh_sidebar_mac.png");
#else
    _refresh_icon = Utilities::load_icon("refresh_sidebar.png");
#endif
  }
  else
    _refresh_icon = NULL;
}

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

SidebarSection::~SidebarSection()
{
  clear();
  if (_refresh_icon != NULL)
    cairo_surface_destroy(_refresh_icon);
}

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

void SidebarSection::set_selected(SidebarEntry* entry)
{
  if (entry)
    _owner->clear_selection();

  if (_selected_entry != entry)
  {
    _selected_entry= entry;
    set_needs_repaint();
    if (entry)
      (*_owner->on_section_command())(entry->command());
  }
}

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

#define SECTION_TOP_SPACING 4    // Vertical distance from the top border to the heading.
#define SECTION_SIDE_SPACING 6   // Horizontal spacing between border and content.
#define SECTION_BOTTOM_SPACING 7 // Vertical distance from the content to the bottom.
#define SECTION_HEADER_SPACING 6 // Vertical distance between the heading bottom line and the content top.
#define SECTION_HEADER_HEIGHT 12 // Height of the section heading.
#define SECTION_HEADER_REFRESH_SPACING 4 // Horizontal distance between collapse text and refresh button.

void SidebarSection::layout(cairo_t* cr)
{
  if (is_layout_dirty())
  {
    set_layout_dirty(false);

    _layout_height= SECTION_TOP_SPACING + SECTION_HEADER_HEIGHT;

    if (_expanded)
    {
      if (_entries.size() > 0)
        _layout_height += SECTION_HEADER_SPACING;
      _layout_width= SECTION_SIDE_SPACING;

      if (_entries.size() > 0)
        _layout_height += _entries.size() * SECTION_ENTRY_HEIGHT + (_entries.size() - 1) * SECTION_ENTRY_SPACING;
    }

    _layout_height += SECTION_BOTTOM_SPACING;
  }

  // Precompute size of the hide/show text for hit tests.
  if (cr != NULL)
  {
    std::string expand_text = _expanded ? _("Hide") : _("Show");
    cairo_text_extents_t extents;

    cairo_select_font_face(cr, SIDEBAR_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, SIDEBAR_TITLE_FONT_SIZE);
    cairo_text_extents(cr, expand_text.c_str(), &extents);
    _expand_text_width = (int) ceil(extents.x_advance);
  }
}

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

SidebarEntry* SidebarSection::entry_from_point(double x, double y)
{
  if (x < 0 || y < SECTION_TOP_SPACING + SECTION_HEADER_HEIGHT + SECTION_HEADER_SPACING 
    || x > get_width() || y > get_height() || _entries.size() == 0)
    return NULL;

  y -= SECTION_TOP_SPACING + SECTION_HEADER_HEIGHT + SECTION_HEADER_SPACING;

  int index = (int) y / (SECTION_ENTRY_HEIGHT + SECTION_ENTRY_SPACING);
  if (index < (int) _entries.size())
    return _entries[index];
  else
    return NULL;
}

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

/**
 * Find an entry with the given title and return its index or -1 if there is none.
 */
int SidebarSection::find_entry(const std::string& title)
{
  for (size_t i = 0; i < _entries.size(); i++)
  {
    if (_entries[i]->title() == title)
      return i;
  }

  return -1;
}

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

void SidebarSection::toggle_expand()
{
  _expanded = !_expanded;
  set_layout_dirty(true);
  set_needs_repaint();
  relayout();

  _expanded_changed(this);
}

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

int SidebarSection::add_entry(const std::string& title, const std::string& icon,
  const std::string& command, bool as_link)
{
  int result = find_entry(title);
  if (result > -1)
    return result;

  SidebarEntry* entry = new SidebarEntry(title, icon, command, as_link);
  _entries.push_back(entry);
  set_layout_dirty(true);

  return _entries.size() -1 ;
}

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

void SidebarSection::remove_entry(const std::string& entry)
{
  int index = find_entry(entry);
  if (index < 0)
    return;

  delete _entries[index];
  _entries.erase(_entries.begin() + index);

  set_layout_dirty(true);
}

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

void SidebarSection::clear()
{
  for (size_t i = 0; i < _entries.size(); i++)
    delete _entries[i];
  _entries.clear();

  set_layout_dirty(true);
}

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

bool SidebarSection::select(int index)
{
  const bool index_is_valid = index >= 0 && index < (int)_entries.size();

  if (index_is_valid)
    set_selected(_entries[index]);
  else
    set_selected(NULL);

  return index_is_valid;
}

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

bool SidebarSection::select(const std::string& title)
{
  const int index = find_entry(title);
  return select(index);
}

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

//void SidebarSection::set_selection_color(const Color* color)
//{
//  _selection_color = color;
//}

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

void draw_header_text(cairo_t* cr, Rect &bounds, const std::string& text, bool active)
{
  // Draw heading twice. Once like a white shadow and once normal.
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_move_to(cr, bounds.left(), bounds.top() + 1);
  cairo_show_text(cr, text.c_str());
  cairo_stroke(cr);

  if (active)
    cairo_set_source_rgb(cr, 0x04 / 255.0, 0x7c / 255.0, 0xf2 / 255.0);
  else
    cairo_set_source_rgb(cr, 0x61 / 255.0, 0x70 / 255.0, 0x80 / 255.0);
  cairo_move_to(cr, bounds.left(), bounds.top());
  cairo_show_text(cr, text.c_str());
  cairo_stroke(cr);
}

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

void SidebarSection::repaint(cairo_t *cr, int areax, int areay, int areaw, int areah)
{
  layout(cr);

  double width= get_width();

  Rect bounds(SECTION_SIDE_SPACING, SECTION_TOP_SPACING + SIDEBAR_TITLE_FONT_SIZE,
    width - SECTION_SIDE_SPACING, SECTION_HEADER_HEIGHT);

  cairo_select_font_face(cr, SIDEBAR_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
  cairo_set_font_size(cr, SIDEBAR_TITLE_FONT_SIZE);
  draw_header_text(cr, bounds, _title, false);

  if (_refresh_icon != NULL)
  {
    int image_width = cairo_image_surface_get_width(_refresh_icon);
    int image_height = cairo_image_surface_get_height(_refresh_icon);
    double image_left = bounds.size.width - image_width + 0.5;
    double image_top = SECTION_TOP_SPACING + 0.5;
    if (_refresh_hot || _refresh_down)
    {
      // Draw a bezel shape under the icon.
      double radius = 3;
      cairo_new_sub_path(cr);
      cairo_arc(cr, image_left + image_width - radius, image_top + radius, radius, -M_PI / 2, 0);
      cairo_arc(cr, image_left + image_width - radius, image_top + image_height - radius, radius, 0, M_PI / 2);
      cairo_arc(cr, image_left + radius, image_top + image_height - radius, radius, M_PI / 2, M_PI);
      cairo_arc(cr, image_left + radius, image_top + radius, radius, M_PI, 3 * M_PI / 2);
      cairo_close_path(cr);

      cairo_set_line_width(cr, 1);
      if (_refresh_down)
      {
        cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
        cairo_fill_preserve(cr);
        cairo_set_source_rgba(cr, 0, 0, 0, 0.15);
      }
      else
      {
        cairo_set_source_rgba(cr, 0, 0, 0, 0.05);
        cairo_fill_preserve(cr);
        cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
      }
      cairo_stroke(cr);
    }

    cairo_set_source_surface(cr, _refresh_icon, image_left + 0.5, image_top + 0.5);
    cairo_paint(cr);

    width -= image_width + SECTION_HEADER_REFRESH_SPACING;
  }

  if (_expand_text_visible)
  {
    cairo_select_font_face(cr, SIDEBAR_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, SIDEBAR_TITLE_FONT_SIZE);

    std::string expand_text = _expanded ? _("Hide") : _("Show");
    Rect text_bounds = bounds;
    text_bounds.pos.x = width - _expand_text_width - SECTION_SIDE_SPACING;
    text_bounds.size.width = _expand_text_width;
    draw_header_text(cr, text_bounds, expand_text, _expand_text_active);
  }

  if (_expanded)
  {
    bounds.pos.x += SECTION_ENTRY_INDENT;
    bounds.size.width -= SECTION_ENTRY_INDENT;
    bounds.pos.y += SECTION_HEADER_SPACING;
    bounds.size.height = SECTION_ENTRY_HEIGHT;
    const Color& selection_color = _owner->selection_color();
    for (std::vector<SidebarEntry*>::const_iterator iterator= _entries.begin(); iterator != _entries.end(); iterator++)
    {
      (*iterator)->paint(cr, bounds, *iterator == _hot_entry, (*iterator == _selected_entry), selection_color);
      bounds.pos.y += SECTION_ENTRY_HEIGHT + SECTION_ENTRY_SPACING;
    }
  }
}

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

void SidebarSection::mouse_leave()
{
  if (_hot_entry != NULL || _expand_text_visible || _expand_text_active || _refresh_hot || _refresh_down)
  {
    _hot_entry= NULL;
    _expand_text_visible = false;
    _expand_text_active = false;
    _refresh_down = false;
    _refresh_hot = false;
    set_needs_repaint();
  }
}

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

void SidebarSection::mouse_move(int x, int y)
{
  bool need_refresh = false;
  
  if (y < SECTION_TOP_SPACING + SECTION_HEADER_HEIGHT)
  {
    // Header area.
    if (_expandable && !_expand_text_visible)
    {
      need_refresh = true;
      _expand_text_visible = true;
    }
    if (_hot_entry != NULL)
    {
      need_refresh = true;
      _hot_entry= NULL;
    }

    // Check refresh button.
    if (_refresh_icon != NULL)
    {
      int width = get_width() - SECTION_SIDE_SPACING;
      bool isHot = (x >= (width - cairo_image_surface_get_width(_refresh_icon)) && x < width);
      if (isHot != _refresh_hot)
      {
        _refresh_hot = isHot;
        need_refresh = true;
      }
    }
  }
  else
  {
    if (_expand_text_visible || _refresh_hot)
    {
      _expand_text_visible = false;
      _refresh_hot = false;
      need_refresh = true;
    }

    SidebarEntry* entry = entry_from_point(x, y);
    if (entry != _hot_entry)
    {
      if (_hot_entry != NULL || entry->is_link())
        need_refresh = true;

      if (entry != NULL && entry->is_link())
        _hot_entry = entry; // Only links can appear as hot entries.
      else
        _hot_entry = NULL;
    }
  }

  if (need_refresh)
    set_needs_repaint();
}

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

void SidebarSection::mouse_down(int button, int x, int y)
{
  if (button == MouseButtonLeft)
  {
    if (_refresh_hot)
    {
      _refresh_down = true;
      set_needs_repaint();
    }
    else
      if (_expand_text_visible)
      {
        _expand_text_active = true;
        set_needs_repaint();
      }
      else
      {
        SidebarEntry* entry = entry_from_point(x, y);
        if (entry && !entry->is_link())
          set_selected(entry);
      }
  }
}

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

void SidebarSection::mouse_click(int button, int x, int y)
{
  switch (button)
  {
  case MouseButtonLeft:
    {
      bool handled = false;

      if (_expand_text_active)
      {
        handled = true;
        toggle_expand();
        _expand_text_active = false;
        set_needs_repaint();
      }
      else
        if (_refresh_down)
        {
          handled = true;
          _owner->refresh_clicked(this);
        }

      if (!handled)
      {
        SidebarEntry* entry = entry_from_point(x, y);
        if (entry && ((entry == _hot_entry) || (entry == _selected_entry)))
          (*_owner->on_section_command())(entry->command());
      }
    }
    break;

  case MouseButtonRight:
    /*
    if (_context_menu != NULL && _selected_link != NULL)
      _context_menu->popup_at(this, x + 5, y + 5);
    */
    break;
  }
}

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

void SidebarSection::mouse_up(int button, int x, int y)
{
  switch (button)
  {
  case MouseButtonLeft:
    if (_refresh_down)
    {
      _refresh_down = false;
      set_needs_repaint();
    }
    break;
  }
}

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

void SidebarSection::get_layout_size(int* w, int* h)
{
  layout(NULL);

  *w= _layout_width;
  *h= _layout_height;
}

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

void SidebarSection::clear_selection()
{
  const bool had_selection = _selected_entry != 0;

  set_selected(0);

  if (had_selection)
    set_needs_repaint();
}

//----------------- AdvancedSidebar ----------------------------------------------------------------

// implicitly initialize the adv. sidebar
bool AdvancedSidebar::__init = init_factory_method();

bool AdvancedSidebar::init_factory_method()
{
  log_debug("Initializing AdvancedSidebar");
  register_factory(&create_instance);
  return true;
}


AdvancedSidebar::AdvancedSidebar()
  : _schema_tree_heading(NULL), 
    _schema_tree(TreeNoColumns | TreeNoBorder | TreeSidebar | TreeNoHeader), 
    _schema_search_box(true),
    _schema_search_text(mforms::SmallSearchEntry),
    _schema_search_warning(_("Showing loaded schemas only")),
    _schema_model(0),
    _base_model(0),
    _is_model_owner(false),
    _schema_box(false)
{
  _schema_search_warning.show(false);
  _schema_search_warning.set_style(mforms::SmallHelpTextStyle);
  _schema_search_warning.set_text_align(mforms::MiddleCenter);
  setup_schema_tree();
}

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

AdvancedSidebar::~AdvancedSidebar()
{
  for (size_t i = 0; i < _sections.size(); i++)
    delete _sections[i];

  _schema_tree.set_model(0);
  if (_is_model_owner)
    delete _schema_model;
}

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

/**
 * Factory method for this control to be used by mforms to create the instance.
 */
mforms::TaskSidebar* AdvancedSidebar::create_instance()
{
  return new AdvancedSidebar();
}

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

/**
 * Find a section with the given title and return its index or -1 if there is none.
 */
int AdvancedSidebar::find_section(const std::string& title)
{
  for (size_t i = 0; i < _sections.size(); i++)
  {
    if (_sections[i]->title() == title)
      return i;
  }

  return -1;
}

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

/**
 * Do all the necessary setup for the schema tree (colors, columns and other visual stuff).
 */
void AdvancedSidebar::setup_schema_tree()
{
#if defined(_WIN32) || defined(__APPLE__)
  _schema_tree.add_column(IconStringGRTColumnType, 0, _("Schema"));
#else
  _schema_tree.add_column(IconStringGRTColumnType, 4242, _("Schema"));
#endif
  _schema_tree.set_allow_multi_selection(true);
#if defined(_WIN32) || defined(__APPLE__)
  _schema_tree.set_back_color("#d9e2ef");
  set_back_color("#d9e2ef");
#endif
  _schema_tree.set_context_menu(&_tree_context_menu);
  scoped_connect(_tree_context_menu.signal_will_show(),boost::bind(&AdvancedSidebar::on_show_menu, this));
  _tree_context_menu.set_handler(boost::bind(&AdvancedSidebar::handle_menu_command, this, _1));

#if defined(_WIN32) || defined(__APPLE__)
  _schema_search_box.set_back_color("#d9e2ef");
#endif
  _schema_search_box.add(&_schema_search_text, true, true);
  _schema_search_box.set_padding(8, 0, 8, 2);
  scoped_connect(_schema_search_text.signal_changed(), boost::bind(&AdvancedSidebar::on_search_text_changed, this));
  // Add the tree itself and its section caption to the container.
  _schema_tree_heading = new SidebarSection(this, _("SCHEMAS"), false, true);
  scoped_connect(_schema_tree_heading->expanded_changed(),boost::bind(&AdvancedSidebar::on_expand_changed, this, _1));
  _sections.push_back(_schema_tree_heading);
#if defined(_WIN32) || defined(__APPLE__)
  _schema_box.set_back_color("#d9e2ef");
#endif
  _schema_box.add(_schema_tree_heading, false, true);
  _schema_box.add(&_schema_search_box, false, true);
  _schema_box.add(&_schema_tree, true, true);
//  _schema_box.set_font("Trebuchet MS bold 9");
  _schema_box.add(&_schema_search_warning, false, true);

  _schema_box.show(false);
  add_end(&_schema_box, true, true);
}

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

void AdvancedSidebar::on_search_text_changed()
{
  std::string filter = _schema_search_text.get_string_value();

  // Updates the current schema model to filtered/base
  // based on the content of filter
  if ( filter.length() > 0 )
  {
    _base_model->filter_data(LiveSchemaTree::Any, _schema_search_text.get_string_value(), *_filtered_schema_model);
    set_schema_model(_filtered_schema_model);
    _schema_search_warning.show(true);
  }
  else
  {
    set_schema_model(_base_model);
    _schema_search_warning.show(false);
  }

  // Raises a signal indicating the filter has changed
  _search_box_changed_signal(filter);

}

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

void AdvancedSidebar::on_show_menu()
{
  _tree_context_menu.clear();

  std::vector<bec::NodeId> nodes;
  _schema_tree.get_selection(nodes);
  bec::MenuItemList items = _schema_model->get_popup_items_for_nodes(nodes);
  _tree_context_menu.add_items_from_list(items);
}

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

void AdvancedSidebar::on_expand_changed(SidebarSection* section)
{
  _schema_tree.show(section->expanded());
}

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

void AdvancedSidebar::handle_menu_command(const std::string& command)
{
  std::vector<bec::NodeId> nodes;
  _schema_tree.get_selection(nodes);
  _schema_model->activate_popup_item_for_nodes(command, nodes);
}

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

void AdvancedSidebar::refresh_clicked(SidebarSection* section)
{
  if (section == _schema_tree_heading)
  {
    std::vector<bec::NodeId> nodes;
    _schema_model->activate_popup_item_for_nodes("refresh", nodes);
  }
}

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

void AdvancedSidebar::refresh_model()
{
  _schema_model->refresh();
}

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

void AdvancedSidebar::set_schema_model(LiveSchemaTree* model)
{
  _schema_tree.set_model(model);
  _changed_conn.disconnect();
  if (model)
      _changed_conn = model->tree_changed_signal()->connect(boost::bind(&GRTTreeView::row_count_changed, &_schema_tree, _1, _2));
  
  if (_is_model_owner)
  {
    delete _schema_model;
    _is_model_owner = false;
  }
  _schema_model = model;

  // Sets the given model as the base model if none assigned already
  if(!_base_model)
    _base_model = model;

  _schema_box.show(_schema_model != NULL);
  
  _activate_conn.disconnect();
  if (_schema_model)
      _activate_conn = _schema_tree.signal_row_activate()->connect(boost::bind(&wb::LiveSchemaTree::activate_node, _schema_model, _1));
}

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

int AdvancedSidebar::add_section(const string& title)
{
  int result = find_section(title);
  if (result > -1)
    return result;

  SidebarSection* box = new SidebarSection(this, title, true, false);
  box->set_back_color("#d9e2ef");
  _sections.push_back(box);
  add(box, false, true);

  return _sections.size() - 1;
}

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

void AdvancedSidebar::remove_section(const std::string& section)
{
  int index = find_section(section);
  if (index < 0)
    return;

  remove(_sections[index]);
  delete _sections[index];
  _sections.erase(_sections.begin() + index);
}

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

int AdvancedSidebar::add_section_entry(const std::string& section, const std::string& title,
  const std::string& icon, const std::string& command, bool as_link)
{
  int index = find_section(section);
  if (index < 0)
    return - 1;

  return _sections[index]->add_entry(title, icon, command, as_link);
}

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

void AdvancedSidebar::remove_section_entry(const std::string& section, const std::string& entry)
{
  int index = find_section(section);
  if (index < 0)
    return;

  _sections[index]->remove_entry(entry);
}

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

void AdvancedSidebar::set_collapse_states(const std::string& data)
{
  std::vector<std::string> collapsed_sections = base::split(data, ",");
  for (std::vector<std::string>::const_iterator iter = collapsed_sections.begin();
       iter != collapsed_sections.end(); ++iter)
  {
    int section;
    int state;
    const char *ptr = strrchr(iter->c_str(), '=');
    if (ptr)
    {
      section = find_section(iter->substr(0, ptr-iter->c_str()));
      if (section < 0)
        continue;
      
      state = atoi(ptr+1);
    }
    else
      continue;
    
    if ((state!=0) == _sections[section]->expanded())
      _sections[section]->toggle_expand();
  }
}

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

std::string AdvancedSidebar::get_collapse_states()
{
  std::string states;
  for (int i = 0; i < (int)_sections.size(); i++)
  {
    if (i > 0)
      states.append(",");
    states.append(base::strfmt("%s=%i", _sections[i]->title().c_str(), !_sections[i]->expanded()));
  }
  return states;
}

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

void AdvancedSidebar::clear_sections()
{
  SidebarSection* schema_heading = _sections[0]; // A special section. We wanna keep it.
  for (size_t i = 1; i < _sections.size(); i++)
    delete _sections[i];
  _sections.clear();
  _sections.push_back(schema_heading);

  relayout();
}

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

void AdvancedSidebar::clear_section(const std::string& section)
{
  int index = find_section(section);
  if (index > -1)
  {
    delete _sections[index];
    _sections.erase(_sections.begin() + index);

    relayout();
  }
}

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

void AdvancedSidebar::set_selection_color(const std::string& color)
{
  _selection_color = Color::parse(color);
  //for (int i = 0; i < (int) _sections.size(); i++)
  //  _sections[i]->set_selection_color(_selection_color);
  set_needs_repaint();
}

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

void AdvancedSidebar::set_selection_color(const mforms::SystemColor color)
{
  mforms::App* app = mforms::App::get();
  if (app)
    set_selection_color(app->get_system_color(color).to_html());
}

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

/**
 * Expands the schema node with the given index. A schema node is a top level node.
 */
void AdvancedSidebar::expand_schema(int schema_index)
{
  _schema_tree.set_expanded(schema_index, true);
}

bool AdvancedSidebar::expand_node(const bec::NodeId &node, int depth_limit)
{
  bool ret_val = false;

  if (_schema_model->is_expanded(node))
  {
    
    int child_count = _schema_model->count_children(node);

    if (node.depth() < depth_limit)
    {
      bec::NodeId child_node(node);
      child_node.append(0);

      for(int index = 0; index < child_count; index++)
      {
        child_node[child_node.depth()-1] = index;
        if (expand_node(child_node, depth_limit))
          ret_val = true;
      }
    }

    if (!ret_val)
    {
      _schema_tree.set_expanded(node, true);
      ret_val = true;
    }
  }

  return ret_val;
}

void AdvancedSidebar::restore_expanded_nodes()
{
  _schema_tree.enable_asynch_loading(false);

  for(int index = 0; index < _schema_model->count(); index++)
  {
    bec::NodeId node(index);

    if (_schema_model->is_expanded(node))
    {
      //_schema_tree.set_expanded(node, true);
      bec::NodeId collection_node(node);
      
      // Expands table nodes
      collection_node.append(0);
      bool tables_expanded = expand_node(collection_node, 4);

      // Expands view nodes
      collection_node[1] = 1;
      bool views_expanded = expand_node(collection_node, 3);

      // Expands routine nodes
      collection_node[1] = 2;
      bool routines_expanded = expand_node(collection_node, 2);

      if (!tables_expanded && !views_expanded && !routines_expanded)
        _schema_tree.set_expanded(node, true);
    }
  }

  _schema_tree.enable_asynch_loading(true);
}
//--------------------------------------------------------------------------------------------------

int AdvancedSidebar::select_entry(const std::string& section_name, const std::string& entry_title)
{
  int was_selected = 0;
  const int index = find_section(section_name);

  if (index >= 0)
  {
    SidebarSection *section = _sections[index];
    if (section->select(entry_title))
      was_selected = 1;
  }

  return was_selected;
}

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

void AdvancedSidebar::clear_selection()
{
  for (size_t i = 1; i < _sections.size(); i++)
    _sections[i]->clear_selection();
}
