/*
 * Copyright (c) 2011, 2019, 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, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is also distributed with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have included with MySQL.
 * 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, version 2.0, 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 <boost/signals2/signal.hpp>

#include "wb_sql_editor_form.h"
#include "wb_sql_editor_panel.h"
#include "query_side_palette.h"

#include "wb_sql_editor_snippets.h"
#include "wb_sql_editor_help.h"
#include "grt/tree_model.h"
#include "snippet_popover.h"
#include "snippet_list.h"
#include "grt/grt_manager.h"

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

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

DEFAULT_LOG_DOMAIN("QuerySidebar");

using namespace mforms;
using namespace base;

//----------------- SnippetListView ------------------------------------------------------------------------------------

/**
  * Internal class containing a list of snippet entries in a scrollable list.
  */
class SnippetListView : public BaseSnippetList {
private:
  friend class QuerySidePalette;

  wb::SnippetPopover *_snippetPopover;
  bool _user_snippets_active;
  bool _shared_snippets_active;

private:
  DbSqlEditorSnippets *model() {
    return dynamic_cast<DbSqlEditorSnippets *>(_model);
  }

  void popover_closed() {
    if (getPopover()->has_changed()) {
      std::string title = getPopover()->get_heading();
      model()->set_field(bec::NodeId(_selected_index), DbSqlEditorSnippets::Description, title);
      std::string sub_title = getPopover()->get_text();
      model()->set_field(bec::NodeId(_selected_index), DbSqlEditorSnippets::Script, sub_title);
      if (_selected_snippet)
        set_snippet_info(_selected_snippet, title, sub_title);
      model()->save();
      refresh_snippets();

      set_needs_repaint();
    }
  }

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

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

    _context_menu->add_item(_("Insert Snippet at Cursor"), "insert_text");
    _context_menu->add_item(_("Replace Editor Content with Snippet"), "replace_text");
    _context_menu->add_item(_("Execute Snippet"), "exec_snippet");
    _context_menu->add_separator();
    _context_menu->add_item(_("Copy Snippet to Clipboard"), "copy_to_clipboard");
    _context_menu->add_separator();
    _context_menu->add_item(_("Edit Snippet"), "edit_snippet");
    _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() {
    bool shared_usable = model()->shared_snippets_usable();

    _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(6, _selected_index > -1 && (!_shared_snippets_active || shared_usable));
    _context_menu->set_item_enabled(7, (!_shared_snippets_active || shared_usable));
    _context_menu->set_item_enabled(8, _selected_index > -1 && (!_shared_snippets_active || shared_usable));
    _context_menu->set_item_enabled(10, !_user_snippets_active && !_shared_snippets_active);
  }

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

