/* 
 * Copyright (c) 2011, 2013, 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 "base/notifications.h"

#include "mforms/imagebox.h"
#include "mforms/button.h"
#include "mforms/utilities.h"

using namespace base;

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& name, const string& title, const string& icon, TaskEntryType type)
{
  _name = name;
  _title = title;
  if (!icon.empty())
    _icon = Utilities::load_icon(icon);
  else
    _icon = NULL;
  _type = type;
  _enabled = true;
}

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

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


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

void SidebarEntry::set_title(const std::string &title)
{
  _title = title;
}

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

void SidebarEntry::set_icon(const std::string &icon)
{
  if (_icon)
    cairo_surface_destroy(_icon);
  _icon = Utilities::load_icon(icon);
}

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

void SidebarEntry::set_enabled(bool flag)
{
  _enabled = flag;
}

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


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 if (_enabled)
    cairo_set_source_rgb(cr, 0, 0, 0);
  else
    cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
  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::Button::Button(const std::string &icon_name, const std::string &alt_icon_name)
: icon(NULL), alt_icon(NULL), hot(false), down(false), state(false)
{
  move(0, 0);

  if (!icon_name.empty())
    icon = Utilities::load_icon(icon_name);
  if (!alt_icon_name.empty())
    alt_icon = Utilities::load_icon(alt_icon_name);

  width = 0;
  height = 0;
  if (icon)
  {
    width = cairo_image_surface_get_width(icon);
    height = cairo_image_surface_get_height(icon);
  }
  else if (alt_icon)
  {
    width = cairo_image_surface_get_width(alt_icon);
    height = cairo_image_surface_get_height(alt_icon);
  }
  bounds_width = width;
  bounds_height = height;
}


SidebarSection::Button::~Button()
{
  if (icon)
    cairo_surface_destroy(icon);
  if (alt_icon)
    cairo_surface_destroy(alt_icon);
}


void SidebarSection::Button::draw(cairo_t *cr)
{
  cairo_surface_t *image = (state && alt_icon != NULL) ? alt_icon : icon;

  if (image)
  {
    double image_left = x + 0.5;
    double image_top = y + 0.5;
    if (hot || down)
    {
      // Draw a bezel shape under the icon.
      double radius = 3;
      cairo_new_sub_path(cr);
      cairo_arc(cr, image_left + bounds_width - radius, image_top + radius, radius, -M_PI / 2, 0);
      cairo_arc(cr, image_left + bounds_width - radius, image_top + bounds_height - radius, radius, 0, M_PI / 2);
      cairo_arc(cr, image_left + radius, image_top + bounds_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 (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);
    }

    image_left += (bounds_width - width) / 2;
    image_top += (bounds_height - height) / 2;
    cairo_set_source_surface(cr, image, image_left + 0.5, image_top + 0.5);
    cairo_paint(cr);
  }
}


bool SidebarSection::Button::check_hit(int x, int y)
{
  return (x >= this->x && x < this->x + bounds_width && y >= this->y && y < this->y + bounds_height);
}


void SidebarSection::Button::move(int x, int y)
{
  this->x = x;
  this->y = y;
}


SidebarSection::SidebarSection(SimpleSidebar* owner, const std::string& name, const std::string& title, mforms::TaskSectionFlags flags)
  : DrawBox()
{
  _owner = owner;
  _name = name;
  _title = title;
  _selected_entry = NULL;
  _hot_entry = NULL;

  _layout_width = 0;
  _layout_height = 0;
  _expanded = true;
  _expandable = (flags & mforms::TaskSectionCollapsible) != 0;
  _expand_text_visible = false;
  _expand_text_width = 0;
  _expand_text_active = false;

  _refresh_button = NULL;
  _toggle_mode_button = NULL;
  _config_button = NULL;

  _layout_surface= NULL;
  _layout_context= NULL;
  _last_width = 0;

  if (flags & mforms::TaskSectionRefreshable)
  {
#ifdef __APPLE__
    _refresh_button = new Button("refresh_sidebar_mac.png", "busy_sidebar_mac.png");
#else
    _refresh_button = new Button("refresh_sidebar.png", "busy_sidebar.png");
#endif
  }
  else
  {
#ifdef __APPLE__
    _refresh_button = new Button("", "busy_sidebar_mac.png");
#else
    _refresh_button = new Button("", "busy_sidebar.png");
#endif
  }

  if (flags & mforms::TaskSectionToggleModeButton)
  {
    _toggle_mode_button = new Button("wb-sidebar-collapse.png", "wb-sidebar-expand.png");
    if (flags & TaskSectionToggleModeButtonPreSelected)
      _toggle_mode_button->state = true;
  }

  if (flags & mforms::TaskSectionShowConfigButton)
    _config_button = new Button("wb_perform_config.png", "");
}

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

SidebarSection::~SidebarSection()
{
  clear();
  delete _refresh_button;
  delete _toggle_mode_button;
  delete _config_button;

  if (_layout_surface != NULL)
    cairo_surface_destroy(_layout_surface);

  if (_layout_context != NULL)
    cairo_destroy(_layout_context);
}

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

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

  if (_selected_entry != entry)
  {
    _selected_entry = entry;
    set_needs_repaint();
  }
}

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

/**
 * Creates a cairo context on a small image surface, to be used for layouting.
 */
