/*
 * Copyright (c) 2014, 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 designed to work 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 either included with
 * the program or referenced in the documentation.
 * 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 "wb_sql_editor_form.h"
#include "wb_sql_editor_panel.h"
#include "wb_sql_editor_result_panel.h"
#include "sqlide/sql_editor_be.h"
#include "sqlide/recordset_cdbc_storage.h"
#include "grtpp_notifications.h"

#include "base/log.h"
#include "base/file_functions.h"
#include "base/util_functions.h"

#include "mforms/toolbar.h"
#include "mforms/menubar.h"
#include "mforms/code_editor.h"
#include "mforms/find_panel.h"
#include "mforms/filechooser.h"

#include "workbench/wb_command_ui.h"

#include "base/boost_smart_ptr_helpers.h"

#include "objimpl/wrapper/mforms_ObjectReference_impl.h"
#include "objimpl/db.query/db_query_Resultset.h"

#include "grtui/file_charset_dialog.h"
#include "grtsqlparser/mysql_parser_services.h"

#include "mysql/MySQLRecognizerCommon.h"

#include <fstream>
#include <sstream>

// 20 MB max file size for auto-restoring
#define MAX_FILE_SIZE_FOR_AUTO_RESTORE 20000000

DEFAULT_LOG_DOMAIN("SqlEditorPanel");

using namespace bec;
using namespace base;

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

SqlEditorPanel::SqlEditorPanel(SqlEditorForm *owner, bool is_scratch, bool start_collapsed)
  : mforms::AppView(false, "Query Buffer", "db.query.QueryBuffer", false),
    _form(owner),
    _editor_box(false),
    _splitter(false, false),
#ifdef __APPLE__
    _lower_tabview(mforms::TabViewEditorBottomPinnable), // TODO: Windows, Linux
#else
    _lower_tabview(mforms::TabViewEditorBottom),
#endif
    _lower_dock_delegate(&_lower_tabview, db_query_QueryEditor::static_class_name()),
    _lower_dock(&_lower_dock_delegate, false),
    _tab_action_box(true),
    _tab_action_apply(mforms::SmallButton),
    _tab_action_revert(mforms::SmallButton),
    _tab_action_info("Read Only"),
    _rs_sequence(0),
    _busy(false),
    _is_scratch(is_scratch) {
  db_query_QueryEditorRef grtobj(grt::Initialized);

  grtobj->resultDockingPoint(mforms_to_grt(&_lower_dock));

  _autosave_file_suffix = grtobj.id();

  // In opposition to the object editors, each individual sql editor gets an own parser context
  // (and hence an own parser), to allow concurrent and multi threaded work.
  parsers::MySQLParserServices::Ref services = parsers::MySQLParserServices::get();

  parsers::MySQLParserContext::Ref context = services->createParserContext(
    owner->rdbms()->characterSets(), owner->rdbms_version(), owner->sql_mode(), owner->lower_case_table_names() != 0);

  parsers::SymbolTable *functionSymbols = parsers::functionSymbolsForVersion(bec::versionToEnum(owner->rdbms_version()));
  _editor = MySQLEditor::create(context, owner->work_parser_context(), { functionSymbols, owner->databaseSymbols() }, grtobj);
  _editor->set_sql_mode(owner->sql_mode());
  _editor->set_current_schema(owner->active_schema());
  UIForm::scoped_connect(_editor->text_change_signal(), std::bind(&SqlEditorPanel::update_title, this));

  add(&_splitter, true, true);

  mforms::CodeEditor *code_editor = editor_be()->get_editor_control();
  code_editor->set_name("Code Editor");
  code_editor->setInternalName("code editor");
  _editor_box.add(setup_editor_toolbar(), false, true);
  _editor_box.add_end(code_editor, true, true);

  code_editor->set_font(
    grt::StringRef::cast_from(bec::GRTManager::get()->get_app_option("workbench.general.Editor:Font")));
  code_editor->set_status_text("");
  code_editor->set_show_find_panel_callback(
    std::bind(&SqlEditorPanel::show_find_panel, this, std::placeholders::_1, std::placeholders::_2));

  if (start_collapsed)
    _editor->get_editor_control()->set_size(-1, 25);

  _splitter.add(&_editor_box, 150);
  _splitter.add(&_lower_tabview, 150);
  _editor_box.set_name("Editor Area");
  _editor_box.setInternalName("Editor area");
  _lower_tabview.set_name("Resultset Placeholder");
  _lower_tabview.setInternalName("Resultset placeholder");

  UIForm::scoped_connect(_splitter.signal_position_changed(), std::bind(&SqlEditorPanel::splitter_resized, this));
  _tab_action_box.set_spacing(4);
  _tab_action_box.add_end(&_tab_action_info, false, true);
  _tab_action_box.add_end(&_tab_action_icon, false, true);
  _tab_action_box.add_end(&_tab_action_revert, false, true);
  _tab_action_box.add_end(&_tab_action_apply, false, true);
  _tab_action_icon.set_image(mforms::App::get()->get_resource_path("mini_notice.png"));
  _tab_action_icon.show(false);
  _tab_action_info.show(false);
  _tab_action_apply.enable_internal_padding(true);
  _tab_action_apply.set_text("Apply");
  _tab_action_apply.signal_clicked()->connect(std::bind(&SqlEditorPanel::apply_clicked, this));
  _tab_action_revert.enable_internal_padding(true);
  _tab_action_revert.set_text("Revert");
  _tab_action_revert.signal_clicked()->connect(std::bind(&SqlEditorPanel::revert_clicked, this));

#ifdef _MSC_VER
  // 19 is the size of the tabs in the bottom tabview.
  _tab_action_apply.set_size(-1, 19);
  _tab_action_revert.set_size(-1, 19);
  _tab_action_box.set_size(-1, 19);
  _tab_action_box.set_back_color(Color::getApplicationColorAsString(AppColorTabUnselected, false));
#endif

  _lower_tabview.set_aux_view(&_tab_action_box);
  _lower_tabview.set_allows_reordering(true);
  _lower_tabview.signal_tab_reordered()->connect(std::bind(
    &SqlEditorPanel::lower_tab_reordered, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
  _lower_tabview.signal_tab_changed()->connect(std::bind(&SqlEditorPanel::lower_tab_switched, this));
  _lower_tabview.signal_tab_closing()->connect(
    std::bind(&SqlEditorPanel::lower_tab_closing, this, std::placeholders::_1));
  _lower_tabview.signal_tab_closed()->connect(
    std::bind(&SqlEditorPanel::lower_tab_closed, this, std::placeholders::_1, std::placeholders::_2));
  _lower_tabview.signal_tab_pin_changed()->connect(
    std::bind(&SqlEditorPanel::tab_pinned, this, std::placeholders::_1, std::placeholders::_2));
  _lower_tabview.is_pinned = std::bind(&SqlEditorPanel::is_pinned, this, std::placeholders::_1);
  _lower_tabview.set_tab_menu(&_lower_tab_menu);

  _splitter.set_expanded(false, false);
  set_on_close(std::bind(&SqlEditorPanel::on_close_by_user, this));

  _lower_tab_menu.signal_will_show()->connect(std::bind(&SqlEditorPanel::tab_menu_will_show, this));
  _lower_tab_menu.add_item_with_title("Rename Tab", std::bind(&SqlEditorPanel::rename_tab_clicked, this), "Rename Tab", "rename");
  _lower_tab_menu.add_check_item_with_title("Pin Tab", std::bind(&SqlEditorPanel::pin_tab_clicked, this), "Pin Tab", "pin");
  _lower_tab_menu.add_separator();
  _lower_tab_menu.add_item_with_title("Close Tab", std::bind(&SqlEditorPanel::close_tab_clicked, this), "Close Tab", "close");
  _lower_tab_menu.add_item_with_title("Close Other Tabs", std::bind(&SqlEditorPanel::close_other_tabs_clicked, this),
                                      "Close Other Tabs", "close_others");
}

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

SqlEditorPanel::~SqlEditorPanel() {
  _editor->stop_processing();
  _editor->cancel_auto_completion();
}

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

db_query_QueryEditorRef SqlEditorPanel::grtobj() {
  return db_query_QueryEditorRef::cast_from(_editor->grtobj());
}

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

bool SqlEditorPanel::on_close_by_user() {
  // this can also get closed when close_all_view() is called when the connection is closed
  if (_form->is_closing() || can_close()) {
    // do not call close, since that would undock ourselves and the caller will also undock this
    // we just need to be sure that the d-tor is called in all closing methods (close the tab itself from the X and
    // through kbd,
    // close the connection, close WB)
    //    close();
    return true;
  }
  return false;
}

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

bool SqlEditorPanel::can_close() {
  if (_busy)
    return false;

  bool check_editors = true;
  // if Save of workspace on close is enabled, we don't need to check whether there are unsaved scratch
  // SQL editors but other stuff should be checked.
  grt::ValueRef option(bec::GRTManager::get()->get_app_option("workbench:SaveSQLWorkspaceOnClose"));
  if (option.is_valid() && *grt::IntegerRef::cast_from(option))
    check_editors = false;

  // don't need to check for unsaved changes when closing the whole form
  // if save-workspace is enabled, since they'll get autosaved anyway
  // otoh, when closing the file itself, it should check
  if (!_form->is_closing())
    check_editors = true;

  if (!_is_scratch && check_editors) {
    if (is_dirty()) {
      int result = mforms::Utilities::show_warning(_("Close SQL Tab"),
                                                   strfmt(_("SQL script %s has unsaved changes.\n"
                                                            "Would you like to Save these changes before closing?"),
                                                          get_title().c_str()),
                                                   _("Save"), _("Cancel"), _("Don't Save"));

      if (result == mforms::ResultCancel)
        return false;
      else if (result == mforms::ResultOk) {
        if (!save())
          return false;
      } else
        _editor->get_editor_control()->reset_dirty();
    }
  }

  // check if there are unsaved recordset changes
  int edited_recordsets = 0;
  for (int c = _lower_tabview.page_count(), i = 0; i < c; i++) {
    SqlEditorResult *result = dynamic_cast<SqlEditorResult *>(_lower_tabview.get_page(i));
    if (result && result->has_pending_changes())
      edited_recordsets++;
  }

  int r = -999;
  if (edited_recordsets == 1)
    r = mforms::Utilities::show_warning(
      _("Close SQL Tab"), strfmt(_("An edited recordset has unsaved changes in %s.\n"
                                   "Would you like to save these changes, discard them or cancel closing the page?"),
                                 get_title().c_str()),
      _("Save Changes"), _("Cancel"), _("Don't Save"));
  else if (edited_recordsets > 0)
    r = mforms::Utilities::show_warning(
      _("Close SQL Tab"),
      strfmt(_("There are %i recordsets with unsaved changes in %s.\n"
               "Would you like to save these changes, discard them or cancel closing to review them manually?"),
             edited_recordsets, get_title().c_str()),
      _("Save All"), _("Cancel"), _("Don't Save"));

  bool success = true;
  if (r != -999) {
    if (r == mforms::ResultCancel)
      success = false;
    else {
      for (int c = _lower_tabview.page_count(), i = 0; i < c; i++) {
        SqlEditorResult *result = dynamic_cast<SqlEditorResult *>(_lower_tabview.get_page(i));
        if (result && result->has_pending_changes()) {
          try {
            if (r == mforms::ResultOk)
              result->apply_changes();
            else
              result->discard_changes();
          } catch (const std::exception &exc) {
            if (mforms::Utilities::show_error(
                  _("Save Changes"), strfmt(_("An error occurred while saving changes to the recordset %s\n%s"),
                                            result->recordset()->caption().c_str(), exc.what()),
                  _("Ignore"), _("Cancel"), "") == mforms::ResultCancel) {
              success = false;
              break;
            }
          }
        }
      }
    }
  }

  // close everything now (recordsets should already have their dirty flag cleared)
  if (success && !_lower_dock.close_all_views())
    return false;

  return success;
}

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

void SqlEditorPanel::apply_clicked() {
  SqlEditorResult *result = active_result_panel();
  if (result)
    result->apply_changes();
}

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

void SqlEditorPanel::revert_clicked() {
  SqlEditorResult *result = active_result_panel();
  if (result)
    result->discard_changes();
}

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

void SqlEditorPanel::resultset_edited() {
  SqlEditorResult *result = active_result_panel();
  Recordset::Ref rset;
  if (result && (rset = result->recordset())) {
    bool edited = rset->has_pending_changes();
    _tab_action_apply.set_enabled(edited);
    _tab_action_revert.set_enabled(edited);

    _form->get_menubar()->set_item_enabled("query.save_edits", edited);
    _form->get_menubar()->set_item_enabled("query.discard_edits", edited);
  }
}

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

void SqlEditorPanel::splitter_resized() {
  if (_lower_tabview.page_count() > 0) {
    bec::GRTManager::get()->set_app_option("DbSqlEditor:ResultSplitterPosition",
                                           grt::IntegerRef(_splitter.get_divider_position()));
  }
}

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

static bool check_if_file_too_big_to_restore(const std::string &path, const std::string &file_caption,
                                             bool allow_save_as = false) {
  std::int64_t length;
  if ((length = get_file_size(path.c_str())) > MAX_FILE_SIZE_FOR_AUTO_RESTORE) {
  again:
    std::string size = sizefmt(length, false);
    int rc = mforms::Utilities::show_warning(
      "Restore Workspace", strfmt("The file %s has a size of %s. Are you sure you want to restore this file?",
                                  file_caption.c_str(), size.c_str()),
      "Restore", "Skip", allow_save_as ? "Save As..." : "");
    if (rc == mforms::ResultCancel)
      return false;

    if (rc == mforms::ResultOther) {
      mforms::FileChooser fchooser(mforms::SaveFile);

      fchooser.set_title(_("Save File As..."));
      if (fchooser.run_modal()) {
        if (!base::copyFile(path.c_str(), fchooser.get_path().c_str())) {
          if (mforms::Utilities::show_error("Save File",
                                            strfmt("File %s could not be saved.", fchooser.get_path().c_str()),
                                            _("Retry"), _("Cancel"), "") == mforms::ResultOk)
            goto again;
        }
      } else
        goto again;
      return false;
    }
  }
  return true;
}

#define EDITOR_TEXT_LIMIT 100 * 1024 * 1024

SqlEditorPanel::AutoSaveInfo::AutoSaveInfo(const std::string &info_file) : word_wrap(false), show_special(false) {
  wchar_t buffer[4098] = {0};
  std::wifstream f = openTextInputStream(info_file);
  while (f.getline(buffer, sizeof(buffer))) {
    std::string key, value;
    base::partition(base::wstring_to_string(buffer), "=", key, value);
    if (key == "orig_encoding")
      orig_encoding = value;
    else if (key == "type")
      type = value;
    else if (key == "filename")
      filename = value;
    else if (key == "title")
      title = value;
    else if (key == "word_wrap")
      word_wrap = value == "1";
    else if (key == "show_special")
      show_special = value == "1";
    else if (key == "first_visible_line")
      first_visible_line = base::atoi<int>(value, 0);
    else if (key == "caret_pos")
      caret_pos = base::atoi<int>(value, 0);
  }
}

SqlEditorPanel::AutoSaveInfo SqlEditorPanel::AutoSaveInfo::old_scratch(const std::string &scratch_file) {
  AutoSaveInfo info;
  info.title = base::strip_extension(base::basename(scratch_file));
  if (base::is_number(info.title))
    info.title = base::strfmt("Query %i", 1 + base::atoi<int>(info.title, 0));
  info.type = "scratch";
  return info;
}

SqlEditorPanel::AutoSaveInfo SqlEditorPanel::AutoSaveInfo::old_autosave(const std::string &autosave_file) {
  char buffer[4098];

  AutoSaveInfo info;
  info.title = base::strip_extension(base::basename(autosave_file));
  info.type = "file";
  std::ifstream f(base::strip_extension(autosave_file).c_str());
  if (f.getline(buffer, sizeof(buffer)))
    info.filename = buffer;
  if (f.getline(buffer, sizeof(buffer)))
    info.orig_encoding = buffer;
  return info;
}

bool SqlEditorPanel::load_autosave(const AutoSaveInfo &info, const std::string &text_file) {
  _orig_encoding = info.orig_encoding;
  _file_timestamp = 0;
  _is_scratch = (info.type == "scratch");

  // there's no autosave
  if (text_file.empty() || !base::file_exists(text_file)) {
    if (!info.filename.empty() &&
        !check_if_file_too_big_to_restore(info.filename, strfmt("Saved editor '%s'", info.title.c_str()))) {
      return false;
    }

    // if this was a file, try to load it
    if (!info.filename.empty() && load_from(info.filename, info.orig_encoding, false) != Loaded)
      return false;
  } else {
    // check if autosave too big
    if (!check_if_file_too_big_to_restore(text_file, strfmt("Saved editor '%s'", info.title.c_str()))) {
      return false;
    }

    // load the autosave
    if (load_from(text_file, info.orig_encoding, true) != Loaded)
      return false;
  }
  _filename = info.filename;
  if (!_filename.empty())
    base::file_mtime(_filename, _file_timestamp);

  set_title(info.title);

  mforms::ToolBarItem *item = get_toolbar()->find_item("query.toggleInvisible");
  item->set_checked(info.show_special);
  (*item->signal_activated())(item);

  item = get_toolbar()->find_item("query.toggleWordWrap");
  item->set_checked(info.word_wrap);
  (*item->signal_activated())(item);

  _editor->get_editor_control()->set_caret_pos(info.caret_pos);
  _editor->get_editor_control()->send_editor(SCI_SETFIRSTVISIBLELINE, info.first_visible_line, 0);

  return true;
}

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

SqlEditorPanel::LoadResult SqlEditorPanel::load_from(const std::string &file, const std::string &encoding,
                                                     bool keep_dirty) {
  GError *error = NULL;
  gchar *data;
  gsize length;
  gsize file_size = base_get_file_size(file.c_str());

  if (file_size > EDITOR_TEXT_LIMIT) {
    // File is larger than 100 MB. Tell the user we are going to switch off code folding and
    // auto completion.
    int result = mforms::Utilities::show_warning(
      _("Large File"), strfmt(_("The file \"%s\" has a size "
                                "of %.2f MB. Are you sure you want to open this large file?\n\nNote: code folding "
                                "will be disabled for this file.\n\nClick Run SQL Script... to just execute the file."),
                              file.c_str(), file_size / 1024.0 / 1024.0),
      _("Open"), _("Cancel"), _("Run SQL Script..."));
    if (result == mforms::ResultCancel)
      return Cancelled;
    else if (result == mforms::ResultOther)
      return RunInstead;
  }

  _orig_encoding = encoding;

  if (!g_file_get_contents(file.c_str(), &data, &length, &error)) {
    logError("Could not read file %s: %s\n", file.c_str(), error->message);
    std::string what = error->message;
    g_error_free(error);
    throw std::runtime_error(what);
  }

  char *utf8_data;
  std::string original_encoding;
  FileCharsetDialog::Result result =
    FileCharsetDialog::ensure_filedata_utf8(data, length, encoding, file, utf8_data, &original_encoding);
  if (result == FileCharsetDialog::Cancelled) {
    g_free(data);
    return Cancelled;
  } else if (result == FileCharsetDialog::RunInstead) {
    g_free(data);
    return RunInstead;
  }

  // if original data was in utf8, utf8_data comes back NULL
  if (!utf8_data)
    utf8_data = data;
  else
    g_free(data);

  _editor->set_refresh_enabled(true);
  _editor->sql(utf8_data ? utf8_data : "");

  g_free(utf8_data);

  if (!keep_dirty) {
    _editor->get_editor_control()->reset_dirty();

    _filename = file;
    _orig_encoding = original_encoding;

    set_title(strip_extension(basename(file)));
  }

  if (!file_mtime(file, _file_timestamp)) {
    logWarning("Can't get timestamp for %s\n", file.c_str());
    _file_timestamp = 0;
  }
  return Loaded;
}

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

void SqlEditorPanel::close() {
  _form->remove_sql_editor(this);
}

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

bool SqlEditorPanel::save_as(const std::string &path) {
  if (path.empty()) {
    mforms::FileChooser dlg(mforms::SaveFile);

    dlg.set_title(_("Save SQL Script"));
    dlg.set_extensions("SQL Files (*.sql)|*.sql", "sql");
    if (!_filename.empty())
      dlg.set_path(_filename);
    if (!dlg.run_modal())
      return false;
    _filename = dlg.get_path();
  }

  if (save()) {
    set_title(strip_extension(basename(_filename)));
    {
      NotificationInfo info;
      info["opener"] = "SqlEditorForm";
      info["path"] = _filename;
      NotificationCenter::get()->send("GNDocumentOpened", this, info);
    }
    return true;
  }
  return false;
}

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

bool SqlEditorPanel::save() {
  if (_filename.empty())
    return save_as("");

  GError *error = NULL;

  // File extension check is already done in FileChooser.

  bec::GRTManager::get()->replace_status_text(strfmt(_("Saving SQL script to '%s'..."), _filename.c_str()));

  std::pair<const char *, size_t> text = text_data();
  if (!g_file_set_contents(_filename.c_str(), text.first, text.second, &error)) {
    logError("Could not save script %s: %s\n", _filename.c_str(), error->message);
    bec::GRTManager::get()->replace_status_text(strfmt(_("Error saving SQL script to '%s'."), _filename.c_str()));

    mforms::Utilities::show_error(strfmt(_("Error writing file %s"), _filename.c_str()), error->message, _("OK"));
    g_error_free(error);
    return false;
  }

  // reset dirty marker, but not the undo stack
  _editor->get_editor_control()->reset_dirty();
  _is_scratch = false; // saving a file makes it not a scratch buffer anymore
  file_mtime(_filename, _file_timestamp);

  bec::GRTManager::get()->replace_status_text(strfmt(_("SQL script saved to '%s'"), _filename.c_str()));

  // update autosave state
  _form->auto_save();

  update_title();

  return true;
}

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

void SqlEditorPanel::revert_to_saved() {
  _editor->sql("");
  if (load_from(_filename, _orig_encoding) == Loaded) {
    {
      NotificationInfo info;
      info["opener"] = "SqlEditorForm";
      info["path"] = _filename;
      NotificationCenter::get()->send("GNDocumentOpened", this, info);
    }
    _form->auto_save();
    bec::GRTManager::get()->replace_status_text(strfmt(_("Reverted to saved '%s'"), _filename.c_str()));
  }
}

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

std::string SqlEditorPanel::autosave_file_suffix() {
  return _autosave_file_suffix;
}

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

void SqlEditorPanel::auto_save(const std::string &path) {
  // save info about the file
  {
    std::wofstream f = openTextOutputStream(base::makePath(path, _autosave_file_suffix + ".info"));
    std::string content;
    if (_is_scratch)
      content += "type=scratch\n";
    else
      content += "type=file\n";

    if (!_is_scratch && !_filename.empty()) {
      content += "filename=" + _filename + "\n";
    }
    content += "orig_encoding=" + _orig_encoding + "\n";

    content += "title=" + _title + "\n";

    if (get_toolbar()->get_item_checked("query.toggleInvisible"))
      content += "show_special=1\n";
    else
      content += "show_special=0\n";
    if (get_toolbar()->get_item_checked("query.toggleWordWrap"))
      content += "word_wrap=1\n";
    else
      content += "word_wrap=0\n";

    size_t caret_pos = _editor->get_editor_control()->get_caret_pos();
    content += "caret_pos=" + std::to_string(caret_pos) + "\n";

    size_t first_line = _editor->get_editor_control()->send_editor(SCI_GETFIRSTVISIBLELINE, 0, 0);
    content += "first_visible_line=" + std::to_string(first_line) + "\n";

    if (f.good())
      f << base::string_to_wstring(content);
    f.close();
  }

  std::string fn = base::makePath(path, _autosave_file_suffix + ".scratch");

  // only save editor contents for scratch areas and unsaved editors
  if (_is_scratch || _filename.empty() || (!_filename.empty() && is_dirty())) {
    // We don't need to lock the editor as we are in the main thread here
    // and directly set the file content without detouring to anything that could change the text.
    GError *error = 0;

    std::pair<const char *, size_t> text = text_data();
    if (!g_file_set_contents(fn.c_str(), text.first, text.second, &error)) {
      logError("Could not save snapshot of editor contents to %s: %s\n", fn.c_str(), error->message);
      std::string msg(strfmt("Could not save snapshot of editor contents to %s: %s", fn.c_str(), error->message));
      g_error_free(error);
      throw std::runtime_error(msg);
    }
  } else {
    // delete the autosave file if the file was saved
    try {
      base::remove(fn);
    } catch (std::exception &e) {
      logWarning("Error deleting autosave file %s: %s\n", fn.c_str(), e.what());
    }
  }
}

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

void SqlEditorPanel::delete_auto_save(const std::string &path) {
  // delete the autosave related files
  try {
    base::remove(base::makePath(path, _autosave_file_suffix + ".autosave"));
  } catch (std::exception &exc) {
    logWarning("Could not delete auto-save file: %s\n", exc.what());
  }
  try {
    base::remove(base::makePath(path, _autosave_file_suffix + ".info"));
  } catch (std::exception &exc) {
    logWarning("Could not delete auto-save file: %s\n", exc.what());
  }
}

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

// Toolbar handling.

void SqlEditorPanel::show_find_panel(mforms::CodeEditor *editor, bool show) {
  mforms::FindPanel *panel = editor->get_find_panel();
  if (show && !panel->get_parent())
    _editor_box.add(panel, false, true);
  panel->show(show);
}

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

static void toggle_continue_on_error(SqlEditorForm *sql_editor_form) {
  sql_editor_form->continue_on_error(!sql_editor_form->continue_on_error());
}

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

mforms::ToolBar *SqlEditorPanel::setup_editor_toolbar() {
  mforms::ToolBar *tbar(mforms::manage(new mforms::ToolBar(mforms::OptionsToolBar)));
  tbar->set_name("Editor Toolbar");
#ifdef _MSC_VER
  tbar->set_size(-1, 27);
#endif
  mforms::ToolBarItem *item;

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Open File");
  item->setInternalName("query.openFile");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_open.png"));
  item->set_tooltip(_("Open a script file in this editor"));
  bec::UIForm::scoped_connect(
    item->signal_activated(),
    std::bind((void (SqlEditorForm::*)(const std::string &, bool, bool)) & SqlEditorForm::open_file, _form, "", false,
              true));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Save File");
  item->setInternalName("query.saveFile");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_save.png"));
  item->set_tooltip(_("Save the script to a file."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorPanel::save, this));
  tbar->add_item(item);

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

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Execute");
  item->setInternalName("query.execute");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_execute.png"));
  item->set_tooltip(_("Execute the selected portion of the script or everything, if there is no selection"));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::run_editor_contents, _form, false));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Execute Current Statement");
  item->setInternalName("query.execute_current_statement");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_execute-current.png"));
  item->set_tooltip(_("Execute the statement under the keyboard cursor"));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::run_editor_contents, _form, true));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Explain Current Statement");
  item->setInternalName("query.explain_current_statement");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_explain.png"));
  item->set_tooltip(_("Execute the EXPLAIN command on the statement under the cursor"));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::explain_current_statement, _form));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Cancel");
  item->setInternalName("query.cancel");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop.png"));
  item->set_tooltip(
    _("Stop the query being executed (the connection to the DB server will not be restarted and any open transactions "
      "will remain open)"));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::cancel_query, _form));
  tbar->add_item(item);

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

  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("Continue On Error");
  item->setInternalName("query.continueOnError");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop-on-error-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_stop-on-error-off.png"));
  item->set_tooltip(_("Toggle whether execution of SQL script should continue after failed statements"));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(toggle_continue_on_error, _form));

  tbar->add_item(item);

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

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Commit");
  item->setInternalName("query.commit");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_commit.png"));
  item->set_tooltip(
    _("Commit the current transaction.\nNOTE: all query tabs in the same connection share the same transaction. To "
      "have independent transactions, you must open a new connection."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::commit, _form));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Rollback");
  item->setInternalName("query.rollback");
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_rollback.png"));
  item->set_tooltip(
    _("Rollback the current transaction.\nNOTE: all query tabs in the same connection share the same transaction. To "
      "have independent transactions, you must open a new connection."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::rollback, _form));
  tbar->add_item(item);

  item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("Auto Commit");
  item->setInternalName("query.autocommit");
  item->set_alt_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_autocommit-on.png"));
  item->set_icon(IconManager::get_instance()->get_icon_path("qe_sql-editor-tb-icon_autocommit-off.png"));
  item->set_tooltip(_(
    "Toggle autocommit mode. When enabled, each statement will be committed immediately.\nNOTE: all query tabs in the "
    "same connection share the same transaction. To have independent transactions, you must open a new connection."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::toggle_autocommit, _form));
  tbar->add_item(item);

  tbar->add_separator_item();

  item = mforms::manage(new mforms::ToolBarItem(mforms::SelectorItem));
  item->set_name("Limit Rows");
  item->setInternalName("limit_rows");
  item->set_tooltip(
    _("Set limit for number of rows returned by queries.\nWorkbench will automatically add the LIMIT clause with the "
      "configured number of rows to SELECT queries."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorPanel::limit_rows, this, item));
  tbar->add_item(item);

  tbar->add_separator_item();

  item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
  item->set_name("Add Snippet");
  item->setInternalName("add_snippet");
  item->set_icon(IconManager::get_instance()->get_icon_path("snippet_add.png"));
  item->set_tooltip(_("Save current statement or selection to the snippet list."));
  bec::UIForm::scoped_connect(item->signal_activated(), std::bind(&SqlEditorForm::save_snippet, _form));
  tbar->add_item(item);

  tbar->add_separator_item();

  // adds generic SQL editor toolbar buttons
  _editor->set_base_toolbar(tbar);

  update_limit_rows();

  return tbar;
}

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

void SqlEditorPanel::limit_rows(mforms::ToolBarItem *item) {
  _form->limit_rows(item->get_text());
}

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

void SqlEditorPanel::update_limit_rows() {
  mforms::MenuItem *mitem = _form->get_menubar()->find_item("limit_rows");
  std::string selected;
  std::vector<std::string> items;
  for (int i = 0; i < mitem->item_count(); i++) {
    if (!mitem->get_item(i)->get_title().empty()) {
      items.push_back(mitem->get_item(i)->get_title());
      if (mitem->get_item(i)->get_checked())
        selected = items.back();
    }
  }

  mforms::ToolBarItem *item = get_toolbar()->find_item("limit_rows");
  item->set_selector_items(items);
  item->set_text(selected);
}

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

mforms::ToolBar *SqlEditorPanel::get_toolbar() {
  return _editor->get_toolbar();
}

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

bool SqlEditorPanel::is_dirty() const {
  return _editor->get_editor_control()->is_dirty();
}

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

void SqlEditorPanel::check_external_file_changes() {
  time_t ts;
  if (!_filename.empty() && file_mtime(_filename, ts)) {
    if (ts > _file_timestamp) {
      // File was changed externally. For now we ignore local changes if the user chooses to reload.
      std::string connection_description =
        _form->connection_descriptor().is_valid()
          ? strfmt("(from connection to %s) ", _form->connection_descriptor()->name().c_str())
          : "";
      if (mforms::Utilities::show_warning("File Changed",
                                          strfmt(_("File %s %swas changed from outside MySQL Workbench.\nWould you "
                                                   "like to discard your changes and reload it?"),
                                                 _filename.c_str(), connection_description.c_str()),
                                          "Reload File", "Ignore", "") == mforms::ResultOk) {
        revert_to_saved();
      } else {
        _file_timestamp = ts;
      }
    }
  }
}

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

std::pair<const char *, std::size_t> SqlEditorPanel::text_data() const {
  return _editor->text_ptr();
}

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

void SqlEditorPanel::set_title(const std::string &title) {
  _title = title;
  grtobj()->name(_title);
  mforms::AppView::set_title(title);
}

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

void SqlEditorPanel::set_filename(const std::string &filename) {
  _filename = filename;
  if (!filename.empty())
    set_title(strip_extension(basename(filename)));
}

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

void SqlEditorPanel::update_title() {
  if (!_is_scratch)
    mforms::AppView::set_title(_title + (is_dirty() ? "*" : ""));
}

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

/**
 * Starts the auto completion list in the currently active editor. The content of this list is
 * determined from various sources + the current query context.
 */