  void on_action(const std::string &action) {
    if (action == "edit_snippet") {
      if (_selected_snippet)
        edit_snippet(_selected_snippet);
    } else
      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:
  SnippetListView(const std::string &icon_name) : BaseSnippetList(icon_name, DbSqlEditorSnippets::get_instance()), _snippetPopover(nullptr) {
    _user_snippets_active = false;
    _shared_snippets_active = false;

    _defaultSnippetActionCb = [&](int x, int y) {
       Snippet *snippet = snippet_from_point(x, y);
       if (snippet != nullptr) {
         set_selected(snippet);
         edit_snippet(snippet);
       }
    };

    prepare_context_menu();
  }

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

  wb::SnippetPopover* getPopover() {
    if (_snippetPopover != nullptr) {
      return _snippetPopover;
    }

    _snippetPopover = new wb::SnippetPopover(this);
    _snippetPopover->set_size(376, 257);
    _snippetPopover->signal_closed()->connect(std::bind(&SnippetListView::popover_closed, this));

    return _snippetPopover;
  }

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

  ~SnippetListView() {
    delete _snippetPopover;
    _context_menu->release();
  }

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

  void edit_new_snippet() {
    if (!_snippets.empty()) {
      _selected_index = 0;
      _selected_snippet = _snippets.front();
      edit_snippet(_selected_snippet);
      getPopover()->set_read_only(false);
    }
  }

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

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

  bool shared_snippets_active() const {
    return _shared_snippets_active;
  }

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

  /**
   * Updates the content depending on the selected snippet group.
   */
  void show_category(std::string category) {
    _user_snippets_active = (category == USER_SNIPPETS);
    _shared_snippets_active = (category == SHARED_SNIPPETS);
    try {
      model()->select_category(category);
    } catch (std::exception &exc) {
      logWarning("Error switching snippet category: %s\n", exc.what());
    }
    refresh_snippets();
  }

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

  void edit_snippet(Snippet *snippet) {
    base::Rect bounds = snippet_bounds(snippet);

    std::pair<int, int> left_top = client_to_screen((int)bounds.left(), (int)bounds.top());
    std::pair<int, int> bottom = client_to_screen(0, (int)bounds.bottom());
    left_top.second = (left_top.second + bottom.second) / 2;

    std::string title, description;
    get_snippet_info(snippet, title, description);


    getPopover()->set_heading(title);
    getPopover()->set_read_only(false);
    getPopover()->set_text(description);
    getPopover()->set_read_only(true);
    getPopover()->show(left_top.first, left_top.second, mforms::StartLeft);
  }

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

  virtual bool mouse_double_click(mforms::MouseButton button, int x, int y) {
    bool result = BaseSnippetList::mouse_double_click(button, x, y);

    if (!result) {
      if (button == MouseButtonLeft) {
        Snippet *snippet = snippet_from_point(x, y);
        if (snippet != NULL && snippet == _selected_snippet) {
          edit_snippet(snippet);
          result = true;
        }
      }
    }
    return result;
  }

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

  void close_popover() {
    // Check if it's at least created cause maybe we don't need to close it.
    if (_snippetPopover != nullptr) {
      getPopover()->close();
    }
  }

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

  // End of internal class SnippetListView.
};

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

QuerySidePalette::QuerySidePalette(const SqlEditorForm::Ref &owner)
  :
#ifdef _MSC_VER
    TabView(mforms::TabViewPalette),
#else
    TabView(mforms::TabViewSelectorSecondary),
#endif
    _owner(owner) {

  _help_timer = NULL;
  _automatic_help = bec::GRTManager::get()->get_app_option_int("DbSqlEditor:DisableAutomaticContextHelp", 0) == 0;
  _switching_help = false;
  _helpContext = new help::HelpContext(owner->rdbms()->characterSets(), owner->sql_mode(), owner->server_version());
  set_name("Query Side Palette");
  setInternalName("querySidePalette");

  _pending_snippets_refresh = true;

  mforms::Box *help_page = manage(new Box(false));
  _help_toolbar = prepare_help_toolbar();
  _help_text = manage(new mforms::HyperText());

  // Separate box since we need a border around the content, but not the toolbar.
  _contentBorder = manage(new Box(false));

  // No scoped_connect needed since _help_text is a member variable.
  scoped_connect(_help_text->signal_link_click(),
                 std::bind(&QuerySidePalette::click_link, this, std::placeholders::_1));

#if _MSC_VER
  std::string backgroundColor = base::Color::getApplicationColorAsString(AppColorPanelContentArea, false);
  _help_text->set_font("Tahoma 8");
#elif __APPLE__
  std::string backgroundColor = Color::getSystemColor(base::SystemColor::WindowBackgroundColor).to_html();
#else
  std::string backgroundColor = "#ebebeb";
#endif

  _help_text->set_back_color(backgroundColor);
  _contentBorder->set_back_color(backgroundColor);
  _contentBorder->set_padding(3, 3, 3, 3);

  _help_text->set_markup_text("");
  _current_topic_index = -1;

  help_page->add(_help_toolbar, false, true);
  _contentBorder->add(_help_text, true, true);
  help_page->add(_contentBorder, true, true);
  add_page(help_page, _("Context Help"));

  Box *snippet_page = manage(new Box(false));

  Box *content_border = manage(new Box(false));
  _snippet_list = manage(new SnippetListView("snippet_sql.png"));
  _snippet_list->set_name("Snippet List");
  _snippet_list->setInternalName("Snippet list");

  _snippet_box = manage(new ScrollPanel());
  _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);

  _snippet_toolbar = prepare_snippet_toolbar();

  snippet_page->add(_snippet_toolbar, false, true);
  content_border->add(_snippet_box, true, true);
  snippet_page->add(content_border, true, true);
  add_page(snippet_page, "Snippets");

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

  std::string old_category = bec::GRTManager::get()->get_app_option_string("DbSqlEditor:SelectedSnippetCategory");
  if (!old_category.empty()) {
    mforms::ToolBarItem *item = _snippet_toolbar->find_item("select_category");
    item->set_text(old_category);
    snippet_toolbar_item_activated(item);
  }

  snippet_selection_changed();
  show_help_hint_or_update();

  updateColors();

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

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

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