void SidebarSection::create_context_for_layout()
{
  if (_layout_surface == NULL)
    _layout_surface= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, get_width(), get_height());
  if (_layout_context == NULL)
    _layout_context= cairo_create(_layout_surface);
}

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

#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_ICON_SPACING    4 // Horizontal distance between buttons and/or text.

void SidebarSection::layout(cairo_t* cr)
{
  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.
  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);

  double width = get_width();

  Rect bounds(SECTION_SIDE_SPACING, SECTION_TOP_SPACING + SIDEBAR_TITLE_FONT_SIZE,
              width - SECTION_SIDE_SPACING, SECTION_HEADER_HEIGHT);
  
  if (_config_button != NULL)
  {
    cairo_select_font_face(cr, SIDEBAR_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_text_extents(cr, _title.c_str(), &extents);
    int title_width = (int)ceil(extents.x_advance);

    _config_button->move((int)bounds.left() + title_width + SECTION_ICON_SPACING, SECTION_TOP_SPACING / 2);
    _config_button->bounds_width = SECTION_HEADER_HEIGHT + SECTION_TOP_SPACING;
    _config_button->bounds_height = SECTION_HEADER_HEIGHT + SECTION_TOP_SPACING;
  }

  if (_refresh_button)
  {
    _refresh_button->move((int)bounds.size.width - _refresh_button->bounds_width - SECTION_SIDE_SPACING, SECTION_TOP_SPACING);
    _refresh_button->bounds_width = SECTION_HEADER_HEIGHT;
    _refresh_button->bounds_height = SECTION_HEADER_HEIGHT;
  }

  if (_toggle_mode_button)
  {
    if (_refresh_button)
      _toggle_mode_button->move(_refresh_button->x -_toggle_mode_button->bounds_width - SECTION_ICON_SPACING, SECTION_TOP_SPACING);
    else
      _toggle_mode_button->move((int)bounds.size.width - _toggle_mode_button->bounds_width - SECTION_SIDE_SPACING, SECTION_TOP_SPACING);
    _toggle_mode_button->bounds_width = SECTION_HEADER_HEIGHT;
    _toggle_mode_button->bounds_height = SECTION_HEADER_HEIGHT;
  }
}

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

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& name)
{
  for (size_t i = 0; i < _entries.size(); i++)
  {
    if (_entries[i]->name() == name)
      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& name, const std::string& title, const std::string& icon, TaskEntryType type)
{
  int result = find_entry(name);
  if (result > -1)
    return result;

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

  return _entries.size() -1 ;
}

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

void SidebarSection::set_entry_text(int index, const std::string& title)
{
  if (index >= 0 && index < (int)_entries.size())
    _entries[index]->set_title(title);
}

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

void SidebarSection::set_entry_icon(int index, const std::string& icon)
{
  if (index >= 0 && index < (int)_entries.size())
    _entries[index]->set_icon(icon);
}

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

void SidebarSection::set_entry_enabled(int index, bool flag)
{
  if (index >= 0 && index < (int)_entries.size())
    _entries[index]->set_enabled(flag);
}

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

void SidebarSection::mark_busy(bool busy)
{
  if (_refresh_button)
    _refresh_button->state = busy;
  set_needs_repaint();
}

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

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(const std::string& name)
{
  const int index = find_entry(name);
  if (index >= 0 && index < (int)_entries.size())
  {
    set_selected(_entries[index]);
    return false;
  }
  else
  {
    set_selected(NULL);
    return false;
  }
}

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

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)
{
  double width = get_width();
  if (_last_width != width)
  {
    _last_width = width;
    layout(cr);
  }

  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 (_config_button)
    _config_button->draw(cr);

  if (_refresh_button)
    _refresh_button->draw(cr);

  if (_toggle_mode_button)
    _toggle_mode_button->draw(cr);

  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 ||
      (_config_button && _config_button->hot) ||
      (_refresh_button && _refresh_button->hot) ||
      (_toggle_mode_button && _toggle_mode_button->hot))
  {
    _hot_entry= NULL;
    _expand_text_visible = false;
    _expand_text_active = false;

    if (_config_button)
    {
      _config_button->down = false;
      _config_button->hot = false;
    }

    if (_refresh_button)
    {
      _refresh_button->down = false;
      _refresh_button->hot = false;
    }

    if (_toggle_mode_button)
    {
      _toggle_mode_button->down = false;
      _toggle_mode_button->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 buttons.
    if (_config_button)
    {
      bool isHot = _config_button->check_hit(x, y);
      if (isHot != _config_button->hot)
      {
        _config_button->hot = isHot;
        need_refresh = true;
      }
    }

    if (_refresh_button)
    {
      bool isHot = _refresh_button->check_hit(x, y);
      if (isHot != _refresh_button->hot)
      {
        _refresh_button->hot = isHot;
        need_refresh = true;
      }
    }

    if (_toggle_mode_button)
    {
      bool isHot = _toggle_mode_button->check_hit(x, y);
      if (isHot != _toggle_mode_button->hot)
      {
        _toggle_mode_button->hot = isHot;
        need_refresh = true;
      }
    }
  }
  else
  {
    if (_expand_text_visible ||
        (_config_button && _config_button->hot) ||
        (_refresh_button && _refresh_button->hot) ||
        (_toggle_mode_button && _toggle_mode_button->hot))
    {
      _expand_text_visible = false;
      if (_config_button)
        _config_button->hot = false;
      if (_refresh_button)
        _refresh_button->hot = false;
      if (_toggle_mode_button)
      _toggle_mode_button->hot = false;
      need_refresh = true;
    }

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

      if (entry != NULL && entry->type() == mforms::TaskEntryLink)
        _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 (_config_button && _config_button->hot)
    {
      _config_button->down = true;
      set_needs_repaint();
    }
    else if (_refresh_button && _refresh_button->hot)
    {
      _refresh_button->down = true;
      set_needs_repaint();
    }
    else if (_toggle_mode_button && _toggle_mode_button->hot)
    {
      _toggle_mode_button->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->enabled() && entry->type() == mforms::TaskEntrySelectableItem)
        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 (_config_button && _config_button->down)
      {
        handled = true;
        (*_owner->on_section_command())("configure");
      }
      else if (_refresh_button && _refresh_button->down && _refresh_button->icon)
      {
        handled = true;
        if (dynamic_cast<AdvancedSidebar*>(_owner))
          dynamic_cast<AdvancedSidebar*>(_owner)->tool_action_clicked("refresh");
      }
      else if (_toggle_mode_button && _toggle_mode_button->down)
      {
        handled = true;
        if (dynamic_cast<AdvancedSidebar*>(_owner))
        {
          if (_toggle_mode_button->state)
            dynamic_cast<AdvancedSidebar*>(_owner)->tool_action_clicked("toggle_view_expand");
          else
            dynamic_cast<AdvancedSidebar*>(_owner)->tool_action_clicked("toggle_view_collapse");
          _toggle_mode_button->state = !_toggle_mode_button->state;
          set_needs_repaint();
        }
      }

      if (!handled)
      {
        SidebarEntry* entry = entry_from_point(x, y);
        if (entry && entry->enabled() && ((entry == _hot_entry) || (entry == _selected_entry)))
          (*_owner->on_section_command())(entry->name());
      }
    }
    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 (_config_button && _config_button->down)
    {
      _config_button->down = false;
      set_needs_repaint();
    }

    if (_refresh_button && _refresh_button->down)
    {
      _refresh_button->down = false;
      set_needs_repaint();
    }

      if (_toggle_mode_button && _toggle_mode_button->down)
    {
      _toggle_mode_button->down = false;
      set_needs_repaint();
    }
    break;
  }
}

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

