/* 
 * 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 "query_side_palette.h"
#include "wb_sql_editor_snippets.h"
#include "tree_model.h"
#include "base/geometry.h"
#include "snippet_popover.h"

#include "mforms/toolbar.h"
#include "mforms/hypertext.h"
#include "mforms/scrollpanel.h"
#include "mforms/drawbox.h"
#include "mforms/utilities.h"

#include <boost/signals2/signal.hpp>

#ifdef __APPLE__
  #define SNIPPET_DETAILS_FONT "Lucida Grande"
  #define SNIPPET_NORMAL_FONT "Tahoma"
#elif _WIN32
  #define SNIPPET_DETAILS_FONT "Arial"
  #define SNIPPET_NORMAL_FONT "Tahoma"
#else
  #define SNIPPET_DETAILS_FONT "Helvetica"
  #define SNIPPET_NORMAL_FONT "Tahoma"
#endif

#define USER_SNIPPETS "My Snippets"

#define SNIPPET_NORMAL_FONT_SIZE 11
#define SNIPPET_DETAILS_FONT_SIZE 10

using namespace mforms;
using namespace base;

//--------------------------------------------------------------------------------------------------
// TODO: move this to a central cairo helper class.
static int image_width(cairo_surface_t* image)
{
  if (image != NULL)
    return cairo_image_surface_get_width(image);
  return 0;
}

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

static int image_height(cairo_surface_t* image)
{
  if (image != NULL)
    return cairo_image_surface_get_height(image);
  return 0;
}


//----------------- Snippet ------------------------------------------------------------------------

class Snippet
{
private:
  cairo_surface_t* _icon;
  std::string _title;
  std::string _description;
  std::string _shortened_title;       // Contains the description shortened and with ellipses if the
                                      // full title doesn't fit into the available space.
  std::string _shortened_description; // Ditto for the description
  double _last_text_width;            // The last width for which the shortened description has been
                                      // computed. Used to avoid unnecessary re-computation.
  double _title_offset;               // Vertical position of the title.
  double _description_offset;         // Ditto for description.
  double _title_width;                // Width of the (possibly shortened) title. For text decoration.
  base::Rect _bounds;      // The link's bounds when it was drawn the last time.
  int _text_height;
  bool _enabled;                      // Draw the button in enabled or disabled state.

public:
  Snippet(cairo_surface_t* icon, const std::string& title, const std::string& description,
    bool enabled)
  {
    _icon = (icon != NULL) ? cairo_surface_reference(icon) : NULL;
    _title = title;
    _description = description;
    _last_text_width = 0;

    _title_offset = 0;
    _description_offset = 0;
    _title_width = 0;
    _text_height = 0;
    _enabled = enabled;
  }

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

  virtual ~Snippet()
  {
    if (_icon != NULL)
      cairo_surface_destroy(_icon);
  }

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

  #define SNIPPET_PADDING 2      // Left and right padding.
  #define SNIPPET_ICON_SPACING 4 // Horizontal distance between icon and text.
  #define SNIPPET_TEXT_SPACING 4 // Vertical distance between title and description.

  void layout(cairo_t* cr)
  {
    // Re-compute shortened title and its position.
    cairo_select_font_face(cr, SNIPPET_NORMAL_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, SNIPPET_NORMAL_FONT_SIZE);

    _shortened_title = Utilities::shorten_string(cr, _title, _last_text_width);

    cairo_text_extents_t title_extents;
    cairo_text_extents(cr, _shortened_title.c_str(), &title_extents);
    _title_offset = (int) -title_extents.y_bearing + 2;
    _title_width = title_extents.width;

    // Same for the description.
    cairo_select_font_face(cr, SNIPPET_DETAILS_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, SNIPPET_DETAILS_FONT_SIZE);
    _shortened_description = Utilities::shorten_string(cr, _description, _last_text_width);

    cairo_text_extents_t description_extents;
    cairo_text_extents(cr, _shortened_description.c_str(), &description_extents);
    _description_offset = _title_offset - (int) description_extents.y_bearing + SNIPPET_TEXT_SPACING;

    // Determine overall text height. This is used to center the text during paint.
    _text_height = (int) ceil(title_extents.height + description_extents.height + SNIPPET_TEXT_SPACING);
  }

  virtual void paint(cairo_t* cr, base::Rect bounds, bool hot, bool active)
  {
    _bounds = bounds;
    cairo_save(cr);

    double icon_width= image_width(_icon);
    double icon_height= image_height(_icon);

    double new_width= bounds.size.width - 2 * SNIPPET_PADDING - icon_width - SNIPPET_ICON_SPACING;

    if (new_width != _last_text_width)
    {
      _last_text_width= new_width;
      layout(cr);
    }

    cairo_set_line_width(cr, 1);

    // Fill a blue background if the item is active.
    if (active && _enabled)
    {
      cairo_set_source_rgb(cr, 0x5a / 255.0, 0x85 / 255.0, 0xdc / 255.0);
      cairo_rectangle(cr, bounds.left(), bounds.top(), bounds.size.width, bounds.size.height);
      cairo_fill(cr);
    }
    else
    {
      // If not selected draw a white separator line.
      cairo_set_source_rgb(cr, 1, 1, 1);
      cairo_move_to(cr, bounds.left(), bounds.bottom());
      cairo_line_to(cr, bounds.right(), bounds.bottom());
      cairo_stroke(cr);
    }

    cairo_set_source_surface(cr, _icon, bounds.left() + SNIPPET_PADDING, bounds.top() + (int) ceil((bounds.height() - icon_height) / 2));
    if (_enabled)
      cairo_paint(cr);
    else
      cairo_paint_with_alpha(cr, 0.25);

    int text_offset= (int) (bounds.height() - _text_height) / 2;
    cairo_select_font_face(cr, SNIPPET_NORMAL_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, SNIPPET_NORMAL_FONT_SIZE);

    if (!_enabled)
      cairo_set_source_rgb(cr, 0.75, 0.75, 0.75);
    else
      if (active)
        cairo_set_source_rgb(cr, 1, 1, 1);
      else
        if (hot)
          cairo_set_source_rgb(cr, 90 / 255.0, 147 / 255.0, 220 / 255.0);
        else
          cairo_set_source_rgb(cr, 34 / 255.0, 34 / 255.0, 34 / 255.0);
    double offset= bounds.left() + SNIPPET_PADDING + 
      ((icon_width > 0) ? icon_width + SNIPPET_ICON_SPACING : 0);

    cairo_move_to(cr, offset, bounds.top()  + _title_offset + text_offset);
    cairo_show_text(cr, _shortened_title.c_str());
    cairo_stroke(cr);

    cairo_select_font_face(cr, SNIPPET_DETAILS_FONT, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, SNIPPET_DETAILS_FONT_SIZE);

    if (!_enabled)
      cairo_set_source_rgb(cr, 0.75, 0.75, 0.75);
    else
      if (active)
        cairo_set_source_rgb(cr, 1, 1, 1);
      else
        cairo_set_source_rgb(cr, 182 / 255.0, 182 / 255.0, 182 / 255.0);

    cairo_move_to(cr, offset, bounds.top()  + _description_offset + text_offset);
    cairo_show_text(cr, _shortened_description.c_str());
    cairo_stroke(cr);

    // Hot style implementation is currently done manually (draw a line).
    // TODO: replace this with font decoration once pango is incorporated.
    if (hot && _enabled)
    {
      cairo_set_source_rgb(cr, 90 / 255.0, 147 / 255.0, 220 / 255.0);
      cairo_move_to(cr, offset, bounds.top() + _title_offset + text_offset + 1.5);
      cairo_line_to(cr, offset + _title_width, bounds.top() + _title_offset + text_offset + 1.5);
      cairo_stroke(cr);
    }

    cairo_restore(cr);
  }

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

  bool contains(double x, double y)
  {
    return _bounds.contains(x, y);
  }

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

  bool enabled()
  {
    return _enabled;
  };

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

  base::Rect bounds()
  {
    return _bounds;
  };

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

  std::string title()
  {
    return _title;
  };

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

  void title(const std::string& text)
  {
    _title = text;
    _last_text_width = 0;
  };

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

  std::string description()
  {
    return _description;
  };

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

  void description(const std::string& text)
  {
    _description = text;
    _last_text_width = 0;
  };

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

  // End of internal class Snippets.
};

//----------------- SnippetList --------------------------------------------------------------------

/**
  * Internal class containing a list of snippet entries in a scrollable list.
  */