  cancel_timer();

  delete _helpContext;
}

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

void QuerySidePalette::cancel_timer() {
  if (_help_timer != NULL)
    bec::GRTManager::get()->cancel_timer(_help_timer);
}

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

void QuerySidePalette::handle_notification(const std::string &name, void *sender, base::NotificationInfo &info) {
  // Selection and caret changes notification.
  // Only act if this side palette is actually visible.
  if ((name == "GNTextSelectionChanged") && _automatic_help && (get_active_tab() == 0) && is_fully_visible()) {
    mforms::Object *object = (mforms::Object *)sender;
    mforms::CodeEditor *code_editor = dynamic_cast<mforms::CodeEditor *>(object);
    if (code_editor == NULL)
      return;

    MySQLEditor *editor = static_cast<MySQLEditor *>(code_editor->get_host());
    if (editor != NULL && editor->grtobj().is_valid()) {
      SqlEditorForm::Ref form = _owner.lock();
      cancel_timer();
      _help_timer = bec::GRTManager::get()->run_every(
        std::bind(&QuerySidePalette::find_context_help, this, editor), 0.5);
    }
  } else if (name == "GNColorsChanged") {
    updateColors();
  }
}

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

void QuerySidePalette::updateColors() {
#if _MSC_VER
  std::string backgroundColor = base::Color::getApplicationColorAsString(AppColorPanelContentArea, false);
  _help_text->set_font("Tahoma 8");
#elif __APPLE__
  std::string backgroundColor = Color::getSystemColor(base::SystemColor::WindowBackgroundColor).to_html();
#else
  std::string backgroundColor = "#ebebeb";
#endif

  _help_text->set_back_color(backgroundColor);
  _contentBorder->set_back_color(backgroundColor);

#ifdef _MSC_VER 
  _contentBorder->set_padding(3, 3, 3, 3);
  _snippet_list->set_back_color(base::Color::getApplicationColorAsString(AppColorPanelContentArea, false));
#elif __APPLE__
  _snippet_list->set_back_color(backgroundColor);
#else
  _snippet_list->set_back_color("#f2f2f2");
#endif

  // Also reload any help text, to update HTML colors.
  if (!_currentHelpTopic.empty()) {
    std::string text;
    help::DbSqlEditorContextHelp::get()->helpTextForTopic(_helpContext, _currentHelpTopic, text);
    _help_text->set_markup_text(text);
  }
}

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

void QuerySidePalette::show_help_text_for_topic(const std::string &topic) {
  _currentHelpTopic = topic;
  if (_currentHelpTopic.empty()) {
    _help_text->set_markup_text(std::string("<hmtl><body style=\"font-family:'") + DEFAULT_FONT_FAMILY + "'; \">"
      "<div style='text-align: center;'><b style='color: gray; font-size: 16pt;'>No Context Help</b></div></body></html>");
  } else {
    std::string text;
    help::DbSqlEditorContextHelp::get()->helpTextForTopic(_helpContext, _currentHelpTopic, text);
    _help_text->set_markup_text(text);

    _switching_help = true;
    _quick_jump_item->set_text(_currentHelpTopic);
    _switching_help = false;
  }
}

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

void QuerySidePalette::show_help_hint_or_update() {
  if (!_automatic_help) {
    _help_text->set_markup_text(
      std::string("<hmtl><body style=\"font-family:") + DEFAULT_FONT_FAMILY + ";\">"
      "<div style='text-align: center;'><b style='color: gray; font-size: 12pt;'>"
      "Automatic context help is disabled. Use the toolbar to manually get help for the current caret "
      "position or to toggle automatic help.</b></div></body></html>");
  } else {
    show_help_text_for_topic(_current_topic_index > 0 ? _topic_history[_current_topic_index] : "");
  }
}

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

/**
 * Triggered by timer or manually to find a help topic from the given editor's text + position.
 */
bool QuerySidePalette::find_context_help(MySQLEditor *editor) {
  _help_timer = nullptr;

  // If no editor was given use the currently active one in the SQL editor form.
  if (editor == nullptr) {
    SqlEditorForm::Ref form = _owner.lock();
    SqlEditorPanel *panel = form->active_sql_editor_panel();
    if (panel != nullptr)
      editor = panel->editor_be().get();
    else
      return false;
  }

  size_t caretPosition = editor->cursor_pos();

  size_t start, stop;
  editor->get_current_statement_range(start, stop); // To convert the caret position to a local statement position.

  std::string topic = help::DbSqlEditorContextHelp::get()->helpTopicFromPosition(_helpContext,
    editor->current_statement(), caretPosition - start);
  update_help_history(topic);
  show_help_text_for_topic(topic);

  return false; // Don't re-run this task, it's a single-shot.
}

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

