/* 
 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"
#include "grt_shell_window.h"
#include "base/file_functions.h"
#include "base/string_utilities.h"
#include "base/wb_iterators.h"
#include <errno.h>

#include "grt/common.h"
#include "grt_code_editor.h"
#include "grt_plugin_wizard.h"
#include "grt_python_debugger.h"

#include "wb_context.h"
#include "mforms/app.h"
#include "mforms/imagebox.h"

using namespace MySQL::Geometry;


#define EDITOR_TAB_OFFSET 2

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

GRTShellWindow::GRTShellWindow(wb::WBContext* context)
: mforms::Form(mforms::Form::main_form(), (mforms::FormFlag)(mforms::FormResizable | mforms::FormMinimizable | mforms::FormHideOnClose)), 
  _context(context),
  _toolbar(true),
  _content(false), _padding_box(false), _hsplitter(true), _side_tab(mforms::TabViewPalette),
  _main_tab(mforms::TabViewDocumentClosable),
  _global_box1(false), _global_box2(false), _global_splitter(false), _global_combo(mforms::SelectorCombobox),
  _global_tree(mforms::TreeDefault), _global_list(mforms::TreeDefault), 
#ifdef _WIN32
  _side_header_panel(mforms::FilledHeaderPanel),
  _lower_tab(mforms::TabViewPalette),
  _lower_header_panel(mforms::FilledHeaderPanel),
#else
  _lower_tab(mforms::TabViewDocument),
#endif
  _classes_box(false), _classes_splitter(false), _classes_tree(mforms::TreeNoBorder),
  _classes_text(mforms::VerticalScrollBar),
  _modules_splitter(false), _modules_tree(mforms::TreeNoBorder), _modules_text(mforms::VerticalScrollBar),
  _right_splitter(false), 
  _shell_box(false), _shell_text(mforms::VerticalScrollBar), _shell_hbox(true),
  _output_text(mforms::VerticalScrollBar),
  _snippet_splitter(false), _snippet_page(false),
  _snippet_text()
{
  set_title(("Workbench Scripting Shell"));
  set_content(&_content);
  scoped_connect(signal_closed(),boost::bind(&GRTShellWindow::shell_closed, this));
  
  _content.add(&_toolbar, false, true);

#ifdef _WIN32
  _content.add(&_padding_box, true, true);
  _padding_box.add(&_hsplitter, true, true);

  _padding_box.set_padding(6);
  set_back_color("#283752");
  _hsplitter.set_back_color("#283752");
  _side_header_panel.add(&_side_tab);
  _side_header_panel.set_back_color("#283752");
  _hsplitter.add(&_side_header_panel);
#else
  _content.add(&_hsplitter, true, true);
  _hsplitter.add(&_side_tab);
#endif

  scoped_connect(_side_tab.signal_tab_changed(),boost::bind(&GRTShellWindow::side_tab_changed, this));

  _hsplitter.add(&_right_splitter);

  // Side bar consists of 4 pages: files, globals tree, classes tree and modules tree.
  _first_show= true;

  // Setup toolbar.
#ifdef _WIN32
  _toolbar.set_size(-1, 24);
  _toolbar.set_back_color("#BCC7D8");
#endif

  _toolbar.set_padding(2);
  _toolbar.set_spacing(4);

  add_tool_button("tiny_new_doc.png", boost::bind(&GRTShellWindow::add_new_script, this),
    _("Create a new file from a template"));
  add_tool_button("tiny_load.png", boost::bind(&GRTShellWindow::open_script_file, this),
    _("Open a script file"));

  add_tool_separator();
  _save_button = add_tool_button("tiny_save.png", boost::bind(&GRTShellWindow::save_file, this, false),
    _("Save file"));
  _save_as_button = add_tool_button("tiny_saveas.png", boost::bind(&GRTShellWindow::save_file, this, true),
    _("Save file with a new name"));

  add_tool_separator();
  //  if (_editing_module)
  {
    //   add_tool_button("tiny_refresh.png", boost::bind(&GRTCodeEditor::execute, this),
    //                   "Refresh the module");
  }
  //  else
  {
    _run_button = add_tool_button("tiny_execute.png", boost::bind(&GRTShellWindow::execute_file, this),
      _("Execute script"));
    _continue_button = add_tool_button("debug_continue.png", boost::bind(&GRTShellWindow::debug_continue, this), 
      _("Continue execution until next breakpoint"));
    _pause_button = add_tool_button("debug_pause.png", boost::bind(&GRTShellWindow::debug_pause, this),
                                    "Pause script execution");
    _step_button = add_tool_button("debug_step.png", boost::bind(&GRTShellWindow::debug_step, this), 
      _("Step to next statement"));
    _step_into_button = add_tool_button("debug_step_into.png", boost::bind(&GRTShellWindow::debug_step_into, this), 
      _("Step into next function"));
    _step_out_button = add_tool_button("debug_step_out.png", boost::bind(&GRTShellWindow::debug_step_out, this),
      _("Finish execution of current function"));
    _stop_button = add_tool_button("debug_stop.png", boost::bind(&GRTShellWindow::debug_stop, this),
      _("Stop executing script"));

    _continue_button->show(false);
    _step_button->set_enabled(false);
    _step_into_button->set_enabled(false);
    _step_out_button->set_enabled(false);
    _continue_button->set_enabled(false);
    _stop_button->set_enabled(false);
    _pause_button->set_enabled(false);
  }  

#ifndef _WIN32
  // TODO: remove as soon as all platforms support closable tabs.
  _close_script_tab_button = add_tool_button("Discard.png", boost::bind(&GRTShellWindow::close_tab, this),
    _("Close this script tab"), false);
#else
  _close_script_tab_button = NULL;
#endif
  add_tool_separator();

  _clear_script_output_button = add_tool_button("clear_output.png", boost::bind(&mforms::TextBox::set_value, &_output_text, ""), 
    _("Clear script output"), false);

  add_tool_button("snippet_add.png", boost::bind(&GRTShellWindow::add_snippet, this), 
    _("Add a new snippet"));
  _snippet_delete_button = add_tool_button("snippet_del.png", boost::bind(&GRTShellWindow::del_snippet, this), 
    _("Delete the selected snippet"));
  _snippet_copy_button = add_tool_button("snippet_clipboard.png", boost::bind(&GRTShellWindow::copy_snippet, this), 
    _("Copy snippet text to the clipboard"));
  add_tool_separator();

  // Files
  mforms::Box *files_box= mforms::manage(new mforms::Box(false));
  
#ifdef _WIN32
  _files_tree = mforms::manage(new mforms::TreeView(mforms::TreeNoBorder));
#else
  _files_tree = mforms::manage(new mforms::TreeView(mforms::TreeDefault));
#endif

  _files_menu.add_item("Add New File", "file-from-template");
  _files_menu.add_item("Open Script File", "open-script");
  _files_menu.add_separator();
  _files_menu.add_item("Delete Script", "delete-script");

  _files_menu.set_handler(boost::bind(&GRTShellWindow::file_menu_activate, this, _1));

  _files_tree->set_context_menu(&_files_menu);

  _files_tree->add_column(StringColumnType, "", 400, false);
  _files_tree->end_columns();

  scoped_connect(_files_tree->signal_row_activated(),boost::bind(&GRTShellWindow::file_list_activated, this, _1, _2));
  
  files_box->add(_files_tree, true, true);
#ifdef _WIN32
  files_box->set_back_color("#FFFFFF");
  files_box->set_padding(0, 0, 0, 2);
#endif
  _side_tab.add_page(files_box, "Files");
      
  // 1) Globals tree
  _global_tree_be= 0;
#ifdef _WIN32
  _global_splitter.set_back_color("#FFFFFF");
  _hsplitter.set_back_color("#283752");
  //_global_splitter.set_padding(0, 0, 0, 2); TODO: might require work around since we removed padding from View.
#endif
  _side_tab.add_page(&_global_splitter, "Globals");
  _global_splitter.add(&_global_box1, 0);
  _global_splitter.add(&_global_box2, 0);
#ifndef _WIN32
  _global_box1.set_spacing(4);
  _global_box2.set_spacing(4);
#endif
  _global_box1.add(&_global_combo, false, false);
  _global_box1.add(&_global_tree, true, true);

  _global_box2.add(&_global_entry, false, true);
  _global_entry.set_read_only(true);
  _global_entry.set_back_color("#FFFFFF");
  
  _global_box2.add(&_global_list, true, true);
  _global_list.add_column(mforms::IconStringGRTColumnType, 0, "Name");
  _global_list.add_column(mforms::StringGRTColumnType, 1, "Value");
  _global_list.set_column_width(0, 100);
  _global_list.set_column_width(1, 100);
  _global_tree.add_column(mforms::IconStringGRTColumnType, 0, "GRT Globals");
  _global_tree.set_column_width(0, 220);
  scoped_connect(_global_tree.signal_changed(),boost::bind(&GRTShellWindow::global_selected, this));
  scoped_connect(_global_combo.signal_changed(),boost::bind(&GRTShellWindow::change_global_path, this));
  _inspector= 0;

  _global_menu.add_item(_("Copy Value"), "copy_value");
  _global_menu.add_item(_("Copy Path"), "copy_path");
  _global_menu.add_item(_("Copy Path for Python"), "copy_path_py");
  _global_menu.set_handler(boost::bind(&GRTShellWindow::handle_global_menu, this, _1));
  
  _global_tree.set_context_menu(&_global_menu);
  // 2) Classes tree
#ifdef _WIN32
  _classes_splitter.set_back_color("#FFFFFF");
  //_classes_splitter.set_padding(0, 0, 0, 2); TODO: might require workaround.
#endif
  _side_tab.add_page(&_classes_splitter, "Classes");
  _classes_splitter.add(&_classes_box, 0);
  _classes_box.set_spacing(4);
  _classes_box.add(&_classes_sorting, false, true);
  _classes_box.add(&_classes_tree, true, true);

  _classes_splitter.add(&_classes_text, 0);
  _classes_text.set_read_only(true);
  _classes_text.set_back_color("#FFFFFF");
  _classes_tree.add_column(mforms::IconStringGRTColumnType, bec::StructsTreeBE::Name, "Name");
  _classes_tree.add_column(mforms::StringGRTColumnType, bec::StructsTreeBE::Type, "Type");
  _classes_tree.set_column_width(0, 150);
  scoped_connect(_classes_tree.signal_changed(),boost::bind(&GRTShellWindow::class_selected, this));
  _classes_sorting.add_item("Group by Name");
  _classes_sorting.add_item("Group by Hierarchy");
  _classes_sorting.add_item("Group by Package");
  scoped_connect(_classes_sorting.signal_changed(),boost::bind(&GRTShellWindow::change_class_sorting, this));
  
  // 3) Modules tree
#ifdef _WIN32
  _modules_splitter.set_back_color("#FFFFFF");
  //_modules_splitter.set_padding(0, 0, 0, 2); TODO: might require workaround.
#endif
  _side_tab.add_page(&_modules_splitter, "Modules");
  _modules_splitter.add(&_modules_tree, 0);
  _modules_splitter.add(&_modules_text, 0);
  _modules_text.set_read_only(true);
  _modules_text.set_back_color("#FFFFFF");
  _modules_tree.add_column(mforms::IconStringGRTColumnType, 0, "GRT Modules");
  _modules_tree.set_column_width(0, 220);
  scoped_connect(_modules_tree.signal_changed(),boost::bind(&GRTShellWindow::module_selected, this));
  
  _right_splitter.add(&_main_tab);

#ifdef _WIN32
  _right_splitter.set_back_color("#283752");
  _lower_header_panel.add(&_lower_tab);
  _right_splitter.add(&_lower_header_panel);
  _lower_header_panel.set_title(_("Debugging"));
  _lower_header_panel.set_back_color("#283752");
#else
  _right_splitter.add(&_lower_tab);
 #endif
 
  // setup shell
#ifdef _WIN32
  _snippet_list = mforms::manage(new TreeView(mforms::TreeNoBorder));
#else
  _snippet_list = mforms::manage(new TreeView(mforms::TreeDefault));
#endif

  _shell_box.add(&_shell_text, true, true);
  _shell_text.set_monospaced(true);
  _shell_text.set_read_only(true);
  _shell_text.set_front_color("#E0E0E0");
  _shell_text.set_back_color("#000000");
  _shell_text.set_padding(2);
  _shell_box.add(&_shell_hbox, false, true);
  _shell_hbox.add(&_shell_prompt, false, true);
  _shell_hbox.add(&_shell_entry, true, true);
  _main_tab.add_page(&_shell_box, "Shell");
  
  scoped_connect(_shell_entry.signal_action(),boost::bind(&GRTShellWindow::shell_action, this, _1));

  // snippets tab
#ifdef _WIN32
//  ((View*)&_snippet_page)->set_padding(Padding(0, 0, 0, 21));
  _snippet_page.set_back_color("#283752");
  _snippet_splitter.set_back_color("#283752");
#else
  _snippet_page.set_spacing(8);
  _snippet_page.set_padding(8);
#endif
  _snippet_page.add(&_snippet_splitter, true, true);

  //_snippet_box.set_spacing(8);
  _snippet_splitter.add(_snippet_list);
  _snippet_splitter.add(&_snippet_text);

  _snippet_menu.add_item("Execute Snippet", "execute");
  _snippet_menu.add_item("Send to Script Editor", "new_with_snippet");
  _snippet_menu.add_separator();
  _snippet_menu.add_item("Copy to Clipboard", "copy_clipboard");
  _snippet_menu.add_separator();
  _snippet_menu.add_item("Delete Snippet", "delete");

  _snippet_menu.set_handler(boost::bind(&GRTShellWindow::snippet_menu_activate, this, _1));

  _snippet_list->set_context_menu(&_snippet_menu);

  scoped_connect(_snippet_list->signal_changed(),boost::bind(&GRTShellWindow::snippet_selected, this));
  _snippet_text.set_language(LanguagePython);

  scoped_connect(_snippet_text.signal_changed(),boost::bind(&GRTShellWindow::snippet_changed, this, _1, _2));

  _snippet_list->add_column(mforms::StringColumnType, "Snippet", 500, false);
  _snippet_list->end_columns();
  _main_tab.add_page(&_snippet_page, "Snippets");

  scoped_connect(_main_tab.signal_tab_closing(),boost::bind(&GRTShellWindow::on_tab_closing, this, _1));
  scoped_connect(_main_tab.signal_tab_closed(),boost::bind(&GRTShellWindow::on_tab_closed, this, _1));
  scoped_connect(_main_tab.signal_tab_changed(),boost::bind(&GRTShellWindow::on_tab_changed, this));

  //
  
  _output_text.set_read_only(true);
  _output_text.set_monospaced(true);
  _lower_tab.add_page(&_output_text, "Output");
  
  _debugger = new PythonDebugger(this, &_lower_tab);
  
  try
  {
    _debugger->init_pdb();
  }
  catch (const std::exception)
  {
    add_output("Could not initialize the debugger\n");
    delete _debugger;
    _debugger= 0;
  }
  
  // Minimum size for the entire window.
  set_size(800, 600);
  _snippet_splitter.set_position(400);
  _hsplitter.set_position(250);
  _global_splitter.set_position(400);
  _modules_splitter.set_position(400);
  _classes_splitter.set_position(400);
  
  grtm()->get_shell()->set_ready_handler(boost::bind(&GRTShellWindow::handle_prompt, this, _1));
  grtm()->get_shell()->set_output_handler(boost::bind(&GRTShellWindow::handle_output, this, _1));

  on_tab_changed();
  snippet_selected();
  side_tab_changed();  
}

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

bec::GRTManager *GRTShellWindow::grtm()
{ 
  return _context->get_grt_manager(); 
}

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

void GRTShellWindow::shell_action(mforms::TextEntryAction action)
{ 
  switch (action)
  {
    case mforms::EntryActivate:
    {
      std::string command= _shell_entry.get_string_value();
      _shell_entry.set_value("");
      //  _completion->add_completion_text(command);
      command += '\n';
      grtm()->get_shell()->write(grtm()->get_grt()->get_shell()->get_prompt()+" "+command);
      grtm()->get_shell()->process_line_async(command);
      break;
    }
    case mforms::EntryKeyUp:
    {
      std::string line;
      if (grtm()->get_shell()->previous_history_line(_shell_entry.get_string_value(), line))
        _shell_entry.set_value(line);
      break;
    }
    case mforms::EntryCKeyUp:
      break;
    case mforms::EntryKeyDown:
    {
      std::string line;
      if (grtm()->get_shell()->next_history_line(line))
        _shell_entry.set_value(line);
      break;
    }      
    case mforms::EntryCKeyDown:
      break;
  }
}

void GRTShellWindow::show(bool flag)
{
  if (flag)
    refresh_all();
  
  load_state();
  mforms::Form::show(flag);
}


void GRTShellWindow::refresh_all()
{
  if (_first_show)
  {
    _first_show = false;
    set_globals_tree_path(_global_combo.get_string_value());
    _classes_tree.set_model(grtm()->get_shared_structs_tree());
    _modules_tree.set_model(grtm()->get_shared_modules_tree());
  }
  
  refresh_files();

  int idx= 0;
  std::string root = _global_tree_be->get_path_for_node(bec::NodeId(), true);
  std::vector<std::string> l = grtm()->get_shell()->get_grt_tree_bookmarks();
  _global_combo.clear();
  for (std::vector<std::string>::const_iterator i = l.begin(); i != l.end(); ++i, ++idx)
  {
    _global_combo.add_item(*i);
    if (root == *i)
      _global_combo.set_selected(idx);
  }
  
  //refresh values
  _global_tree.refresh(bec::NodeId());
  grtm()->get_shared_structs_tree()->refresh();
  grtm()->get_shared_modules_tree()->refresh();
  
///  _global_tree.expand_row(Gtk::TreePath("0"), false);
  _global_list.refresh(bec::NodeId());
  
  //refresh _struct
  _classes_tree.refresh(bec::NodeId());
  
  // refresh modules
  _modules_tree.refresh(bec::NodeId());

  if (grtm()->get_grt()->shell_type() == "lua")
  {
    _script_extension = ".lua";
    _comment_prefix = "-- ";
  }
  else if (grtm()->get_grt()->shell_type() == "python")
  {
    _script_extension = ".py";
    _comment_prefix = "# ";
  }
  
  refresh_snippets();
}


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

void GRTShellWindow::execute_file()
{
  GRTCodeEditor *editor = get_active_editor();
  if (!editor) return;

  grtm()->push_output_callback(boost::bind(&GRTShellWindow::add_output, this, _1));

  if (_debugger && g_str_has_suffix(editor->get_path().c_str(), ".py"))
  {
    _run_button->show(false);
    _continue_button->show(true);
    _pause_button->set_enabled(true);
    
    _debugger->run(editor);
    
    _run_button->show(true);
    _continue_button->show(false);
    
    _step_button->set_enabled(false);
    _step_into_button->set_enabled(false);
    _step_out_button->set_enabled(false);
    _continue_button->set_enabled(false);
    _stop_button->set_enabled(false);
    _pause_button->set_enabled(false);
  }
  else
    editor->execute();

  grtm()->pop_output_callback();
}


void GRTShellWindow::debug_step()
{
  GRTCodeEditor *editor = get_active_editor();

  if (editor && _debugger && g_str_has_suffix(editor->get_path().c_str(), ".py"))
  {
    if (_debugger->program_stopped())
      _debugger->step();
    else 
    {
      // start the program stopping at the 1st line
      grtm()->push_output_callback(boost::bind(&GRTShellWindow::add_output, this, _1));
    
      _run_button->show(false);
      _continue_button->show(true);
      _pause_button->set_enabled(true);
      
      _debugger->run(editor, true);
      
      _run_button->show(true);
      _continue_button->show(false);
      
      _step_button->set_enabled(true);
      _step_into_button->set_enabled(false);
      _step_out_button->set_enabled(false);
      _continue_button->set_enabled(false);
      _stop_button->set_enabled(false);
      _pause_button->set_enabled(false);

      grtm()->pop_output_callback();
    }
  }
}


void GRTShellWindow::debug_step_into()
{
  if (_debugger)
    _debugger->step_into();

}

void GRTShellWindow::debug_step_out()
{
  if (_debugger)
    _debugger->step_out();
  
}


void GRTShellWindow::debug_continue()
{
  if (_debugger)
    _debugger->continue_();
}


void GRTShellWindow::debug_stop()
{
  if (_debugger)
    _debugger->stop();
}


void GRTShellWindow::debug_pause()
{
  if (_debugger)
    _debugger->pause();
}

void GRTShellWindow::save_file(bool save_as)
{
  GRTCodeEditor *editor = get_active_editor();
  if (editor)
    editor->save(save_as);
}


void GRTShellWindow::close_tab()
{
  GRTCodeEditor *editor = get_active_editor();
  if (editor)
  {
    if (editor->can_close())
      close_editor(editor);
  }
}

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

void GRTShellWindow::side_tab_changed()
{
#ifdef _WIN32
  static std::string side_bar_titles[] = {
    _("File Browser"),
    _("Globals Tree"),
    _("Classes List"),
    _("Modules List")
  };

  _side_header_panel.set_title(side_bar_titles[_side_tab.get_active_tab()]);
#endif
}

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

void GRTShellWindow::handle_output(const std::string &text)
{
  _shell_text.append_text(text, true);
}


void GRTShellWindow::handle_error(const std::string &text, const std::string &detail)
{
  _shell_text.append_text(text);
  
  _shell_text.append_text(detail);
  
}


void GRTShellWindow::handle_prompt(const std::string &text)
{
  _shell_prompt.set_text(text);
}



void GRTShellWindow::set_globals_tree_path(const std::string &path)
{
  try
  {
    if (path.empty() || path[0] != '/')
      _global_tree_be = grtm()->get_shared_value_tree("/");
    else
      _global_tree_be = grtm()->get_shared_value_tree(path);
    _global_tree.set_model(_global_tree_be);
    _global_tree.refresh(bec::NodeId());
    _global_list.refresh(bec::NodeId());
  }
  catch (std::exception &exc)
  {
    g_message("error: %s", exc.what());
  }
}


void GRTShellWindow::change_global_path()
{
  set_globals_tree_path(_global_combo.get_string_value());
}


void GRTShellWindow::change_class_sorting()
{
  grtm()->get_shared_structs_tree()->set_display_mode((bec::StructsTreeBE::DisplayMode)_classes_sorting.get_selected_index());
  _classes_tree.refresh(bec::NodeId());
}


void GRTShellWindow::global_selected()
{
  if (_inspector)
  {
    _global_list.set_model(0);
    delete _inspector;
    _inspector= 0;
  }
  
  bec::NodeId selected;
  
  if (_global_tree.get_selected_node(selected))
  {
    grt::ValueRef value(_global_tree_be->get_node_value(selected));
    
    if (value.is_valid())
    {
      _inspector= grtm()->get_new_value_inspector(value, false);
      _global_list.set_model(_inspector);
    }
    
    _global_entry.set_value(_global_tree_be->get_path_for_node(selected, true));
  }
  else
    _global_entry.set_value("");
}

void GRTShellWindow::class_selected()
{
  bec::NodeId selected;
  
  if (_classes_tree.get_selected_node(selected))
  {
    std::string text;
   
    grtm()->get_shared_structs_tree()->get_field(selected, bec::StructsTreeBE::Caption, text);
    text.append("\n");
    text.append(grtm()->get_shared_structs_tree()->get_field_description(selected, 0));
   
    _classes_text.set_value(text);
  }
  else
    _classes_text.set_value("");
}

void GRTShellWindow::module_selected()
{
  bec::NodeId selected;
  
  if (_modules_tree.get_selected_node(selected))
  {
    std::string text(grtm()->get_shared_modules_tree()->get_field_description(selected, 0));
    
    _modules_text.set_value(text);
  }
  else
    _modules_text.set_value("");
}



void GRTShellWindow::handle_global_menu(const std::string &action)
{
  bec::NodeId selected;
  
  if (_global_tree.get_selected_node(selected))
  {
    if (action == "copy_value")
    {
      grt::ValueRef value(_global_tree_be->get_node_value(selected));
      mforms::Utilities::set_clipboard_text(value.repr());      
    }
    else if (action == "copy_path")
    {
      mforms::Utilities::set_clipboard_text(_global_tree_be->get_path_for_node(selected, true));
    }
    else if (action == "copy_path_py")
    {
      std::string path = "grt.root";
      std::vector<std::string> parts;
      
      parts = base::split(_global_tree_be->get_path_for_node(selected, true), "/");
      for (base::const_range<std::vector<std::string> > p(parts); p; ++p)
      {
        if (p->empty()) continue;
        
        if (isdigit(p->at(0)))
          path.append("[").append(*p).append("]");
        else
          path.append(".").append(*p);
      }

      mforms::Utilities::set_clipboard_text(path);
    }
  }
}


void GRTShellWindow::save_snippets()
{
  std::string path = bec::make_path(grtm()->get_user_datadir(), "shell_snippets"+_script_extension+".txt");
  FILE *f = base_fopen(path.c_str(), "w+");
  if (!f)
  {
    _shell_text.append_text(base::strfmt("Cannot save snippets to %s: %s", 
                                         path.c_str(), g_strerror(errno)));
    return;
  }
  
  int c = _snippet_list->count();
  for (int i = _global_snippet_count; i < c; i++)
  {
    std::string snippet = _snippet_list->get_row_tag(i);
    std::string::size_type p = 0, l = snippet.size();

    while (p < l)
    {
      std::string::size_type eol = snippet.find('\n', p);
      if (eol == std::string::npos)
        eol = l;
      else
        eol++;
      fwrite(" ", 1, 1, f);
      fwrite(snippet.data()+p, 1, eol-p, f);
      p = eol;
    }
    fwrite("\n", 1, 1, f);
  }
  
  fclose(f);
}

void GRTShellWindow::load_snippets_from(const std::string &path)
{
  FILE *f = base_fopen(path.c_str(), "r");
  if (f)
  {
    char line[4096];
    
    while (fgets(line, sizeof(line), f))
    {
      std::string script = line+1;
      char *ptr = strchr(line, '\n');
      if (ptr)
        *ptr= 0;
      std::string name = line+1;
      
      while (fgets(line, sizeof(line)-1, f) && line[0] == ' ')
      {
        script.append(line+1);
      }
      
      // Remove the last line break, we added that, not the user.
      if (script.size() > 0)
        script.erase(script.size() - 1, 1);
      
      int row = _snippet_list->add_row();
      _snippet_list->set(row, 0, name);
      _snippet_list->set_row_tag(row, script);
    }
    fclose(f);
  }
}


void GRTShellWindow::refresh_snippets()
{
  _snippet_list->clear_rows();
  
  load_snippets_from(grtm()->get_data_file_path("shell_snippets"+_script_extension+".txt"));
  _global_snippet_count = _snippet_list->count();
  load_snippets_from(bec::make_path(grtm()->get_user_datadir(), "shell_snippets"+_script_extension+".txt"));
  
  snippet_selected();
}


void GRTShellWindow::open_script_file()
{
  mforms::FileChooser chooser(mforms::OpenFile);
  chooser.set_title(_("Open GRT Script"));
  if (chooser.run_modal())
  {
    open_file_in_editor(chooser.get_path(), true);
  }
}


bool GRTShellWindow::execute_script(const std::string &script, const std::string &language)
{
  bool result = grtm()->get_shell()->run_script(script, language);
  save_state();

  return result;
}


void GRTShellWindow::add_snippet()
{
  std::string snippet = _comment_prefix + " new snippet\n";
  
  int row = _snippet_list->add_row();
  _snippet_list->set_row_tag(row, snippet);
  _snippet_list->set_selected(row);

  snippet_selected(); // force snippet to be displayed
  snippet_changed(0, 0); // force setting of the snippet name

  save_state();
}


void GRTShellWindow::del_snippet()
{
  int sel = _snippet_list->get_selected();
  if (sel >= 0)
  {
    _snippet_list->delete_row(sel);
    snippet_selected();
    save_snippets();
  }
}


void GRTShellWindow::copy_snippet()
{
  int sel = _snippet_list->get_selected();
  if (sel >= 0)
    mforms::Utilities::set_clipboard_text(_snippet_list->get_row_tag(sel));
}


void GRTShellWindow::scriptize_snippet()
{
  int sel = _snippet_list->get_selected();
  if (sel >= 0)
  {
    std::string snippet = _snippet_list->get_row_tag(sel);
    std::string language = "python";
    
    // kind of a hack.. pick language depending on comment type used
    if (g_str_has_prefix(snippet.c_str(), "#"))
      language= "python";
    else if (g_str_has_prefix(snippet.c_str(), "--"))
      language= "lua";
    
    GRTCodeEditor *editor= add_editor(true, language);
    editor->set_text(snippet);
  }
}

bool run_return_true(boost::function<void (const std::string&)> f, const std::string& param)
{
  f(param);
  return true;
}

void GRTShellWindow::run_snippet()
{
  int sel = _snippet_list->get_selected();
  if (sel >= 0)
  {
    std::string script = _snippet_list->get_row_tag(sel);

    // auto-select the tab where output goes
    _main_tab.set_active_tab(0);

    handle_output("Running snippet...\n");
    // redirect snippet output to the shell
//    static const bool trueval = true;
    boost::function<void (const std::string&)> f = boost::bind(&GRTShellWindow::handle_output,this,_1);
    boost::function<bool (const std::string&)> task = boost::bind(run_return_true,f, _1);
    grtm()->push_output_callback(task);
    
    try
    {
      std::string language = "python";
      
      // kind of a hack.. pick language depending on comment type used
      if (g_str_has_prefix(script.c_str(), "#"))
        language= "python";
      else if (g_str_has_prefix(script.c_str(), "--"))
        language= "lua";

      bool ret = execute_script(script, language);
      grtm()->pop_output_callback();
      if (!ret)
        handle_output("Snippet execution finished with an error\n");
    }
    catch (const std::exception &exc)
    {
      grtm()->pop_output_callback();
      
      handle_output("Exception caught while executing snippet:\n");
      handle_output(std::string(exc.what()).append("\n"));
    }
  }

  save_state();
}


void GRTShellWindow::snippet_selected()
{
  bool read_only = false;
  _snippet_text.set_read_only(false); // Necessary to be able to change the text.
  int sel = _snippet_list->get_selected();
  if (sel < 0)
  {
    _snippet_delete_button->set_enabled(false);
    _snippet_copy_button->set_enabled(false);
    _snippet_text.set_value("");
    read_only = true;
    
    for (int i= 0; i < 6; i++)
      _snippet_menu.set_item_enabled(i, false);    
  }
  else
  {
    if (sel < _global_snippet_count)
    {
      read_only = true;
      _snippet_delete_button->set_enabled(false);
      
      for (int i= 0; i < 6; i++)
      {
        if (i != 5)
          _snippet_menu.set_item_enabled(i, true);
        else
          _snippet_menu.set_item_enabled(i, false); // 5 is delete
      }
    }
    else
    {
      _snippet_delete_button->set_enabled(true);
      
      for (int i= 0; i < 6; i++)
        _snippet_menu.set_item_enabled(i, true);
    }
    _snippet_text.set_value(_snippet_list->get_row_tag(sel));
    _snippet_copy_button->set_enabled(true);
  }

  _snippet_text.set_read_only(read_only);
}


void GRTShellWindow::snippet_changed(int line, int linesAdded)
{
  std::string snippet = _snippet_text.get_string_value();
  int sel = _snippet_list->get_selected();
  
  if (sel >= 0)
  {
    _snippet_list->set_row_tag(sel, snippet);
    
    std::string::size_type p = snippet.find('\n');
    if (p != std::string::npos)
      snippet = snippet.substr(0, p);
    _snippet_list->set(sel, 0, snippet);
    
    save_snippets();
  }
}

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

void GRTShellWindow::snippet_menu_activate(const std::string &action)
{
  if (action == "execute")
    run_snippet();
  else if (action == "new_with_snippet")
    scriptize_snippet();
  else if (action == "copy_clipboard")
    copy_snippet();
  else if (action == "delete")
    del_snippet();
}

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

void GRTShellWindow::file_menu_activate(const std::string &action)
{
  if (action == "file-from-template")
    add_new_script();
  else if (action == "open-script")
    open_script_file();
  else if (action == "delete-script")
    delete_selected_file();
}

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

GRTCodeEditor *GRTShellWindow::add_editor(bool is_script, const std::string &language)
{
  GRTCodeEditor *editor = manage(new GRTCodeEditor(this, !is_script, language));

  _editors.push_back(editor);
  
  int page = _main_tab.add_page(editor, editor->get_title());
  _main_tab.set_active_tab(page);
  
  save_state();
  
  if (language == "python" && _debugger)
    _debugger->editor_added(editor);

  return editor;
}

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

void GRTShellWindow::close_editor(GRTCodeEditor *editor)
{
  for (std::vector<GRTCodeEditor*>::iterator iter= _editors.begin(); iter != _editors.end(); ++iter)
  {
    if ((*iter) == editor)
    {
      _editors.erase(iter);
      break;
    }
  }

  if (_debugger)
    _debugger->editor_closed(editor);
  
  _main_tab.remove_page(editor); 
  delete editor;

  save_state();
}


void GRTShellWindow::open_file_in_editor(const std::string &path, bool is_script)
{
  if (get_editor_for(path, true) != NULL)
    return;

  std::string language = ""; // No syntax highlighting if file extension is unknown.
  if (g_str_has_suffix(path.c_str(), ".py"))
    language = "python"; // Python script
  else
    if (g_str_has_suffix(path.c_str(), ".lua"))
      language = "lua"; // Lua script
    else
      if (g_str_has_suffix(path.c_str(), ".sql") || g_str_has_suffix(path.c_str(), ".qbquery"))
        language = "sql";

  // Show warning messages if applicable...
  if (language == "")
  {
    std::string text= base::strfmt(_("The file %s has an unsupported extension for this script editor."), path.c_str());
    Utilities::show_message_and_remember(_("Unsupported File Format"), text, _("OK"), _(""), _(""),  "ShellWindowUnknownLanguageFile", _("") );
  }
  else if (language == "sql")
    Utilities::show_message_and_remember(_("Unsupported Execution"), _("This script editor is meant for developing Workbench plugins and scripts. SQL scripts should be opened and executed in the SQL Editor."), _("OK"), _(""), _(""),  "ShellWindowSqlLanguageFile", _("") );

  GRTCodeEditor *editor= add_editor(is_script, language);
  if (!editor->load(path))
  {
    close_editor(editor);
    return;
  }
#ifdef _DEBUG
  editor->test_markup();
#endif
}


GRTCodeEditor *GRTShellWindow::show_file_at_line(const std::string &path, int line)
{
  open_file_in_editor(path, true);
  GRTCodeEditor *editor = get_editor_for(path, true);
  if (!editor)
    add_output(base::strfmt("Cannot open file %s", path.c_str()));
  else
  {
    int start, length;
    editor->get_editor()->get_range_of_line(line, start, length);
    editor->get_editor()->set_selection(start, 0);
  }
  return editor;
}


void GRTShellWindow::add_new_script()
{
  NewPluginDialog wizard(this, grtm()->get_data_file_path("script_templates"));
  std::string path;
  std::string code;
  bool is_script;
  std::string language;
  
  if (wizard.run(path, code, is_script, language))
  {
    GRTCodeEditor *editor= add_editor(is_script, language);
    if (!path.empty() && g_basename(path.c_str()) == path)
      path= bec::make_path(grtm()->get_user_script_path(), path);
    editor->set_path(path);
    editor->set_text(code);
  }

  save_state();
}


bool GRTShellWindow::add_output(const std::string &text)
{
  _output_text.append_text(text, true);
  return true;
}

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

void GRTShellWindow::set_editor_title(GRTCodeEditor *editor, const std::string &title)
{
  int index= _main_tab.get_page_index(editor);
  if (index >= 0)
    _main_tab.set_tab_title(index, editor->get_title());
}


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

/**
 * Called from the UI context when WB is about to quit. Check if we have pending changes.
 * Return true if we are clear, false otherwise.
 */