void SidebarSection::get_layout_size(int* w, int* h)
{
  if (is_layout_dirty())
  {
    create_context_for_layout();
    layout(_layout_context);
  }

  *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();
}

//----------------- SimpleSidebar ----------------------------------------------------------------

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

bool SimpleSidebar::init_factory_method()
{
 // log_debug3("Initializing SimpleSidebar factory method\n");
  register_factory("Simple", &create_instance);
  return true;
}

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

SimpleSidebar::SimpleSidebar()
{
  switch (Color::get_active_scheme())
  {
  case base::ColorSchemeStandardWin7:
  case base::ColorSchemeStandardWin8:
  case base::ColorSchemeStandardWin8Alternate:
    set_back_color(base::Color::get_application_color_as_string(AppColorPanelContentArea, false));
    break;
  default:
    set_back_color("#d9e2ef");
    break;
  }
}

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

mforms::TaskSidebar* SimpleSidebar::create_instance()
{
  return new SimpleSidebar();
}

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

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


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

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

  return -1;
}

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

int SimpleSidebar::add_section(const std::string &name, const string& title, mforms::TaskSectionFlags flags)
{
  int result = find_section(title);
  if (result > -1)
    return result;

  SidebarSection* box = new SidebarSection(this, name, title, flags);
  box->set_back_color(get_back_color());
  _sections.push_back(box);
  add(box, false, true);

  return _sections.size() - 1;
}

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

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

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

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

