
#include <gtk/gtk.h>
#include "sql_editor_fe.h"
#include "linux_utilities/gtk_helpers.h"
#include "gtk/lf_mforms.h"
#include "mforms/mforms.h"
#include "base/string_utilities.h"

 //------------------------------------------------------------------------------
SqlEditorFE::SqlEditorFE()
:
_container(false, 0),
_search_match_whole_word(false), _search_ignore_case(true), _search_wrap_around(true),
_errors_count(0), _dirty(false)
{
  _editor = scintilla_new();
  _sci    = SCINTILLA(_editor);
  _widget = Glib::wrap(_editor);
  
  _old_selection_start = 0;
  _old_selection_end   = 0;

  _container.pack_end(*_widget, true, true);

  _widget->set_data("SqlEditorFE", this);

  send_editor(SCI_USEPOPUP, false);
  
  send_editor(SCI_SETCODEPAGE, SC_CP_UTF8);

  send_editor(SCI_SETCARETSTICKY, true);
  send_editor(SCI_SETSCROLLWIDTHTRACKING, true);
  send_editor(SCI_SETSCROLLWIDTH, 800); 
  
  gtk_signal_connect(GTK_OBJECT(_editor), SCINTILLA_NOTIFY, GTK_SIGNAL_FUNC(&SqlEditorFE::notify_signal), this);
  _widget->signal_button_press_event().connect(sigc::mem_fun(this, &SqlEditorFE::on_button_press_event));

  _widget->show();
  _container.show();
}

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

SqlEditorFE* SqlEditorFE::from(Gtk::Widget *w)
{
  return reinterpret_cast<SqlEditorFE*>(w->get_data("SqlEditorFE"));
}


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

#if GTK_VERSION_GT(2, 16)
static void clear_text_clicked(Gtk::EntryIconPosition pos, const GdkEventButton*, Gtk::Entry *entry)
{
  if (pos == Gtk::ENTRY_ICON_SECONDARY)
    entry->set_text("");
}

static void text_changed(Gtk::Entry *entry)
{
  if (!entry->get_text().empty())
    entry->set_icon_from_stock(Gtk::Stock::CLEAR, Gtk::ENTRY_ICON_SECONDARY);
  else
    entry->set_icon_from_pixbuf(Glib::RefPtr<Gdk::Pixbuf>(), Gtk::ENTRY_ICON_SECONDARY);
}
#endif


static void toggle_bool(bool &b, Gtk::CheckMenuItem *item)
{
  b = item->get_active();
}


void SqlEditorFE::show_find_panel(bool show)
{
  if (!_be) return;
  if (!_find_panel)
  {
    if (show)
    {
      _find_panel = Gtk::Builder::create_from_file(_be->grtm()->get_data_file_path("embedded_find.glade"));
      Gtk::Container *box = 0;
      _find_panel->get_widget("container", box);
      _container.pack_start(*box, false, true);
      Gtk::Button *btn;
      _find_panel->get_widget("close_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::show_find_panel), false));

      _find_panel->get_widget("result_label", _find_status);

      Gtk::RadioButton *r;
      _find_panel->get_widget("find_radio", r);
      r->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::enable_replace_panel), false));
      _find_panel->get_widget("replace_radio", r);
      r->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::enable_replace_panel), true));

      _find_panel->get_widget("replace_all_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_replace), ReplaceAll));
      _find_panel->get_widget("replace_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_replace), Replace));
      _find_panel->get_widget("find_replace_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_replace), FindReplace));
      _find_panel->get_widget("next_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_find), true));
      _find_panel->get_widget("previous_button", btn);
      btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_find), false));

      _find_panel->get_widget("search_menu", _search_menu);
 
      _find_panel->get_widget("find_entry", _find_entry);
      _find_entry->signal_activate().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_find), true));
      _find_entry->signal_key_press_event().connect(sigc::mem_fun(this, &SqlEditorFE::on_find_key_press));
      _find_entry->signal_changed().connect(sigc::mem_fun(this, &SqlEditorFE::find_text_changed));