bool GRTShellWindow::request_quit()
{
  for (std::vector<GRTCodeEditor*>::iterator editor = _editors.begin(); editor != _editors.end(); editor++)
    if (!(*editor)->can_close())
      return false;

  return true;
}

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

void GRTShellWindow::add_files_from_dir(const std::string &dirname, bool is_script)
{
  GDir *dir= g_dir_open(dirname.c_str(), 0, NULL);
  if (!dir)
    return;
  
  while (const gchar *name= g_dir_read_name(dir))
  {
    if (g_str_has_suffix(name, ".py") || g_str_has_suffix(name, ".lua"))
    {
      int row= _files_tree->add_row();
      _files_tree->set(row, 0, std::string("      ")+name);
      if (is_script)
        _files_tree->set_row_tag(row, std::string("s").append(dirname).append(G_DIR_SEPARATOR_S).append(name));
      else
        _files_tree->set_row_tag(row, std::string("m").append(dirname).append(G_DIR_SEPARATOR_S).append(name));
    }
  }

  g_dir_close(dir);
}


void GRTShellWindow::refresh_files()
{
  int row;
  
  _files_tree->clear_rows();
  
  row= _files_tree->add_row();
  _files_tree->set(row, 0, "User Scripts");
  add_files_from_dir(grtm()->get_user_script_path(), true);

  row= _files_tree->add_row();
  _files_tree->set(row, 0, "User Modules");
  add_files_from_dir(grtm()->get_user_module_path(), false);

  row= _files_tree->add_row();
  _files_tree->set(row, 0, "User Libraries");
  add_files_from_dir(grtm()->get_user_library_path(), true);  
}