int SimpleSidebar::add_section_entry(const std::string& section_name, const std::string &name, const std::string& title,
                                     const std::string& icon, TaskEntryType type)
{
  int index = find_section(section_name);
  if (index < 0)
    return - 1;

  return _sections[index]->add_entry(name, title, icon, type);
}

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

void SimpleSidebar::set_section_entry_text(const std::string& section_entry, const std::string& title)
{
  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    int entry_index = (*section)->find_entry(section_entry);
    if (entry_index >= 0 && entry_index < (*section)->entry_count())
    {
      (*section)->set_entry_text(entry_index, title);
      break;
    }
  }
}

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

void SimpleSidebar::set_section_entry_icon(const std::string& section_entry, const std::string& icon)
{
  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    int entry_index = (*section)->find_entry(section_entry);
    if (entry_index >= 0 && entry_index < (*section)->entry_count())
    {
      (*section)->set_entry_icon(entry_index, icon);
      break;
    }
  }
}

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

void SimpleSidebar::set_section_entry_enabled(const std::string& section_entry, bool enabled)
{
  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    int entry_index = (*section)->find_entry(section_entry);
    if (entry_index >= 0 && entry_index < (*section)->entry_count())
    {
      (*section)->set_entry_enabled(entry_index, enabled);
      break;
    }
  }
}


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