class SnippetList : public mforms::DrawBox
{
private:
  DbSqlEditorSnippets* _model;     // The model containing the actual snippet data.

  cairo_surface_t* _image;         // The icon for all entries.
  std::vector<Snippet*> _snippets;
  int _last_width;                 // The last actual width we used for layouting.
  int _layout_width;               // This is rather a minimal width.
  int _layout_height;
  int _left_spacing;
  int _top_spacing;
  int _right_spacing;
  int _bottom_spacing;

  bool _user_snippets_active;
  bool _single_click;
  int _selected_index;

  Snippet* _hot_snippet;
  Snippet* _selected_snippet;
  MouseButton _last_mouse_button;

  wb::SnippetPopover* _snippet_popover;
  mforms::Menu* _context_menu;

  boost::signals2::signal<void ()> _selection_changed_signal;

private:

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

  int find_selected_index()
  {
    // Rather unlikely the selected link is not in the vector (since we got it from there) but...
    std::vector<Snippet*>::iterator location = std::find(_snippets.begin(), _snippets.end(), _selected_snippet);
    return (location == _snippets.end()) ? -1 : location - _snippets.begin();
  }

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

  void popover_closed()
  {
    if (_snippet_popover->has_changed())
    {
      std::string new_text = _snippet_popover->get_heading();
      _selected_snippet->title(new_text);
      _model->set_field(bec::NodeId(_selected_index), DbSqlEditorSnippets::Description, new_text);

      new_text = _snippet_popover->get_text();
      _selected_snippet->description(new_text);
      _model->set_field(bec::NodeId(_selected_index), DbSqlEditorSnippets::Script, new_text);
      _model->save();

      set_needs_repaint();
    }
  }

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