void GRTShellWindow::file_list_activated(int row, int column)
{
  std::string path= _files_tree->get_row_tag(row);
  if (!path.empty())
  {
    open_file_in_editor(path.substr(1), path[0] == 's');
  }
}


void GRTShellWindow::on_file_save(const std::string &file)
{
  refresh_files();
  if (_debugger)
    _debugger->refresh_file(file);
}

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

void GRTShellWindow::delete_selected_file()
{
  std::string path= _files_tree->get_row_tag(_files_tree->get_selected());
  if (!path.empty())
  {
    std::string fn = path.substr(1);
    if (mforms::Utilities::show_message(_("Delete File"),
                                        base::strfmt(_("Really delete '%s' from disk? This operation cannot be undone."),
                                                     fn.c_str()),
                                        _("Delete"), _("Cancel")) == mforms::ResultOk)
    {
      g_remove(fn.c_str());
      refresh_files();
    }
  }
}

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

mforms::Button *GRTShellWindow::add_tool_button(const std::string &image,
                                    const boost::function<void ()> &action,
                                    const std::string &tooltip,
                                    bool left)
{
  App *app= App::get();
  Button *b = manage(new Button(ToolButton));
  b->set_icon(app->get_resource_path(image));
  b->set_tooltip(tooltip);
  scoped_connect(b->signal_clicked(),action);
  if (left)
    _toolbar.add(b, false, true);
  else
    _toolbar.add_end(b, false, true);
  return b;
}

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