void SqlEditorPanel::list_members() {
  if (owner()->work_parser_context() != NULL)
    editor_be()->show_auto_completion(false);
}

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

void SqlEditorPanel::jump_to_placeholder() {
  _editor->get_editor_control()->jump_to_next_placeholder();
}

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

void SqlEditorPanel::query_started(bool retain_old_recordsets) {
  _busy = true;

  logDebug("Preparing UI for query run\n");

  _form->set_busy_tab(_form->sql_editor_panel_index(this));

  // disable tabview reordering because we can get new tabs added at odd times if a query is running
  _lower_tabview.set_allows_reordering(false);

  // if we're already running the query, it's obvious there's no more need to autocomplete what we typed
  _editor->cancel_auto_completion();

  if (!retain_old_recordsets) {
    logDebug("Releasing old recordset(s) (if possible)\n");

    // close recordsets that were opened previously (unless they're pinned or something)
    for (int i = _lower_tabview.page_count() - 1; i >= 0; --i) {
      SqlEditorResult *result = dynamic_cast<SqlEditorResult *>(_lower_tabview.get_page(i));
      if (result) {
        if (result->pinned())
          continue;

        if (result->has_pending_changes())
          continue;

        // make sure that the result is docked here
        int i = _lower_tabview.get_page_index(result);
        if (i >= 0) {
          result->close();
          result_removed();
        }
      }
    }
  } else {
    logDebug("Retaining old recordset(s)\n");
  }

  _was_empty = (_lower_tabview.page_count() == 0);
}

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