  void prepare_context_menu()
  {
    _context_menu = manage(new Menu());
    _context_menu->set_handler(boost::bind(&SnippetList::on_action, this, _1));
    _context_menu->signal_will_show()->connect(boost::bind(&SnippetList::menu_will_show, this));

    _context_menu->add_item(_("Insert snippet text at cursor"), "insert_text");
    _context_menu->add_item(_("Replace editor content with this snippet"), "replace_text");
    _context_menu->add_item(_("Execute snippet"), "exec_snippet");
    _context_menu->add_separator();
    _context_menu->add_item(_("Copy snippet text to clipboard"), "copy_to_clipboard");
    _context_menu->add_separator();
    _context_menu->add_item(_("Add snippet from editor content"), "add_snippet");
    _context_menu->add_item(_("Delete snippet"), "del_snippet");
    _context_menu->add_separator();
    _context_menu->add_item(_("Restore original snippet list"), "restore_snippets");
  }

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

  void menu_will_show()
  {
    _context_menu->set_item_enabled(0, _selected_index > -1);
    _context_menu->set_item_enabled(1, _selected_index > -1);
    _context_menu->set_item_enabled(2, _selected_index > -1);
    _context_menu->set_item_enabled(4, _selected_index > -1);
    _context_menu->set_item_enabled(7, _selected_index > -1);
    _context_menu->set_item_enabled(9, !_user_snippets_active);
  }

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

  void on_action(const std::string& action)
  {
    _model->activate_toolbar_item(bec::NodeId(_selected_index), action);

    // Refresh display if we added or removed a snippet.
    if (action == "add_snippet" || action == "del_snippet" || action == "restore_snippets")
      refresh_snippets();
  }

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

public:
  SnippetList(const std::string& icon_name)
  {
    // Not sure we need that spacing, so I leave it that way for now.
    _left_spacing = 0;
    _top_spacing = 0;
    _right_spacing = 3;
    _bottom_spacing = 0;

    _hot_snippet = 0;

    _user_snippets_active = false;
    _single_click = false;
    _selected_snippet = 0;
    _selected_index = -1;
    _last_mouse_button = MouseButtonNone;

    _model = DbSqlEditorSnippets::get_instance();
    _image = Utilities::load_icon(icon_name);

    _snippet_popover = new wb::SnippetPopover();
    _snippet_popover->set_size(376, 257);
    _snippet_popover->signal_closed()->connect(boost::bind(&SnippetList::popover_closed, this));

    prepare_context_menu();
  }

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

  ~SnippetList()
  {
    delete _snippet_popover;
    _context_menu->release();
    clear();
    if (_image != NULL)
      cairo_surface_destroy(_image);
  }

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

  boost::signals2::signal<void ()> *signal_selection_changed() { return &_selection_changed_signal; }

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

  std::string selected_category() { return _model->selected_category(); }

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

  /**
   * Updates the content depending on the selected snippet group.
   */
  void show_category(std::string category)
  {
    _user_snippets_active = (category == USER_SNIPPETS);
    _model->select_category(category);
    refresh_snippets();
  }

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