void GRTShellWindow::add_tool_separator()
{
  App *app= App::get();
  ImageBox* image = manage(new ImageBox());
  image->set_image(app->get_resource_path("statusbar_separator.png"));
  image->set_image_align(MiddleCenter);
  _toolbar.add(image, false, true);
}

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

void GRTShellWindow::load_state()
{
  int x = _context->read_state("left", "scripting-shell", 100);
  int y = _context->read_state("top", "scripting-shell", 100);
  int width = _context->read_state("width", "scripting-shell", 800);
  int height = _context->read_state("height", "scripting-shell", 600);

  set_size(width, height);
  set_position(x, y);

  // Restore divider positions.
  _hsplitter.set_position(_context->read_state("main-splitter", "scripting-shell", 250));
  _global_splitter.set_position(_context->read_state("global-splitter", "scripting-shell", 400));
  _modules_splitter.set_position(_context->read_state("modules-splitter", "scripting-shell", 400));
  _classes_splitter.set_position(_context->read_state("classes-splitter", "scripting-shell", 400));
  _snippet_splitter.set_position(_context->read_state("snippets-splitter", "scripting-shell", 400));

  _shell_text.set_font(grtm()->get_app_option_string("workbench.scripting.ScriptingShell:Font"));
  _snippet_text.set_font(grtm()->get_app_option_string("workbench.scripting.ScriptingEditor:Font"));
  for (std::vector<GRTCodeEditor*>::iterator editor = _editors.begin(); editor != _editors.end(); editor++)
    (*editor)->set_font(grtm()->get_app_option_string("workbench.scripting.ScriptingEditor:Font"));
  
  _lower_tab_height = _context->read_state("editor-splitter", "scripting-shell", 400);
  on_tab_changed();
}

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