void SqlEditorPanel::query_finished() {
  logDebug2("Query successfully finished in editor %s\n", get_title().c_str());

  _busy = false;

  _form->set_busy_tab(-1);
  _lower_tabview.set_allows_reordering(true);

  _form->post_query_slot();
}

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

void SqlEditorPanel::query_failed(const std::string &message) {
  logError("Query execution failed in editor: %s. Error during query: %s\n", get_title().c_str(), message.c_str());
  _busy = false;

  _form->set_busy_tab(-1);
  _lower_tabview.set_allows_reordering(true);

  _form->post_query_slot();
}

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

// Resultset management.

SqlEditorResult *SqlEditorPanel::active_result_panel() {
  return result_panel(_lower_tabview.get_active_tab());
}

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

void SqlEditorPanel::lower_tab_switched() {
  _lower_dock.view_switched();

  db_query_QueryEditorRef qeditor(grtobj());
  SqlEditorResult *result = active_result_panel();
  Recordset::Ref rset;
  if (result && (rset = result->recordset())) {
    bool found = false;
    for (size_t c = qeditor->resultPanels().count(), i = 0; i < c; i++) {
      if (mforms_from_grt(qeditor->resultPanels()[i]->dockingPoint()) == result->dock()) {
        found = true;
        qeditor->activeResultPanel(qeditor->resultPanels()[i]);
        break;
      }
    }
    if (!found)
      qeditor->activeResultPanel(db_query_ResultPanelRef());

    bool readonly = rset->is_readonly();
    _tab_action_apply.show(!readonly);
    _tab_action_revert.show(!readonly);
    _tab_action_icon.show(readonly);
    _tab_action_info.show(readonly);
    bool edited = result->has_pending_changes();
    _tab_action_apply.set_enabled(edited);
    _tab_action_revert.set_enabled(edited);
    if (readonly) {
      _tab_action_info.set_tooltip(rset->readonly_reason());
      _tab_action_icon.set_tooltip(rset->readonly_reason());
    }
  } else {
    qeditor->activeResultPanel(db_query_ResultPanelRef());

    _tab_action_apply.show(true);
    _tab_action_revert.show(true);
    _tab_action_icon.show(false);
    _tab_action_info.show(false);

    _tab_action_apply.set_enabled(false);
    _tab_action_revert.set_enabled(false);
  }

#ifdef _MSC_VER
  _editor->focus();
#endif

  mforms::MenuBar *menu;
  if ((menu = _form->get_menubar())) {
    Recordset::Ref rset(result ? result->recordset() : Recordset::Ref());

    menu->set_item_enabled("query.save_edits", rset && rset->has_pending_changes());
    menu->set_item_enabled("query.discard_edits", rset && rset->has_pending_changes());
    menu->set_item_enabled("query.export", (bool)rset);
  }

  // if a lower tab view selection has changed, we make sure it's visible
  if (!_busy && _lower_tabview.page_count() > 0) // if we're running a query, then let dock_result handle this
  {
    int position = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ResultSplitterPosition", 200);
    if (position > _splitter.get_height() - 100)
      position = _splitter.get_height() - 100;
    _splitter.set_divider_position(position);
  }
}

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