#if GTK_VERSION_GT(2, 16)
      _find_entry->signal_changed().connect(sigc::bind(sigc::ptr_fun(text_changed), _find_entry));
#endif
      _find_panel->get_widget("replace_entry", _replace_entry);
      _replace_entry->signal_activate().connect(sigc::bind(sigc::mem_fun(this, &SqlEditorFE::do_replace), FindReplace));
      _replace_entry->signal_key_press_event().connect(sigc::mem_fun(this, &SqlEditorFE::on_find_key_press));
#if GTK_VERSION_GT(2, 16)
      _replace_entry->signal_changed().connect(sigc::bind(sigc::ptr_fun(text_changed), _replace_entry));
#endif

#if GTK_VERSION_GT(2, 16)
      _find_entry->signal_icon_press().connect(sigc::bind(sigc::ptr_fun(clear_text_clicked), _find_entry));
      _find_entry->signal_icon_press().connect(sigc::mem_fun(this, &SqlEditorFE::find_icon_press));
      _replace_entry->signal_icon_press().connect(sigc::bind(sigc::ptr_fun(clear_text_clicked), _replace_entry));
#endif

      Gtk::MenuItem *mitem;
      _find_panel->get_widget("clear_item", mitem);
      mitem->signal_activate().connect(sigc::mem_fun(this, &SqlEditorFE::clear_search_history));
      mitem->set_sensitive(false);
      Gtk::CheckMenuItem *citem;
      _find_panel->get_widget("wrap_item", citem);
      citem->signal_activate().connect(sigc::bind(sigc::ptr_fun(toggle_bool), sigc::ref(_search_wrap_around), citem));
      _find_panel->get_widget("case_item", citem);
      citem->signal_activate().connect(sigc::bind(sigc::ptr_fun(toggle_bool), sigc::ref(_search_ignore_case), citem));
      _find_panel->get_widget("word_item", citem);
      citem->signal_activate().connect(sigc::bind(sigc::ptr_fun(toggle_bool), sigc::ref(_search_match_whole_word), citem));

      enable_replace_panel(false);

      box->show();
    }
  }
  else
  {
    Gtk::Container *box = 0;
    _find_panel->get_widget("container", box);
    if (show)
      box->show();
    else
      box->hide();
  }
  if (show)
  {
    _find_status->set_text("");
    _find_entry->grab_focus();
  }
  else
  {
    _widget->grab_focus();
  }
}


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

void SqlEditorFE::enable_replace_panel(bool flag)
{
  if (_find_panel)
  {
    Gtk::Box *box = 0;
    Gtk::Label *l;
    _find_panel->get_widget("replace_bbox", box);
    _find_panel->get_widget("label8", l);

    Gtk::RadioButton *radio;
    _find_panel->get_widget(flag?"replace_radio":"find_radio", radio);
    if (!radio->get_active())
    {
      Glib::RefPtr<Gtk::Builder> tmp = _find_panel; // little hack to prevent recursion
      _find_panel.clear();
      radio->set_active(true);
      _find_panel = tmp;
    }
    if (flag)
    {
      box->show();
      _replace_entry->show();
      l->show();
    }
    else
    {
      box->hide();
      _replace_entry->hide();
      l->hide();
    }
  } 
}


bool SqlEditorFE::on_find_key_press(GdkEventKey *key)
{
  if (key->keyval == GDK_Escape)
  {
    show_find_panel(false);
    return true;
  }
  return false;
}


void SqlEditorFE::find_icon_press(Gtk::EntryIconPosition pos, const GdkEventButton *ev)
{
  if (ev->button == 1 && pos == Gtk::ENTRY_ICON_PRIMARY)
  { 
    // update the menu
    Gtk::CheckMenuItem *mitem;
    _find_panel->get_widget("wrap_item", mitem);
    mitem->set_active(_search_wrap_around);
    _find_panel->get_widget("case_item", mitem);
    mitem->set_active(_search_ignore_case);
    _find_panel->get_widget("word_item", mitem);
    mitem->set_active(_search_match_whole_word);

    _search_menu->popup(ev->button, ev->time);
  }
}