void GRTShellWindow::save_state()
{
  // Store form's size and position.
  _context->save_state("left", "scripting-shell", get_x());
  _context->save_state("top", "scripting-shell", get_y());
  _context->save_state("width", "scripting-shell", get_width());
  _context->save_state("height", "scripting-shell", get_height());

  // Store all divider positions.
  _context->save_state("main-splitter", "scripting-shell", _hsplitter.get_position());
  _context->save_state("global-splitter", "scripting-shell", _global_splitter.get_position());
  _context->save_state("modules-splitter", "scripting-shell", _modules_splitter.get_position());
  _context->save_state("classes-splitter", "scripting-shell", _classes_splitter.get_position());
  _context->save_state("snippet-splitter", "scripting-shell", _snippet_splitter.get_position());
}

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

/**
 *  Triggered when the shell window was closed by the user. We can use this event to store our state.
 */
void GRTShellWindow::shell_closed()
{
  save_state();
}

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

/**
 * Triggered when a tab is about to close. Don't allow shell and snippets to close and check if
 * editors are dirty.
 */
bool GRTShellWindow::on_tab_closing(int index)
{
  if (index == 0 || index == 1)
    return false;

  GRTCodeEditor* editor = _editors[index - EDITOR_TAB_OFFSET];
  return editor->can_close();
}

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