void SqlEditorPanel::on_recordset_context_menu_show(Recordset::Ptr rs_ptr) {
  Recordset::Ref rs(rs_ptr.lock());
  if (rs) {
    grt::DictRef info(true);

    std::vector<int> selection(rs->selected_rows());
    grt::IntegerListRef rows(grt::Initialized);
    for (std::vector<int>::const_iterator i = selection.begin(); i != selection.end(); ++i)
      rows.insert(*i);

    info.set("selected-rows", rows);
    info.gset("selected-column", rs->selected_column());
    info.set("menu", mforms_to_grt(rs->get_context_menu()));

    db_query_QueryBufferRef qbuffer(grtobj());
    if (qbuffer.is_valid() && db_query_QueryEditorRef::can_wrap(qbuffer)) {
      db_query_QueryEditorRef qeditor(db_query_QueryEditorRef::cast_from(qbuffer));
      for (size_t c = qeditor->resultPanels().count(), i = 0; i < c; i++) {
        db_query_ResultsetRef rset(qeditor->resultPanels()[i]->resultset());

        if (rset.is_valid() && dynamic_cast<WBRecordsetResultset *>(rset->get_data())->recordset == rs) {
          grt::GRTNotificationCenter::get()->send_grt("GRNSQLResultsetMenuWillShow", rset, info);
          break;
        }
      }
    }
  }
}

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