  void clear()
  {
    _selected_snippet = 0;
    _selected_index = -1;
    for (std::vector<Snippet*>::iterator iterator= _snippets.begin(); iterator != _snippets.end(); iterator++)
      delete *iterator;
    _snippets.clear();

    if (!is_destroying())
      set_layout_dirty(true);
  }

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

  void refresh_snippets()
  {
    clear();
    for (int i = 0; i < _model->count(); i++)
    {
      std::string caption;
      _model->get_field(bec::NodeId(i), DbSqlEditorSnippets::Description, caption);
      std::string description;
      _model->get_field(bec::NodeId(i), DbSqlEditorSnippets::Script, description);

      Snippet* snippet = new Snippet(_image, caption, description, true);
      _snippets.push_back(snippet);
    }
    set_layout_dirty(true);
    relayout();
    _selection_changed_signal();
  }

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

  #define SNIPPET_HEIGHT 34       // The height of a single action link.
  #define SNIPPET_SPACING 0       // Vertical distance between two snippets.

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

    double width= get_width();
   // double height= get_height();

    Rect snippet_bounds(_left_spacing, _top_spacing, width - _left_spacing - _right_spacing, SNIPPET_HEIGHT);

    for (std::vector<Snippet*>::const_iterator iterator= _snippets.begin(); iterator != _snippets.end(); iterator++)
    {
      (*iterator)->paint(cr, snippet_bounds, *iterator == _hot_snippet, *iterator == _selected_snippet);
      snippet_bounds.pos.y += snippet_bounds.size.height + SNIPPET_SPACING;
    }
  }

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

  void layout()
  {
    if (is_layout_dirty() || (_last_width != get_width()))
    {
      _last_width = get_width();
      set_layout_dirty(false);

      _layout_height = _top_spacing;
      _layout_width = _left_spacing + _right_spacing;

      if (_snippets.size() > 0)
        _layout_height += _snippets.size() * SNIPPET_HEIGHT + (_snippets.size() - 1) * SNIPPET_SPACING;

      if (_image != NULL)
      {
        // If an image is set then this defines the minimal width.
        _layout_width += SNIPPET_ICON_SPACING + image_width(_image);
      }

      if (_layout_height < SNIPPET_HEIGHT)
        _layout_height = SNIPPET_HEIGHT;
      _layout_height += _bottom_spacing;
    }  
  }

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

  virtual void get_layout_size(int* w, int* h)
  {
    layout();

    *w = _layout_width;
    *h = _layout_height;
  }

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

  Snippet* selected()
  {
    return _selected_snippet;
  }

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

  virtual void mouse_leave()
  {
    if (_hot_snippet != NULL)
    {
      _hot_snippet = NULL;
      set_needs_repaint();
    }
  }

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

  virtual void mouse_move(int x, int y)
  {
    if (_single_click)
    {
      Snippet* snippet = snippet_from_point(x, y);
      if (snippet != _hot_snippet)
      {
        _hot_snippet = snippet;
        set_needs_repaint();
      }
    }
  }

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

  virtual void mouse_down(int button, int x, int y)
  {
    if (button == MouseButtonLeft || button == MouseButtonRight)
    {
      Snippet* snippet= snippet_from_point(x, y);
      set_selected(snippet);
    }
  }

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

  virtual void mouse_double_click(int button, int x, int y)
  {
    switch (button)
    {
    case MouseButtonLeft:
      {
        Snippet* snippet = snippet_from_point(x, y);
        if (snippet != NULL && snippet == _selected_snippet)
        {
          base::Rect bounds = snippet->bounds();

          int left = (int) bounds.left();
          int dummy = left;
          int top = (int) bounds.top();
          int bottom = (int) bounds.bottom();
          client_to_screen(left, top);
          client_to_screen(dummy, bottom);
          top = (top + bottom) / 2; // Don't change this to top + height / 2, on OS X coordinates are flipped.

          _snippet_popover->set_heading(snippet->title());
          _snippet_popover->set_read_only(false);
          _snippet_popover->set_text(snippet->description());
          _snippet_popover->set_read_only(true);
          _snippet_popover->show(left, top, mforms::Left);
        }
        break;
      }
    case MouseButtonRight:
      {
        // If this double click results from a quick !right + right click then handle it as single right click.
        if (_last_mouse_button != MouseButtonRight)
          _context_menu->popup_at(this, x + 3, y + 3);
      }
    }
    _last_mouse_button = MouseButtonNone;
  }

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

  virtual void mouse_click(int button, int x, int y)
  {
    // Keep the last pressed button. A quick series of two different button clicks might be interpreted
    // as a double click of the second button. So we need to handle that.
    _last_mouse_button = (MouseButton)button;
    switch (button)
    {
    case MouseButtonRight:
      _context_menu->popup_at(this, x + 3, y + 3);
      break;
    }
  }

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

  int selected_index()
  {
    return _selected_index;
  }

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

  void set_selected(Snippet* snippet)
  {
    if (_selected_snippet != snippet)
    {
      _selected_snippet = snippet;
      _selected_index = find_selected_index();
      set_needs_repaint();
      _selection_changed_signal();
    }
  }

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

  Snippet* snippet_from_point(double x, double y)
  {
    if (x >= 0 && x < get_width() && y >= 0 && y <= get_height())
    {
      for (std::vector<Snippet*>::const_iterator iterator = _snippets.begin();
        iterator != _snippets.end(); iterator++)
      {
        if ((*iterator)->contains(x, y) && (*iterator)->enabled())
          return *iterator;
      }
    }
    return NULL;
  }

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

  void close_popover()
  {
    _snippet_popover->close();
  }

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

  // End of internal class SnippetList.
};