void SimpleSidebar::mark_section_busy(const std::string& section, bool busy)
{
  int index = find_section(section);
  if (index < 0)
    return;

  _sections[index]->mark_busy(busy);
}

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

void SimpleSidebar::remove_section_entry(const std::string& entry_name)
{
  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    int entry_index = (*section)->find_entry(entry_name);
    if (entry_index >= 0 && entry_index < (*section)->entry_count())
    {
      (*section)->remove_entry(entry_name);
      break;
    }
  }
}

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

void SimpleSidebar::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 SimpleSidebar::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]->name().c_str(), !_sections[i]->expanded()));
  }
  return states;
}

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

void SimpleSidebar::clear_sections()
{
  for (size_t i = 0; i < _sections.size(); i++)
    delete _sections[i];
  _sections.clear();

  relayout();
}

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

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

    relayout();
  }
}

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

void SimpleSidebar::set_selection_color(const std::string& color)
{
  _selection_color = Color::parse(color);
  set_needs_repaint();
}

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

void SimpleSidebar::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());
}

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

int SimpleSidebar::select_entry(const std::string& entry_name)
{
  int was_selected = 0;

  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    int entry_index = (*section)->find_entry(entry_name);
    if (entry_index >= 0 && entry_index < (*section)->entry_count())
    {
      if ((*section)->select(entry_name))
        was_selected = 1;
      break;
    }
  }

  return was_selected;
}

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

std::string SimpleSidebar::selected_entry()
{
  for (std::vector<SidebarSection*>::const_iterator section = _sections.begin(); section != _sections.end(); ++section)
  {
    SidebarEntry *entry = (*section)->selected();
    if (entry)
      return entry->name();
  }
  return "";
}

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

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

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

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

bool AdvancedSidebar::init_factory_method()
{
//  log_debug3("Initializing AdvancedSidebar factory method\n");
  register_factory("SchemaTree", &create_instance);
  return true;
}

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

AdvancedSidebar::AdvancedSidebar()
: _new_schema_tree(TreeNoColumns | TreeNoBorder | TreeSidebar | TreeNoHeader),
_filtered_schema_tree(TreeNoColumns | TreeNoBorder | TreeSidebar | TreeNoHeader),
_schema_search_box(true),
_schema_search_text(mforms::SmallSearchEntry),
_remote_search_enabled(false),
_schema_search_warning(_("Showing loaded schemas only")),
_schema_model(0),
_base_model(0),
_is_model_owner(false),
_schema_box(false)
{
  _remote_search.set_text(_("Search on Server..."));

  _schema_search_warning.set_style(mforms::SmallHelpTextStyle);
  _schema_search_warning.set_text_align(mforms::MiddleCenter);

  NotificationCenter::get()->add_observer(this, "GNColorsChanged");

  setup_schema_tree();
}

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

AdvancedSidebar::~AdvancedSidebar()
{
  NotificationCenter::get()->remove_observer(this);

  if (_is_model_owner)
    delete _schema_model;
}

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

void AdvancedSidebar::mark_section_busy(const std::string& section, bool busy)
{
  if (section.empty())
  {
//    _schema_tree_heading->mark_busy(busy);
    return;
  }
  SimpleSidebar::mark_section_busy(section, busy);
}

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

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

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

/**
 * Do all the necessary setup for the schema tree (colors, columns and other visual stuff).
 */