void GRTShellWindow::on_tab_closed(int index)
{
  GRTCodeEditor* editor = _editors[index - EDITOR_TAB_OFFSET];
  close_editor(editor);
}

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

void GRTShellWindow::on_tab_changed()
{
  GRTCodeEditor *editor = get_active_editor();
  if (editor)
  {
    bool exec_enabled = (editor->get_language() == "python" || editor->get_language() == "lua");

    _save_button->set_enabled(true);
    _save_as_button->set_enabled(true);
    _run_button->set_enabled(exec_enabled);
    _step_button->set_enabled(exec_enabled);
    _clear_script_output_button->set_enabled(true);

    if (_close_script_tab_button)
      _close_script_tab_button->set_enabled(true);

    _right_splitter.set_expanded(false, true);
  }
  else 
  {
    _save_button->set_enabled(false);
    _save_as_button->set_enabled(false);
    _run_button->set_enabled(false);
    _step_button->set_enabled(false);
    _clear_script_output_button->set_enabled(false);

    if (_close_script_tab_button)
      _close_script_tab_button->set_enabled(false);

    _right_splitter.set_expanded(false, false);
  }

}

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

void GRTShellWindow::activate_output_tab()
{
  _lower_tab.set_active_tab(0);
}

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

/**
 * Returns the editor which is currently editing the given file.
 */