/**
 *	Returns the number of all docked result panels in the editor panel.
 */
size_t SqlEditorPanel::result_panel_count() {
  return _lower_tabview.page_count();
}

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

/**
*	Returns the number of all docked resultset panels in the editor panel.
*	That excludes all the explain, spatial etc. panels.
*/
size_t SqlEditorPanel::resultset_count() {
  return grtobj()->resultPanels().count();
}

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

SqlEditorResult *SqlEditorPanel::result_panel(int i) {
  if (i >= 0 && i < _lower_tabview.page_count())
    return dynamic_cast<SqlEditorResult *>(_lower_tabview.get_page(i));
  return NULL;
}

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

void SqlEditorPanel::add_panel_for_recordset_from_main(Recordset::Ref rset) {
  if (bec::GRTManager::get()->in_main_thread()) {
    SqlEditorForm::RecordsetData *rdata = dynamic_cast<SqlEditorForm::RecordsetData *>(rset->client_data());

    rdata->result_panel = add_panel_for_recordset(rset);
  } else
    bec::GRTManager::get()->run_once_when_idle(
      dynamic_cast<bec::UIForm *>(this), std::bind(&SqlEditorPanel::add_panel_for_recordset_from_main, this, rset));
}

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

SqlEditorResult *SqlEditorPanel::add_panel_for_recordset(Recordset::Ref rset) {
  SqlEditorResult *result = mforms::manage(new SqlEditorResult(this));
  if (rset)
    result->set_recordset(rset);
  dock_result_panel(result);

  return result;
}

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