void SqlEditorFE::find_text_changed()
{
  if (_find_status)
    _find_status->set_text("");
}


void SqlEditorFE::clear_search_history()
{
   if (_search_menu)
  {
    Gtk::MenuShell::MenuList items(_search_menu->items());
    while (items.size() > 7)
      items.remove(items[5]);
    items[items.size()-1].set_sensitive(false);
  }
}


void SqlEditorFE::add_search_history(const std::string &entry)
{
  if (_search_menu && !entry.empty())
  {
    Gtk::MenuShell::MenuList items(_search_menu->items());
    for (size_t c = items.size()-2, i = 5; i < c; i++)
    {
      std::string label = items[i].get_label();
      if (label == entry)
      {
        items.remove(items[i]);
        break;
      }
    }
    items[items.size()-1].set_sensitive(true);
    Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(entry));
    item->signal_activate().connect(sigc::bind(sigc::mem_fun(_find_entry, &Gtk::Entry::set_text), entry));
    _search_menu->insert(*item, 5);
    item->show();
  }
}

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

void SqlEditorFE::do_find(bool forward)
{
  std::string text = _find_entry->get_text();
  add_search_history(text);
  switch (find_text(text, false, false, forward))
  {
    case FoundMatch:
      _find_status->set_text("Found match");
      break;
    case NoMatches:
      _find_status->set_text("No matches found");
      break;
    case NoMoreMatches:
      _find_status->set_text("No more matches");
      break;
    case WrappedAround:
      _find_status->set_text("Wrapped to top of file");
      break;
  }
}


void SqlEditorFE::do_replace(ReplaceType type)
{
  switch (type)
  {
    case ReplaceAll:
      {
        std::string search_text = _find_entry->get_text();
        std::string replace_text = _replace_entry->get_text();
        //int searchFlags= 0;
        //  searchFlags |= SCFIND_MATCHCASE;
        //  searchFlags |= SCFIND_WHOLEWORD;
        //send_editor(SCI_SETSEARCHFLAGS, searchFlags);
        send_editor(SCI_SETTARGETSTART, 0);
        send_editor(SCI_SETTARGETEND, send_editor(SCI_GETTEXTLENGTH));

        sptr_t result;
        int replaceCount = 0;
        while (true)
        {
          result = send_editor(SCI_SEARCHINTARGET, search_text.size(), (sptr_t)search_text.data());
          if (result < 0)
            break;

          replaceCount++;
          result = send_editor(SCI_REPLACETARGET, replace_text.size(), (sptr_t)replace_text.data());

          // The replacement changes the target range to the replaced text. Continue after that til the end.
          // The text length might be changed by the replacement so make sure the target end is the actual
          // text end.
          send_editor(SCI_SETTARGETSTART, send_editor(SCI_GETTARGETEND));
          send_editor(SCI_SETTARGETEND, send_editor(SCI_GETTEXTLENGTH));
        }
      }
      break;
    case Replace:
      replace_selected_text(_replace_entry->get_text());
      break;
    case FindReplace:
      if (find_text(_find_entry->get_text(), false, false, true) != NoMatches)
        replace_selected_text(_replace_entry->get_text());
      break;
  }
}

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

void SqlEditorFE::set_focus()
{
  send_editor(SCI_GRABFOCUS);
  send_editor(SCI_SETFOCUS, true);
  _widget->activate();
  _widget->grab_focus();
}

//------------------------------------------------------------------------------
void SqlEditorFE::notify_signal(GtkWidget *w, gint wParam, gpointer lParam, SqlEditorFE *editor)
{
  SCNotification *event = reinterpret_cast<SCNotification *>(lParam);
  editor->notify(event);
}