GRTCodeEditor* GRTShellWindow::get_editor_for(const std::string& path, bool select_tab)
{
#ifdef _WIN32
  // We probably would need g_utf8_normalize too if we want it really good, but since this is
  // supposed to be a temporary solution...
  gchar* path1 = g_utf8_strdown(path.c_str(), -1);
  for (std::vector<GRTCodeEditor*>::iterator editor = _editors.begin(); editor != _editors.end(); editor++)
  {
    gchar* path2 = g_utf8_strdown((*editor)->get_path().c_str(), -1);
    if (g_utf8_collate(path1, path2) == 0)
    {
      if (select_tab)
        _main_tab.set_active_tab(editor - _editors.begin() + EDITOR_TAB_OFFSET);

      g_free(path2);
      return *editor;
    }
    g_free(path2);
  }

  g_free(path1);
#else
  for (std::vector<GRTCodeEditor*>::iterator editor = _editors.begin(); editor != _editors.end(); editor++)
  {
    if ((*editor)->get_path() == path)
    {
      if (select_tab)
        _main_tab.set_active_tab(editor - _editors.begin() + EDITOR_TAB_OFFSET);

      return *editor;
    }
  }
#endif
  return NULL;
}


GRTCodeEditor *GRTShellWindow::get_active_editor()
{
  int index = _main_tab.get_active_tab() - EDITOR_TAB_OFFSET;
  if (index >= 0 && index < (int)_editors.size())
    return _editors[index];
  return 0;
}

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