void SqlEditorPanel::dock_result_panel(SqlEditorResult *result) {
  result->grtobj()->owner(grtobj());
  grtobj()->resultPanels().insert(result->grtobj());

  if (Recordset::Ref rset = result->recordset())
    result->set_title(rset->caption());

  _lower_dock.dock_view(result);
  _lower_dock.select_view(result);
  _splitter.set_expanded(false, true);
  if (_was_empty) {
    int position = (int)bec::GRTManager::get()->get_app_option_int("DbSqlEditor:ResultSplitterPosition", 200);
    if (position > _splitter.get_height() - 100)
      position = _splitter.get_height() - 100;
    _splitter.set_divider_position(position);

    // scroll the editor to make the cursor visible and keep the selection, if available
    std::size_t selection_start = 0;
    std::size_t selection_length = 0;
    _editor->get_editor_control()->get_selection(selection_start, selection_length);
    _editor->get_editor_control()->set_caret_pos(_editor->get_editor_control()->get_caret_pos());
    _editor->get_editor_control()->set_selection(selection_start, selection_length);
  }
}

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

void SqlEditorPanel::lower_tab_reordered(mforms::View *view, int from, int to) {
  if (from == to || dynamic_cast<SqlEditorResult *>(view) == NULL)
    return;

  // not all tabs will have a SqlEditorResult
  // so the reordering gets more complicated, because actual reordering only happens if the relative
  // position between the result objects changes...
  // relative result object order changes always mean that a tab was reordered, but the other way around is
  // not always true

  size_t from_index = grtobj()->resultPanels().get_index(dynamic_cast<SqlEditorResult *>(view)->grtobj());
  if (from_index == grt::BaseListRef::npos) {
    logFatal("Result panel is not in resultPanels() list\n");
    return;
  }

  // first build an array of result panel objects, in the same order as the tabview
  std::vector<std::pair<db_query_ResultPanelRef, int> > panels;
  for (int result_order = 0, i = 0; i < _lower_tabview.page_count(); i++) {
    SqlEditorResult *p = result_panel(i);
    if (p)
      panels.push_back(std::make_pair(p->grtobj(), result_order++));
    else
      panels.push_back(std::make_pair(db_query_ResultPanelRef(), 0));
  }

  int to_index = -1;
  // now find out where we have to move to
  if (from < to) {
    for (int i = to; i > from; i--) {
      if (panels[i].first.is_valid()) {
        to_index = panels[i].second;
        break;
      }
    }
  } else {
    for (int i = to; i < from; i++) {
      if (panels[i].first.is_valid()) {
        to_index = panels[i].second;
        break;
      }
    }
  }

  if (to_index < 0) {
    to_index = panels.back().second;
  }

  grtobj()->resultPanels()->reorder(from_index, to_index);
}

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