/**
 * Adds the given topic to the topic history if the current topic is not the same and updates
 * the forward/backward buttons.
 */
void QuerySidePalette::update_help_history(const std::string &topic) {
  std::string topic_upper = base::toupper(topic);
  if (_current_topic_index > 0 && _topic_history[_current_topic_index] == topic_upper)
    return;

  if (!topic.empty()) {
    _current_topic_index++;

    // Remove everything after the new topic position.
    _topic_history.erase(_topic_history.begin() + _current_topic_index, _topic_history.end());
    _topic_history.push_back(topic_upper);
    _back_item->set_enabled(_current_topic_index > 0);
    _forward_item->set_enabled(false);
  }
}

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

void QuerySidePalette::click_link(const std::string &link) {
  if (link.find("local:") == 0) {
    // Internal link.
    std::string topic = base::trim(link.substr(6, link.size() - 6));
    base::replaceStringInplace(topic, "%20", " ");
    while (topic.find("  ") != std::string::npos)
      base::replaceStringInplace(topic, "  ", " ");

    update_help_history(topic);
    show_help_text_for_topic(topic);
  } else
    mforms::Utilities::open_url(link);
}

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

ToolBar *QuerySidePalette::prepare_snippet_toolbar() {
  ToolBar *toolbar = manage(new ToolBar(mforms::SecondaryToolBar));
  toolbar->set_name("Snippet Toolbar");
  toolbar->setInternalName("snippet_toolbar");
#ifndef __APPLE__
  toolbar->set_padding(0, 0, 0, 0);
  toolbar->set_size(-1, 27);
#endif
  ToolBarItem *item;

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

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

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

  item = mforms::manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("Replace Text");
  item->setInternalName("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(),
                 std::bind(&QuerySidePalette::snippet_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(item);

  item = mforms::manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("Insert Text");
  item->setInternalName("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(),
                 std::bind(&QuerySidePalette::snippet_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(item);

  item = manage(new ToolBarItem(mforms::ActionItem));
  item->set_name("Copy To Clipboard");
  item->setInternalName("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(),
                 std::bind(&QuerySidePalette::snippet_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(item);

  return toolbar;
}

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

void QuerySidePalette::snippet_toolbar_item_activated(ToolBarItem *item) {
  std::string action = item->getInternalName();
  if (action == "select_category") {
    _snippet_list->show_category(item->get_text());
    bec::GRTManager::get()->set_app_option("DbSqlEditor:SelectedSnippetCategory", grt::StringRef(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;
  _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();
}

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

ToolBar *QuerySidePalette::prepare_help_toolbar() {
  ToolBar *toolbar = manage(new ToolBar(mforms::SecondaryToolBar));
  toolbar->set_name("Help Toolbar");
  toolbar->setInternalName("help_toolbar");
#ifndef __APPLE__
  toolbar->set_padding(0, 0, 0, 0);
  toolbar->set_size(-1, 27);
#endif

  _back_item = manage(new ToolBarItem(mforms::ActionItem));
  _back_item->set_name("Back");
  _back_item->setInternalName("back");
  _back_item->set_icon(App::get()->get_resource_path("wb-toolbar_nav-back.png"));
  _back_item->set_tooltip(_("One topic back"));
  _back_item->set_enabled(false);
  scoped_connect(_back_item->signal_activated(),
                 std::bind(&QuerySidePalette::help_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(_back_item);

  _forward_item = manage(new ToolBarItem(mforms::ActionItem));
  _forward_item->set_name("Forward");
  _forward_item->setInternalName("forward");
  _forward_item->set_icon(App::get()->get_resource_path("wb-toolbar_nav-forward.png"));
  _forward_item->set_tooltip(_("One topic forward"));
  _forward_item->set_enabled(false);
  scoped_connect(_forward_item->signal_activated(),
                 std::bind(&QuerySidePalette::help_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(_forward_item);

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

  ToolBarItem *item = manage(new ToolBarItem(mforms::ToggleItem));
  item->set_name("Toggle Auto Context Help");
  item->setInternalName("toggle-auto-context-help");
  item->set_icon(App::get()->get_resource_path("wb-toolbar_automatic-help-off.png"));
  item->set_alt_icon(App::get()->get_resource_path("wb-toolbar_automatic-help-on.png"));
  item->set_tooltip(_("Toggle automatic context help"));
  item->set_checked(_automatic_help);
  scoped_connect(item->signal_activated(),
                 std::bind(&QuerySidePalette::help_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(item);

  _manual_help_item = manage(new ToolBarItem(mforms::ActionItem));
  _manual_help_item->set_name("Manual Context Help");
  _manual_help_item->setInternalName("manual-context-help");
  _manual_help_item->set_icon(App::get()->get_resource_path("wb-toolbar_manual-help.png"));
  _manual_help_item->set_tooltip(_("Get context help for the item at the current caret position"));
  _manual_help_item->set_enabled(!_automatic_help);
  scoped_connect(_manual_help_item->signal_activated(),
                 std::bind(&QuerySidePalette::help_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(_manual_help_item);

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

  _quick_jump_item = manage(new ToolBarItem(mforms::SelectorItem));
  _quick_jump_item->set_name("Quick Jump");
  _quick_jump_item->setInternalName("quick_jump");

  std::vector<std::string> topic_entries;
  topic_entries.push_back(_("Jump to"));
  topic_entries.push_back("SELECT");
  topic_entries.push_back("UPDATE");
  topic_entries.push_back("INSERT");
  topic_entries.push_back("DELETE");
  topic_entries.push_back("CREATE TABLE");
  topic_entries.push_back("CREATE VIEW");
  topic_entries.push_back("CREATE PROCEDURE");
  topic_entries.push_back("CREATE FUNCTION");
  topic_entries.push_back("ALTER TABLE");
  _quick_jump_item->set_selector_items(topic_entries);
  _quick_jump_item->set_text(_("Jump To"));
  scoped_connect(_quick_jump_item->signal_activated(),
                 std::bind(&QuerySidePalette::help_toolbar_item_activated, this, std::placeholders::_1));
  toolbar->add_item(_quick_jump_item);

  return toolbar;
}

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

void QuerySidePalette::help_toolbar_item_activated(ToolBarItem *item) {
  if (_switching_help)
    return;

  std::string action = item->getInternalName();
  if (action == "back" && _current_topic_index > 0) {
    std::string topic = _topic_history[--_current_topic_index];
    _back_item->set_enabled(_current_topic_index > 0);
    _forward_item->set_enabled(true);
    show_help_text_for_topic(topic);
    return;
  }

  if (action == "forward" && _current_topic_index < (int)_topic_history.size() - 1) {
    std::string topic = _topic_history[++_current_topic_index];
    _back_item->set_enabled(true);
    _forward_item->set_enabled(_current_topic_index < (int)_topic_history.size() - 1);
    show_help_text_for_topic(topic);
    return;
  }

  if (action == "quick_jump") {
    std::string topic = _quick_jump_item->get_text();
    update_help_history(topic);
    show_help_text_for_topic(topic);
    return;
  }

  if (action == "toggle-auto-context-help") {
    _automatic_help = item->get_checked();
    _manual_help_item->set_enabled(!_automatic_help);
    bec::GRTManager::get()->set_app_option("DbSqlEditor:DisableAutomaticContextHelp",
                                           grt::IntegerRef(_automatic_help ? 0 : 1));

    show_help_hint_or_update();

    return;
  }

  if (action == "manual-context-help")
    find_context_help(NULL);

  if (_current_topic_index >= 0) {
    if (action == "copy_to_clipboard") {
      std::pair<std::string, std::string> entry = _topic_cache[_topic_history[_current_topic_index]];
      mforms::Utilities::set_clipboard_text(entry.first);
    }
    if (action == "copy_html_to_clipboard") {
      std::pair<std::string, std::string> entry = _topic_cache[_topic_history[_current_topic_index]];
      mforms::Utilities::set_clipboard_text(entry.second);
    }
  }
}

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

void QuerySidePalette::refresh_snippets() {
  if (_pending_snippets_refresh && _snippet_list->shared_snippets_active()) {
    SqlEditorForm::Ref owner(_owner.lock());

    try {
      _snippet_list->model()->load_from_db(owner.get());
      _pending_snippets_refresh = false;
    } catch (std::exception &exc) {
      logError("Error loading DB snippets: %s\n", exc.what());
    }
  }
  _snippet_list->refresh_snippets();
}

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

void QuerySidePalette::edit_last_snippet() {
  _snippet_list->edit_new_snippet();
}

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