void SqlEditorFE::notify(SCNotification *event)
{
  switch (event->nmhdr.code)
  {
    case SCN_MODIFIED:
      {
      const int mod_type = event->modificationType;
      if (mod_type & SC_MOD_INSERTTEXT || mod_type & SC_MOD_DELETETEXT)
      {
        set_dirty(true);
        
        _background_action_timer_conn.disconnect();
        _background_action_timer_conn= Glib::signal_timeout().connect(
          sigc::mem_fun(this, &SqlEditorFE::on_background_action_timer),
          2000); //! get value from wb options
        _text_changed_signal.emit();

        if (_be)
          _be->sql(get_text());
      }
      if (mod_type & SC_MOD_CHANGEFOLD)
        fold_changed(event->line, event->foldLevelNow, event->foldLevelPrev);
      }
      break;

    case SCN_MARGINCLICK:
      {
      if (event->margin == 2)
        margin_click(event->position, event->modifiers);
      }
      break;

    case SCN_UPDATEUI:
      {
        int start = send_editor(SCI_GETSELECTIONSTART);
        int end = send_editor(SCI_GETSELECTIONEND);
        
        if (start != _old_selection_start || end != _old_selection_end)
        {
          if (_be)
            _be->set_selected_range(start, end);
          _selection_changed_signal.emit();
        }

        if (_be)
        {
          int pos= send_editor(SCI_GETCURRENTPOS);
          _be->set_cursor_pos(pos);
        }

        _old_selection_start= start;
        _old_selection_end= end;
      }
      break;
  }
}

//------------------------------------------------------------------------------
Gtk::Widget& SqlEditorFE::widget()
{
  return *_widget;
}