void AdvancedSidebar::setup_schema_tree()
{
  std::string background_color = "#d9e2ef";
  std::string toolbar_background_color = "#d9e2ef";
  switch (Color::get_active_scheme())
  {
  case base::ColorSchemeStandardWin7:
  case base::ColorSchemeStandardWin8:
  case base::ColorSchemeStandardWin8Alternate:
    background_color = base::Color::get_application_color_as_string(AppColorPanelContentArea, false);
    toolbar_background_color = base::Color::get_application_color_as_string(AppColorPanelToolbar, false);
    break;

    default: // Set already.
      break;
  }

  _new_schema_tree.add_column(mforms::IconStringColumnType, _("Schema"), 100, false, true);
  _new_schema_tree.set_selection_mode(mforms::TreeSelectMultiple);
  _new_schema_tree.set_back_color(background_color);
  scoped_connect(_new_schema_tree.signal_changed(), boost::bind(&AdvancedSidebar::on_tree_node_selected, this));

  _filtered_schema_tree.add_column(mforms::IconStringColumnType, _("Schema"), 100, false, true);
  _filtered_schema_tree.set_selection_mode(mforms::TreeSelectMultiple);
  _filtered_schema_tree.set_back_color(background_color);
  scoped_connect(_filtered_schema_tree.signal_changed(), boost::bind(&AdvancedSidebar::on_tree_node_selected, this));

  _new_schema_tree.set_context_menu(&_tree_context_menu);
  _new_schema_tree.end_columns();

  _filtered_schema_tree.set_context_menu(&_tree_context_menu);
  _filtered_schema_tree.end_columns();

  scoped_connect(_tree_context_menu.signal_will_show(),boost::bind(&AdvancedSidebar::on_show_menu, this, _1));

  _schema_search_box.set_back_color(background_color);
  _schema_search_box.set_name("schema-search-box");
  _schema_search_box.set_spacing(5);
#ifdef _WIN32
  _schema_search_box.set_padding(4, 0, 6, 5);
  _schema_search_box.set_size(-1, 17); // Height excluding the padding, which adds extra pixels.
#else
  _schema_search_box.set_padding(8, 1, 8, 5);
#endif

#ifdef _WIN32
  mforms::ImageBox *image = mforms::manage(new mforms::ImageBox, false);
  image->set_image("search_sidebar.png");
  image->set_image_align(mforms::MiddleCenter);
  _schema_search_box.add(image, false, false);
#endif

  _schema_search_text.set_placeholder_text("Filter objects");
  _schema_search_box.add(&_schema_search_text, true, true);
  scoped_connect(_schema_search_text.signal_changed(), boost::bind(&AdvancedSidebar::on_search_text_changed, this));
  scoped_connect(_remote_search.signal_clicked(), boost::bind(&AdvancedSidebar::on_remote_search_clicked, this));

  // Add the tree itself and its section caption to the container.
  _schema_box.set_back_color(background_color);
  _schema_box.add(&_schema_search_box, false, true);
  _schema_box.add(&_new_schema_tree, true, true);
  _schema_box.show(false);
  add_end(&_schema_box, true, true);

}

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

void AdvancedSidebar::handle_notification(const std::string &name, void *sender, NotificationInfo &info)
{
  if (name == "GNColorsChanged")
  {
    std::string background_color = "#d9e2ef";
    std::string toolbar_background_color = "#d9e2ef";
    switch (Color::get_active_scheme())
    {
    case base::ColorSchemeStandardWin7:
    case base::ColorSchemeStandardWin8:
    case base::ColorSchemeStandardWin8Alternate:
      background_color = base::Color::get_application_color_as_string(AppColorPanelContentArea, false);
      toolbar_background_color = base::Color::get_application_color_as_string(AppColorPanelToolbar, false);
      break;

      default: // Set already.
        break;
    }

    _new_schema_tree.set_back_color(background_color);
    _filtered_schema_tree.set_back_color(background_color);
    _schema_box.set_back_color(background_color);

    _schema_search_box.set_back_color(toolbar_background_color);
  }
}

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

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)
  {
    _filtered_schema_model->set_filter(_schema_search_text.get_string_value());
    _filtered_schema_model->filter_data();

    if (_schema_model == _base_model)
    {
      //_base_model->filter_data(LiveSchemaTree::Any, _schema_search_text.get_string_value(), *_filtered_schema_model);
      _schema_box.remove(&_new_schema_tree);
      set_schema_model(_filtered_schema_model);
      _schema_box.add(&_filtered_schema_tree, true, true);

      if (_remote_search_enabled)
        _schema_box.add(&_remote_search, false, true);
      else
        _schema_box.add(&_schema_search_warning, false, true);
        
      _base_model->enable_events(false);
      _filtered_schema_model->enable_events(true);
    }
  }
  else
  {
    _schema_box.remove(&_filtered_schema_tree);
    
    if (_remote_search_enabled)
      _schema_box.remove(&_remote_search);
    else
      _schema_box.remove(&_schema_search_warning);
      
    set_schema_model(_base_model);
    _schema_box.add(&_new_schema_tree, true, true);
    _base_model->enable_events(true);
    _filtered_schema_model->enable_events(false);
  }

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