bool SqlEditorPanel::lower_tab_closing(int tab) {
  mforms::AppView *view = _lower_dock.view_at_index(tab);
  if (view) {
    if (view->on_close()) {
      view->close();
      result_removed();
      return true;
    }
    return false;
  }
  return true;
}

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

void SqlEditorPanel::lower_tab_closed(mforms::View *page, int tab) {
  SqlEditorResult *rpage = dynamic_cast<SqlEditorResult *>(page);
  if (rpage) {
    db_query_ResultPanelRef closed_panel(rpage->grtobj());
    grtobj()->resultPanels().remove_value(closed_panel);
    if (closed_panel->resultset().is_valid())
      closed_panel->resultset()->reset_references();
    closed_panel->reset_references();
  }
}

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

void SqlEditorPanel::result_removed() {
  if (_lower_tabview.page_count() == 0)
    _splitter.set_expanded(false, false);
  lower_tab_switched();
}

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

std::list<SqlEditorResult *> SqlEditorPanel::dirty_result_panels() {
  std::list<SqlEditorResult *> results;

  for (int c = _lower_tabview.page_count(), i = 0; i < c; i++) {
    SqlEditorResult *result = result_panel(i);
    if (result && result->has_pending_changes())
      results.push_back(result);
  }
  return results;
}

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