//----------------- QuerySidePalette ---------------------------------------------------------------

QuerySidePalette::QuerySidePalette(const SqlEditorForm::Ref &owner)
  : 
#ifdef _WIN32
    TabView(mforms::TabViewPalette),
#else
    TabView(mforms::TabViewSelectorSecondary),
#endif
    _owner(owner)
{
  _help_box = manage(new Box(false));
  _help_toolbar = manage(new ToolBar(mforms::PaletteToolBar));
  _help_text = manage(new HyperText());

  //add_page(_help_box, _("Quick Help")); // TODO: enable once it is fully implemented
  _help_box->add(_help_toolbar, false, true);
  
  // No scoped_connect needed since _help_text is member variable.
  _help_text->signal_link_click()->connect(boost::bind(&QuerySidePalette::click_link, this, _1));
#if _WIN32
  _help_text->set_back_color("#ffffff");
  Box* padding_box = manage(new Box(true));
  padding_box->set_padding(3);
  padding_box->add(_help_text, true, true);
  padding_box->set_back_color("#ffffff");
  _help_box->set_back_color("#ffffff");
  _help_box->add(padding_box, true, true);
#else
  _help_text->set_back_color("#ebebeb");
  _help_box->add(_help_text, true, true);
#endif
  
  mforms::ToolBarItem *item;
  item = mforms::manage(new ToolBarItem(mforms::TextActionItem));
  item->set_text("\xe2\x97\x80");
  _help_toolbar->add_item(item);
  item->release();
  
  item = mforms::manage(new ToolBarItem(mforms::TextActionItem));
  item->set_text("\xe2\x96\xb6");
  _help_toolbar->add_item(item);
  item->release();
  
  item = mforms::manage(new ToolBarItem(mforms::SeparatorItem));
  _help_toolbar->add_item(item);
  item->release();

  item = mforms::manage(new ToolBarItem(mforms::SelectorItem, true));
  std::vector<std::string> help_parts;
  help_parts.push_back(_("SQL Statements"));
  item->set_selector_items(help_parts);
  _help_toolbar->add_item(item);
  item->release();
  
  _help_text->set_markup_text("<b>Statement: <font color='#148814'>SELECT</font></b> <font color='#cbcbcb'>(simplified)</font><br/>"
                             "<b>Syntax:</b><br/>  SELECT <a href='123'>select_expr</a>, [, <a href='#1234'>select_expr</a> ...]<br/>  FROM <a href='#32'>table_reference</a><br/>  [WHERE <a href='#3123'>where_condition</a>]<br/><b>Links:</b><br/><a href='#312'>Full SELECT Syntax</a> | <a href='#132'>JOIN Syntax</a>");
  
  Box* snippet_page = manage(new Box(false));
  add_page(snippet_page, "Snippets");

  // Separate box since we need a border around the snippet list, but not the toolbar.
  Box* snippet_list_border = manage(new Box(false));
#ifdef _WIN32
  snippet_list_border->set_padding(3, 3, 3, 3);
#endif
  _snippet_box = manage(new ScrollPanel());
  _snippet_list = manage(new SnippetList("snippet_sql.png"));
  _snippet_list->set_back_color("#f2f2f2");
  _snippet_box->add(_snippet_list);

  DbSqlEditorSnippets* snippets_model = DbSqlEditorSnippets::get_instance();
  std::vector<std::string> snippet_categories = snippets_model->get_category_list();
  if (snippet_categories.size() > 0)
    _snippet_list->show_category(snippet_categories[0]);
  else
    _snippet_list->show_category(USER_SNIPPETS);

  ToolBar* toolbar = prepare_toolbar();
  _snippet_toolbar = toolbar;
  snippet_page->add(toolbar, false, true);

  snippet_list_border->add(_snippet_box, true, true);
  snippet_page->add(snippet_list_border, true, true);

  scoped_connect(_snippet_list->signal_selection_changed(), boost::bind(&QuerySidePalette::snippet_selection_changed, this));
  snippet_selection_changed();
}

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