void AdvancedSidebar::on_tree_node_selected()
{
  _tree_node_selected();
}

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

void AdvancedSidebar::on_remote_search_clicked()
{
  std::vector<std::string> filter = base::split(_schema_search_text.get_string_value(), ".", 2);
  std::string schema_filter = filter[0];
  std::string object_filter = "";
  if (filter.size() == 2 )
    object_filter = filter[1];

  _filtered_schema_model->load_data_for_filter(schema_filter, object_filter);
}

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

void AdvancedSidebar::add_items_from_list(mforms::MenuBase &menu, const bec::MenuItemList &items)
{
  for (bec::MenuItemList::const_iterator item = items.begin(); item != items.end(); ++item)
  {
    if (item->type == bec::MenuAction)
    {
      mforms::MenuItem *mitem = menu.add_item_with_title(item->caption, boost::bind(&AdvancedSidebar::handle_menu_command, this, item->name));
      mitem->set_name(item->name);
      mitem->set_enabled(item->enabled);
    }
    else if (item->type == bec::MenuSeparator)
    {
      mforms::MenuItem *mitem = mforms::manage(new mforms::MenuItem("", mforms::SeparatorMenuItem));
      mitem->set_name(item->name);
      menu.add_item(mitem);
    }
    else if (item->type == bec::MenuCascade)
    {
      mforms::MenuItem *submenu = mforms::manage(new mforms::MenuItem(item->caption));
      submenu->set_name(item->name);
      add_items_from_list(*submenu, item->subitems);
      menu.add_submenu(submenu);
      submenu->set_enabled(item->enabled);
    }
  }
}

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

void AdvancedSidebar::on_show_menu(mforms::MenuItem *parent_item)
{
  if (!parent_item)
  {
    _tree_context_menu.remove_all();

    std::list<mforms::TreeNodeRef> nodes = _schema_model == _base_model ? _new_schema_tree.get_selection() : _filtered_schema_tree.get_selection();

    bec::MenuItemList items = _schema_model->get_popup_items_for_nodes(nodes);
    add_items_from_list(_tree_context_menu, items);
  }
}

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

void AdvancedSidebar::handle_menu_command(const std::string& command)
{
  std::list<mforms::TreeNodeRef> nodes = _schema_model == _base_model ? _new_schema_tree.get_selection() : _filtered_schema_tree.get_selection();
  
  _schema_model->activate_popup_item_for_nodes(command, nodes);
}

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

void AdvancedSidebar::tool_action_clicked(const std::string &action)
{
  std::list<mforms::TreeNodeRef> nodes;
  _schema_model->activate_popup_item_for_nodes(action, nodes);
}

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

void AdvancedSidebar::set_schema_model(LiveSchemaTree* model)
{
  // Sets the given model as the base model if none assigned already
  if(!_base_model)
  {
    _base_model = model;
    _base_model->set_model_view(&_new_schema_tree);
    _base_model->enable_events(true);
  }

  if (_is_model_owner)
  {
    delete _schema_model;
    _is_model_owner = false;
  }
  _schema_model = model;
  _schema_box.show(_schema_model != NULL);
}

void AdvancedSidebar::set_filtered_schema_model(wb::LiveSchemaTree* model) 
{
  _filtered_schema_model = model;
  _filtered_schema_model->set_model_view(&_filtered_schema_tree);
}

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

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