void SqlEditorPanel::tab_menu_will_show() {
  SqlEditorResult *result(result_panel(_lower_tabview.get_menu_tab()));

  _lower_tab_menu.set_item_enabled("rename", result != NULL);
  _lower_tab_menu.set_item_enabled("pin", result != NULL);
  _lower_tab_menu.set_item_checked("pin", result && result->pinned());

  if (_lower_tabview.page_count() > 1)
    _lower_tab_menu.set_item_enabled("close_others", true); // close others
  else
    _lower_tab_menu.set_item_enabled("close_others", false); // close others
}

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

void SqlEditorPanel::rename_tab_clicked() {
  int tab = _lower_tabview.get_menu_tab();
  SqlEditorResult *result = result_panel(tab);
  if (result) {
    std::string title;
    if (mforms::Utilities::request_input(_("Rename Result Tab"), "Enter a new name for the result tab:",
                                         result->caption().c_str(), title))
      _lower_tabview.set_tab_title(tab, title);
  }
}

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

void SqlEditorPanel::pin_tab_clicked() {
  int tab = _lower_tabview.get_menu_tab();
  SqlEditorResult *result = result_panel(tab);
  if (result)
    result->set_pinned(!result->pinned());
}

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

bool SqlEditorPanel::is_pinned(int tab) {
  SqlEditorResult *result = result_panel(tab);
  if (result)
    return result->pinned();
  return false;
}

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

void SqlEditorPanel::tab_pinned(int tab, bool flag) {
  SqlEditorResult *result = result_panel(tab);
  if (result)
    result->set_pinned(flag);
}

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

void SqlEditorPanel::close_tab_clicked() {
  lower_tab_closing(_lower_tabview.get_menu_tab());
}

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

void SqlEditorPanel::close_other_tabs_clicked() {
  int tab = _lower_tabview.get_menu_tab();
  for (int i = _lower_tabview.page_count() - 1; i >= 0; --i) {
    if (i != tab)
      lower_tab_closing(i);
  }
}

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