QuerySidePalette::~QuerySidePalette()
{
}

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

void QuerySidePalette::click_link(const std::string &link)
{
  g_message("click link %s", link.c_str());
}

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

ToolBar* QuerySidePalette::prepare_toolbar()
{
  ToolBar* toolbar = manage(new ToolBar(mforms::PaletteToolBar));
  toolbar->set_name("snippet_toolbar");
#ifndef __APPLE__
  toolbar->set_padding(0, 3, 0, 2);
  toolbar->set_size(-1, 28);
#endif
  ToolBarItem* item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("add_snippet");
  item->set_icon(App::get()->get_resource_path("snippet_add.png"));
  item->set_tooltip(_("Add the current editor content as new snippet"));
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("del_snippet");
  item->set_icon(App::get()->get_resource_path("snippet_del.png"));
  item->set_tooltip(_("Delete the currently selected snippet"));
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::SeparatorItem));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::SelectorItem));
  item->set_name("select_category");

  DbSqlEditorSnippets* snippets_model = DbSqlEditorSnippets::get_instance();
  item->set_selector_items(snippets_model->get_category_list());
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  item->set_text(USER_SNIPPETS);
  item->set_tooltip(_("Select a snippet category for display"));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::SeparatorItem));
  toolbar->add_item(item);
  item->release();


  item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("replace_text");
  item->set_icon(App::get()->get_resource_path("snippet_use.png"));
  item->set_tooltip(_("Replace the current text by this snippet"));
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("insert_text");
  item->set_icon(App::get()->get_resource_path("snippet_insert.png"));
  item->set_tooltip(_("Insert the snippet text at the current caret position replacing selected text if there is any"));
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  toolbar->add_item(item);
  item->release();

  item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("copy_to_clipboard");
  item->set_icon(App::get()->get_resource_path("snippet_clipboard.png"));
  item->set_tooltip(_("Copy the snippet text to the clipboard"));
  scoped_connect(item->signal_activated(), boost::bind(&QuerySidePalette::toolbar_item_activated, this, _1));
  toolbar->add_item(item);
  item->release();

  return toolbar;
}

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

void QuerySidePalette::toolbar_item_activated(ToolBarItem* item)
{
  std::string action = item->get_name();
  if (action == "select_category")
    _snippet_list->show_category(item->get_text());
  else
  {
    DbSqlEditorSnippets* snippets_model = DbSqlEditorSnippets::get_instance();
    snippets_model->activate_toolbar_item(bec::NodeId(_snippet_list->selected_index()), action);

    // Refresh display if we added or removed a snippet.
    if (action == "add_snippet" || action == "del_snippet")
      _snippet_list->refresh_snippets();
  }
}


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

void QuerySidePalette::snippet_selection_changed()
{
  bool has_selection = _snippet_list->selected_index() > -1;
//  bool user_snippets_active = _snippet_list->selected_category() == USER_SNIPPETS;
 // _snippet_toolbar->set_item_enabled("add_snippet", true);
  _snippet_toolbar->set_item_enabled("del_snippet", has_selection);
  _snippet_toolbar->set_item_enabled("copy_to_clipboard", has_selection);
  _snippet_toolbar->set_item_enabled("replace_text", has_selection);
  _snippet_toolbar->set_item_enabled("insert_text", has_selection);
}

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

/**
 * Called by the owning form if the main form changes to remove the popover.
 */
void QuerySidePalette::close_popover()
{
  _snippet_list->close_popover();
}

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