Gtk::Container& SqlEditorFE::container()
{
  return _container;
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_text(const std::string& text)
{
  if (_be)
  {
    std::string eol= _be->eol();
    int sci_eol= SC_EOL_LF;
    if ("\n" == eol)
      sci_eol= SC_EOL_CRLF;
    else if ("\r" == eol)
      sci_eol= SC_EOL_CR;
    else if ("\r\n" == eol)
      sci_eol= SC_EOL_CRLF;
    send_editor(SCI_SETEOLMODE, sci_eol);
    //send_editor(SCI_CONVERTEOLS, sci_eol);
    //send_editor(SCI_SETVIEWEOL, 1);
  }
  const int pos = send_editor(SCI_GETCURRENTPOS);
  send_editor(SCI_SETTEXT, 0, (sptr_t)text.c_str());
  send_editor(SCI_GOTOPOS, pos);
  send_editor(SCI_SCROLLCARET);

  check_sql(false);
}

//------------------------------------------------------------------------------
std::string SqlEditorFE::get_text()
{
  char *buffer(0);
  const int length = send_editor(SCI_GETLENGTH);
  if ( length > 0 )
  {
    buffer = new char[length+1];
    try
    {
      send_editor(SCI_GETTEXT, length+1, (sptr_t)buffer);
      send_editor(SCI_SETSAVEPOINT);
    }
    catch (...)
    {
      delete[] buffer;
      buffer = 0;
    }
  }

  std::string ret = buffer ? buffer : "";
  delete[] buffer;
  return ret;
}

//------------------------------------------------------------------------------
std::string SqlEditorFE::get_selected_text()
{
  int start= send_editor(SCI_GETSELECTIONSTART);
  int end= send_editor(SCI_GETSELECTIONEND);
  std::string text= get_text();
  return text.substr(start, end-start);
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_font(const std::string &font)
{
  std::string name;
  int size;
  bool bold, italic;
  if (!font.empty() && base::parse_font_description(font, name, size, bold, italic))
  {
    // scintilla requires the ! in front of the font name to interpret it as a pango/fontconfig font
    // the non-pango version is totally unusable
    if (!name.empty() && name[0] != '!')
      name = "!"+name;
    for (int i = 0; i < 128; i++)
    {
      send_editor(SCI_STYLESETFONT, i, (sptr_t)name.c_str());
      send_editor(SCI_STYLESETSIZE, i, size);
      send_editor(SCI_STYLESETBOLD, i, bold);
      send_editor(SCI_STYLESETITALIC, i, italic);
    }
  }
}

//------------------------------------------------------------------------------
void SqlEditorFE::set_savepoint()
{
    send_editor(SCI_SETSAVEPOINT);
}

//------------------------------------------------------------------------------
bool SqlEditorFE::get_modify()
{
    return (bool)send_editor(SCI_GETMODIFY);
}

//------------------------------------------------------------------------------
void SqlEditorFE::scroll_to(const int line, const std::string& msg)
{
  send_editor(SCI_GOTOLINE, line);
}

//------------------------------------------------------------------------------
void SqlEditorFE::be(Sql_editor::Ref be)
{
  _be= be;
  if (_be)
  {
    _be->report_sql_statement_border= sigc::mem_fun(this, &SqlEditorFE::process_sql_statement_border);
    _be->sql_parser_err_cb(sigc::mem_fun(this, &SqlEditorFE::process_sql_error));//!
    _be->insert_text_slot= sigc::bind_return(sigc::mem_fun(this, &SqlEditorFE::insert_text), 0);
    _be->replace_selected_text_slot= sigc::bind_return(sigc::mem_fun(this, &SqlEditorFE::replace_selected_text), 0);
    _be->change_selected_range_slot= sigc::mem_fun(this, &SqlEditorFE::change_selected_range);
    _be->change_cursor_pos_slot= sigc::mem_fun(this, &SqlEditorFE::change_cursor_pos);
    _be->current_statement_slot= sigc::mem_fun(this, &SqlEditorFE::current_sql_statement);

    set_font(grt::StringRef::cast_from(be->grtm()->get_app_option("workbench.general.Editor:Font")));
    
    _be->setup_scintilla_editor(boost::bind(&SqlEditorFE::send_editor, this, _1, _2, _3));
  }
  return;
}

void SqlEditorFE::change_selected_range(int start, int end)
{
  send_editor(SCI_SETSELECTIONSTART, start);
  send_editor(SCI_SETSELECTIONEND, end);
}

void SqlEditorFE::change_cursor_pos(int pos)
{
  send_editor(SCI_GOTOPOS, pos);
}

bool SqlEditorFE::on_background_action_timer()
{
  _background_action_cb();
  return false;
}

void SqlEditorFE::check_sql(bool sync)
{
  reset_sql_check_state();
  if (_be)
  {
    _be->sql(get_text());
    _be->check_sql(sync);
  }
}

int SqlEditorFE::reset_sql_check_state()
{
  _errors_count = 0;
  int length= send_editor(SCI_GETLENGTH);

  send_editor(SCI_SETINDICATORCURRENT, 0);
  send_editor(SCI_INDICATORCLEARRANGE, 0, length);

 // send_editor(SCI_SETINDICATORCURRENT, 1);
//  send_editor(SCI_INDICATORCLEARRANGE, 0, length);

  send_editor(SCI_MARKERDELETEALL, -1);

  return 0;
}

int SqlEditorFE::process_sql_error(const int err_tok_line, const int err_tok_line_pos, const int err_tok_len, const std::string &err_msg)
{
  int line_start_pos= send_editor(SCI_POSITIONFROMLINE, err_tok_line-1);

  send_editor(SCI_SETINDICATORCURRENT, 0);
  send_editor(SCI_INDICATORFILLRANGE, line_start_pos+err_tok_line_pos, err_tok_len);

//  send_editor(SCI_SETINDICATORCURRENT, 1);
//  send_editor(SCI_INDICATORFILLRANGE, line_start_pos+err_tok_line_pos, err_tok_len);

  send_editor(SCI_MARKERADD, err_tok_line-1, 1);

  ++_errors_count;

  return 0;
}

int SqlEditorFE::process_sql_statement_border(int begin_lineno, int begin_line_pos, int end_lineno, int end_line_pos)
{
  send_editor(SCI_MARKERADD, begin_lineno-1, 0);
  return 0;
}

//------------------------------------------------------------------------------
std::string SqlEditorFE::current_sql_statement()
{
  int current_pos= send_editor(SCI_GETCURRENTPOS);
  int current_line= send_editor(SCI_LINEFROMPOSITION, current_pos);
  int current_line_beginning_pos= send_editor(SCI_POSITIONFROMLINE, current_line);
  int current_line_pos= current_pos - current_line_beginning_pos;

  Sql_editor::SqlStatementBorder sql_statement_border= _be->get_sql_statement_border_by_line_pos(current_line+1, current_line_pos);
  if (sql_statement_border.begin_lineno == -1)
    return "";
  CharacterRange cr;
  cr.cpMin= send_editor(SCI_POSITIONFROMLINE, sql_statement_border.begin_lineno-1);
  cr.cpMin+= sql_statement_border.begin_line_pos;
  cr.cpMax= send_editor(SCI_POSITIONFROMLINE, sql_statement_border.end_lineno-1);
  cr.cpMax+= sql_statement_border.end_line_pos;
  int doc_length= send_editor(SCI_GETLENGTH);
  if (cr.cpMax > doc_length)
    cr.cpMax= doc_length;

  TextRange tr;
  tr.chrg= cr;
  tr.lpstrText= new char[cr.cpMax - cr.cpMin + 1];
  send_editor(SCI_GETTEXTRANGE, 0, (sptr_t)&tr);
  std::string sql(tr.lpstrText);
  delete [] tr.lpstrText;

  return sql;
}

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

void SqlEditorFE::show_special_chars(bool flag)
{
  send_editor(SCI_SETVIEWWS, flag ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE);
  send_editor(SCI_SETVIEWEOL, flag, 0);
}

//------------------------------------------------------------------------------
sptr_t SqlEditorFE::send_editor(unsigned int msg, uptr_t uparam, sptr_t sparam)
{
  return scintilla_send_message(_sci, msg, uparam, sparam);
}


//------------------------------------------------------------------------------
bool SqlEditorFE::margin_click(int position, int modifiers)
{
  int lineClick = send_editor(SCI_LINEFROMPOSITION, position);
  //    send_editor(SCI_GETFOLDLEVEL, lineClick) & SC_FOLDLEVELHEADERFLAG);
  if (modifiers & SCMOD_SHIFT)
    fold_close_all();
  else if (modifiers & SCMOD_CTRL)
    fold_open_all();
  else if (send_editor(SCI_GETFOLDLEVEL, lineClick) & SC_FOLDLEVELHEADERFLAG)
  {
    if (modifiers & SCMOD_SHIFT)
    {
      // Ensure all children visible
      send_editor(SCI_SETFOLDEXPANDED, lineClick, 1);
      expand(lineClick, true, true, 100);
    }
    else if (modifiers & SCMOD_CTRL)
    {
      if (send_editor(SCI_GETFOLDEXPANDED, lineClick))
      {
        // Contract this line and all children
        send_editor(SCI_SETFOLDEXPANDED, lineClick, 0);
        expand(lineClick, false, true, 0);
      }
      else
      {
        // Expand this line and all children
        send_editor(SCI_SETFOLDEXPANDED, lineClick, 1);
        expand(lineClick, true, true, 100);
      }
    }
    else
    {
      // Toggle this line
      send_editor(SCI_TOGGLEFOLD, lineClick);
    }
  }
  return true;
}

void SqlEditorFE::fold_changed(int line, int levelNow, int levelPrev)
{
  if (levelNow & SC_FOLDLEVELHEADERFLAG)
    send_editor(SCI_SETFOLDEXPANDED, line, 1);
  else if (levelPrev & SC_FOLDLEVELHEADERFLAG)
  {
    if (!send_editor(SCI_GETFOLDEXPANDED, line))
    {
      // Removing the fold from one that has been contracted so should expand
      // otherwise lines are left invisible with no way to make them visible
      expand(line, true, false, 0, levelPrev);
    }
  }
}

void SqlEditorFE::expand(int &line, bool doExpand, bool force, int visLevels, int level)
{
  int lineMaxSubord = send_editor(SCI_GETLASTCHILD, line, level);
  line++;
  while (line <= lineMaxSubord)
  {
    if (force)
    {
      if (visLevels > 0)
        send_editor(SCI_SHOWLINES, line, line);
      else
        send_editor(SCI_HIDELINES, line, line);
    }
    else if (doExpand)
        send_editor(SCI_SHOWLINES, line, line);

    int levelLine = level;
    if (levelLine ==-1)
      levelLine = send_editor(SCI_GETFOLDLEVEL, line);
    if (levelLine & SC_FOLDLEVELHEADERFLAG)
    {
      if (force)
      {
        if (visLevels > 1)
          send_editor(SCI_SETFOLDEXPANDED, line, 1);
        else
          send_editor(SCI_SETFOLDEXPANDED, line, 0);
        expand(line, doExpand, force, visLevels - 1);
      }
      else
      {
        if (doExpand && send_editor(SCI_GETFOLDEXPANDED, line))
          expand(line, true, force, visLevels - 1);
        else
          expand(line, false, force, visLevels - 1);
      }
    }
    else
      line++;
  }
}

void SqlEditorFE::fold_code(bool expanding)
{
  int maxLine = send_editor (SCI_GETTEXTLENGTH);
  send_editor(SCI_COLOURISE, 0, -1);
  for (int line = 0; line < maxLine; line++)
  {
    int level = send_editor(SCI_GETFOLDLEVEL, line);
    if ((level & SC_FOLDLEVELHEADERFLAG) && (SC_FOLDLEVELBASE == (level & SC_FOLDLEVELNUMBERMASK)))
    {
      if (expanding)
      {
        send_editor(SCI_SETFOLDEXPANDED, line, 1);
        expand(line, true);
        line--;
      }
      else
      {
        int lineMaxSubord = send_editor(SCI_GETLASTCHILD, line, -1);
        send_editor(SCI_SETFOLDEXPANDED, line, 0);
        if (lineMaxSubord > line)
          send_editor(SCI_HIDELINES, line + 1, lineMaxSubord);
      }
    }
  }
}

void SqlEditorFE::fold_open_all()
{
  fold_code (true);
}

void SqlEditorFE::fold_close_all()
{
  fold_code (false);
}

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

void SqlEditorFE::insert_text(const std::string &text)
{
  if (has_selection())
    send_editor(SCI_REPLACESEL, 0, (sptr_t)text.c_str());
  else
  {
    int end= send_editor(SCI_GETSELECTIONEND);
    send_editor(SCI_INSERTTEXT, end, (sptr_t)text.c_str());
  }
  gtk_widget_grab_focus(_editor);
}

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

void SqlEditorFE::replace_selected_text(const std::string &text)
{
  int selection_start = send_editor(SCI_GETSELECTIONSTART);
  insert_text(text);
  send_editor(SCI_SETSELECTIONSTART, selection_start);
  send_editor(SCI_SETSELECTIONEND, selection_start + text.length());
}

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

void SqlEditorFE::set_dirty(bool flag)
{
  _dirty= flag;
}

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

void SqlEditorFE::copy()
{
  send_editor(SCI_COPY);
}


void SqlEditorFE::delete_()
{
  send_editor(SCI_DELETEBACK);
}
  
  
void SqlEditorFE::paste()
{
  send_editor(SCI_PASTE);
}


bool SqlEditorFE::has_selection()
{
  int start = send_editor(SCI_GETSELECTIONSTART);
  int end = send_editor(SCI_GETSELECTIONEND);

  return start >= 0 && start < end;
}


bool SqlEditorFE::is_editable()
{
  return true;
}

void SqlEditorFE::select_all()
{
  send_editor(SCI_SELECTALL);
}

SqlEditorFE::FindResult SqlEditorFE::find_text(const std::string &text, bool match_case, bool match_whole_word, bool forward)
{
  bool wrap= true;
  bool wrapped = false;
  int searchFlags= 0;
  if (match_case)
    searchFlags |= SCFIND_MATCHCASE;
  if (match_whole_word)
    searchFlags |= SCFIND_WHOLEWORD;

  int selectionStart = send_editor(SCI_GETSELECTIONSTART);
  int selectionEnd = send_editor(SCI_GETSELECTIONEND);
  
  // Sets the start point for the comming search to the begin of the current selection.
  // For forward searches we have therefore to set the selection start to the current selection end
  // for proper incremental search. This does not harm as we either get a new selection if something
  // is found or the previous selection is restored.
  if (forward)
    send_editor(SCI_SETSELECTIONSTART, selectionEnd);
  send_editor(SCI_SEARCHANCHOR, 0);
  sptr_t result;

  // The following call will also set the selection if something was found.
  if (!forward)
  {
    result = send_editor(SCI_SEARCHPREV, searchFlags, (sptr_t)text.c_str());
    if (result < 0 && wrap)
    {
      // Try again from the end of the document if nothing could be found so far and
      // wrapped search is set.
      send_editor(SCI_SETSELECTIONSTART, send_editor(SCI_GETTEXTLENGTH));
      send_editor(SCI_SEARCHANCHOR, 0);
      result = send_editor(SCI_SEARCHNEXT, searchFlags, (sptr_t)text.c_str());
    }
  }
  else
  {
    result = send_editor(SCI_SEARCHNEXT, searchFlags, (sptr_t)text.c_str());
    if (result < 0 && wrap)
    {
      // Try again from the start of the document if nothing could be found so far and
      // wrapped search is set.
      send_editor(SCI_SETSELECTIONSTART, 0);
      send_editor(SCI_SEARCHANCHOR, 0);
      result = send_editor(SCI_SEARCHNEXT, searchFlags, (sptr_t)text.c_str());
      wrapped = true;
    }
  }

  if (result >= 0)
  {
    send_editor(SCI_SCROLLCARET);
  }
  else
  {
    // Restore the former selection if we did not found anything.
    send_editor(SCI_SETSELECTIONSTART, selectionStart);
    send_editor(SCI_SETSELECTIONEND, selectionEnd);
  }
  return (result >= 0) ? (wrapped ? WrappedAround : FoundMatch) : NoMatches;
}


bool SqlEditorFE::can_undo()
{
  return send_editor(SCI_CANUNDO);
}

  
bool SqlEditorFE::can_redo()
{
  return send_editor(SCI_CANREDO);
}


void SqlEditorFE::undo()
{
  send_editor(SCI_UNDO);
}



void SqlEditorFE::redo()
{
  send_editor(SCI_REDO);
}


void SqlEditorFE::toggle_wrap_lines()
{
  const int wrap = (int)send_editor(SCI_GETWRAPMODE);
  send_editor(SCI_SETWRAPMODE, wrap == 0 ? 1 : 0);
}


bool SqlEditorFE::on_button_press_event(GdkEventButton *event)
{
  if (event->button == 3 && _be) // right button
  {
    bec::MenuItemList items= _be->get_context_menu();
    if (!items.empty())
    {      
      for (bec::MenuItemList::iterator iter= items.begin(); iter != items.end(); ++iter)
      {        
        bool enabled= iter->enabled;
        if (iter->name == "undo")
          enabled= can_undo();
        else if (iter->name == "redo")
          enabled= can_redo();
        else if (iter->name == "cut")
          enabled= has_selection() && is_editable();
        else if (iter->name == "copy")
          enabled= has_selection();
        else if (iter->name == "paste")
          enabled= is_editable();
        else if (iter->name == "toggle_wrap_lines")
          iter->checked = (bool)send_editor(SCI_GETWRAPMODE);

        iter->enabled= enabled;
      }

      run_popup_menu(items, event->time, sigc::mem_fun(this, &SqlEditorFE::activate_menu_action), &_context_menu);
    }
    return true;
  }
  else
    return false;
}

void SqlEditorFE::activate_menu_action(const std::string &action)
{
  if (action == "undo")
    undo();
  else if (action == "redo")
    redo();
  else if (action == "copy")
    copy();
  else if (action == "cut")
  {
    if (has_selection())
    {
      copy();
      delete_();
    }
  }
  else if (action == "paste")
    paste();
  else if (action == "delete")
    delete_();
  else if (action == "select_all")
    select_all();
  else if (action == "toggle_wrap_lines")
    toggle_wrap_lines();
  else
  {
    try
    {
      _be->activate_context_menu_item(action);
    }
    catch (const std::exception &exc)
    {
      mforms::Utilities::show_error("Plugin Error",
                                    base::strfmt("Could not execute %s: %s", action.c_str(),
                                                 exc.what()),
                                   _("OK"), "", "");
    }
  }
   
}
