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

#include "stdafx.h"
#include "grt_shell_window.h"
#include "base/file_functions.h"
#include "base/string_utilities.h"
#include "base/wb_iterators.h"
#include "base/notifications.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 base;
using namespace bec;


#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::TreeFlatList), 
  _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),
  _notifs_splitter(false), _notifs_tree(mforms::TreeNoBorder), _notifs_text(mforms::VerticalScrollBar),
  _right_splitter(false), 
  _shell_box(false), _shell_text(mforms::VerticalScrollBar), _shell_hbox(true),
#ifdef _WIN32
_side_header_panel(mforms::FilledHeaderPanel),
_lower_tab(mforms::TabViewPalette),
_lower_header_panel(mforms::FilledHeaderPanel),
#else
_lower_tab(mforms::TabViewDocument),
#endif
  _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.

  // 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::TreeNodeView(mforms::TreeNoBorder|mforms::TreeNoHeader));
#else
  _files_tree = mforms::manage(new mforms::TreeNodeView(mforms::TreeDefault|mforms::TreeNoHeader));
#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(IconStringColumnType, "", 400, false);
  _files_tree->end_columns();

  scoped_connect(_files_tree->signal_node_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
#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::IconStringColumnType, "Name", 100);
  _global_list.add_column(mforms::StringColumnType, "Value", 100);
  _global_list.end_columns();
  _global_tree.add_column(mforms::IconStringColumnType, "GRT Globals", 200, false);
  _global_tree.add_column(mforms::StringColumnType, "Type", 100, false);
  _global_tree.end_columns();
  scoped_connect(_global_tree.signal_expand_toggle(), boost::bind(&GRTShellWindow::globals_expand_toggle, this, _1, _2));
  scoped_connect(_global_tree.signal_changed(),boost::bind(&GRTShellWindow::global_selected, this));
  scoped_connect(_global_combo.signal_changed(),boost::bind(&GRTShellWindow::refresh_globals_tree, 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::IconStringColumnType, "Name", 150, false);
  _classes_tree.add_column(mforms::StringColumnType, "Type", 100, false);
  _classes_tree.add_column(mforms::StringColumnType, "Caption", 100, false);
  _classes_tree.end_columns();
  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::refresh_classes_tree, 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::IconStringColumnType, "GRT Modules", 220, false);
  _modules_tree.end_columns();
  scoped_connect(_modules_tree.signal_changed(),boost::bind(&GRTShellWindow::module_selected, this));
  
  _right_splitter.add(&_main_tab);

  // 4) Notifications tree
#ifdef _WIN32
  _notifs_splitter.set_back_color("#FFFFFF");
  //_modules_splitter.set_padding(0, 0, 0, 2); TODO: might require workaround.
#endif
//  _side_tab.add_page(&_notifs_splitter, "Notifications");
  _notifs_splitter.add(&_notifs_tree, 0);
  _notifs_splitter.add(&_notifs_text, 0);
  _notifs_text.set_read_only(true);
  _notifs_text.set_back_color("#FFFFFF");
  _notifs_tree.add_column(mforms::IconStringColumnType, "Notifications", 220, false);
  _notifs_tree.end_columns();
  scoped_connect(_notifs_tree.signal_changed(),boost::bind(&GRTShellWindow::notif_selected, this));
  
#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 TreeNodeView(mforms::TreeNoBorder|mforms::TreeFlatList));
#else
  _snippet_list = mforms::manage(new TreeNodeView(mforms::TreeDefault|mforms::TreeFlatList));
#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);
  _notifs_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();  
}

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

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()
{
  refresh_files();

  int idx= 0;
  std::string root = _global_tree.root_node()->get_tag();
  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
  refresh_globals_tree();
  global_selected();
  
  //refresh _struct
  refresh_classes_tree();
  
  // refresh modules
  refresh_modules_tree();

  refresh_notifs_list();

  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();
}


//--------------------------------------------------------------------------------------------------
bool GRTShellWindow::capture_output(const grt::Message &msg, void *sender, bool send_to_output)
{
  if (msg.type == grt::OutputMsg)
  {
    if (grtm()->in_main_thread())
    {
      if (send_to_output)
        add_output(msg.text);
      else
        handle_output(msg.text); // sends to shell window
    }
    else
    {
      if (send_to_output)
        grtm()->run_once_when_idle(boost::bind(&GRTShellWindow::add_output, this, msg.text));
      else
        grtm()->run_once_when_idle(boost::bind(&GRTShellWindow::handle_output, this, msg.text));
    }
    return true;
  }
  return false;
}


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

  grtm()->get_grt()->push_message_handler(boost::bind(&GRTShellWindow::capture_output, this, _1, _2, true));

  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()->get_grt()->pop_message_handler();
}


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()->get_grt()->push_message_handler(boost::bind(&GRTShellWindow::capture_output, this, _1, _2, true));
    
      _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()->get_grt()->pop_message_handler();
    }
  }
}


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::global_selected()
{
  if (_inspector)
  {
    delete _inspector;
    _inspector= 0;
  }
  
  mforms::TreeNodeRef selected;
  
  if ((selected = _global_tree.get_selected_node()))
  {
    grt::ValueRef value(get_global_at_node(selected));
    
    if (value.is_valid())
    {
      _inspector= ValueInspectorBE::create(grtm()->get_grt(), value, false, false);
      refresh_global_list();
    }
    
    _global_entry.set_value(get_global_path_at_node(selected));
  }
  else
    _global_entry.set_value("");
}


void GRTShellWindow::class_selected()
{
  mforms::TreeNodeRef selected;
  
  if ((selected = _classes_tree.get_selected_node()))
    _classes_text.set_value(get_class_node_description(selected));
  else
    _classes_text.set_value("");
}

void GRTShellWindow::module_selected()
{
  mforms::TreeNodeRef selected;
  
  if ((selected = _modules_tree.get_selected_node()))
  {
    std::string text(get_module_node_description(selected));
    _modules_text.set_value(text);
  }
  else
    _modules_text.set_value("");
}

void GRTShellWindow::notif_selected()
{
  mforms::TreeNodeRef selected;
  
  if ((selected = _notifs_tree.get_selected_node()) && selected->get_parent() != _notifs_tree.root_node())
  {
    std::string text;
    std::string name = selected->get_string(0);
    base::NotificationCenter::NotificationHelp info = base::NotificationCenter::get()->get_registered_notification(name);
    
    text = base::strfmt("%s (%s)\n"
                        "%s\n\n"
                        "Sender: %s\n\n"
                        "Extra Info Dictionary:\n%s",
                        name.c_str(), info.context.c_str(),
                        info.summary.c_str(),
                        info.sender.empty() ? "NULL" : info.sender.c_str(),
                        info.info.empty() ? "No additional info is sent" : info.info.c_str());
    
    
    _notifs_text.set_value(text);
  }
  else
    _notifs_text.set_value("");  
}

void GRTShellWindow::handle_global_menu(const std::string &action)
{
  mforms::TreeNodeRef selected;
  
  if ((selected = _global_tree.get_selected_node()))
  {
    if (action == "copy_value")
    {
      grt::ValueRef value(get_global_at_node(selected));
      mforms::Utilities::set_clipboard_text(value.repr());      
    }
    else if (action == "copy_path")
    {
      mforms::Utilities::set_clipboard_text(get_global_path_at_node(selected));
    }
    else if (action == "copy_path_py")
    {
      std::string path = "grt.root";
      std::vector<std::string> parts;
      
      parts = base::split(get_global_path_at_node(selected), "/");
      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 = make_path(grtm()->get_user_datadir(), "shell_snippets"+_script_extension);
  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->root_node()->count();
  for (int i = _global_snippet_count; i < c; i++)
  {
    std::string snippet = _snippet_list->root_node()->get_child(i)->get_tag();
    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);
      mforms::TreeNodeRef node = _snippet_list->add_node();
      node->set_string(0, name);
      node->set_tag(script);
    }
    fclose(f);
  }
}


void GRTShellWindow::refresh_snippets()
{
  _snippet_list->clear();

  load_snippets_from(grtm()->get_data_file_path("shell_snippets"+_script_extension+".txt"));
  _global_snippet_count = _snippet_list->root_node()->count();
  load_snippets_from(make_path(grtm()->get_user_datadir(), "shell_snippets"+_script_extension));

  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";
  
  mforms::TreeNodeRef node = _snippet_list->add_node();
  node->set_tag(snippet);
  _snippet_list->select_node(node);

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

  save_state();
}


void GRTShellWindow::del_snippet()
{
  mforms::TreeNodeRef node = _snippet_list->get_selected_node();
  if (node)
  {
    node->remove_from_parent();
    snippet_selected();
    save_snippets();
  }
}


void GRTShellWindow::copy_snippet()
{
  mforms::TreeNodeRef node = _snippet_list->get_selected_node();
  if (node)
    mforms::Utilities::set_clipboard_text(node->get_tag());
}


void GRTShellWindow::scriptize_snippet()
{
  mforms::TreeNodeRef node = _snippet_list->get_selected_node();
  if (node)
  {
    std::string snippet = node->get_tag();
    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()
{
  mforms::TreeNodeRef node = _snippet_list->get_selected_node();
  if (node)
  {
    std::string script = node->get_tag();

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

    handle_output("Running snippet...\n");
    // redirect snippet output to the shell
    grtm()->get_grt()->push_message_handler(boost::bind(&GRTShellWindow::capture_output, this, _1, _2, false));
    
    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()->get_grt()->pop_message_handler();
      if (!ret)
        handle_output("Snippet execution finished with an error\n");
    }
    catch (const std::exception &exc)
    {
      grtm()->get_grt()->pop_message_handler();
      
      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_features(mforms::FeatureReadOnly, false); // Necessary to be able to change the text.
  int sel = _snippet_list->get_selected_row();
  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);
    }
    mforms::TreeNodeRef node(_snippet_list->get_selected_node());
    if (node)
      _snippet_text.set_value(node->get_tag());
    _snippet_copy_button->set_enabled(true);
  }

  _snippet_text.set_features(mforms::FeatureReadOnly, read_only);
}


void GRTShellWindow::snippet_changed(int line, int linesAdded)
{
  std::string snippet = _snippet_text.get_string_value();
  mforms::TreeNodeRef node = _snippet_list->get_selected_node();
  
  if (node)
  {
    node->set_tag(snippet);
    
    std::string::size_type p = snippet.find('\n');
    if (p != std::string::npos)
      snippet = snippet.substr(0, p);
    node->set_string(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);

  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= 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(mforms::TreeNodeRef parent, 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"))
    {
      mforms::TreeNodeRef node = parent->add_child();
      node->set_string(0, name);
      if (is_script)
        node->set_tag(std::string("s").append(dirname).append(G_DIR_SEPARATOR_S).append(name));
      else
        node->set_tag(std::string("m").append(dirname).append(G_DIR_SEPARATOR_S).append(name));
    }
  }

  g_dir_close(dir);
}


void GRTShellWindow::refresh_files()
{
  mforms::TreeNodeRef node;
  
  _files_tree->clear();
  
  node= _files_tree->root_node()->add_child();
  node->set_string(0, "User Scripts");
  node->set_icon_path(0, "folder");
  add_files_from_dir(node, grtm()->get_user_script_path(), true);
  node->expand();

  node= _files_tree->root_node()->add_child();
  node->set_string(0, "User Modules");
  node->set_icon_path(0, "folder");
  add_files_from_dir(node, grtm()->get_user_module_path(), false);
  node->expand();

  node= _files_tree->root_node()->add_child();
  node->set_string(0, "User Libraries");
  node->set_icon_path(0, "folder");
  add_files_from_dir(node, grtm()->get_user_library_path(), true);
  node->expand();
}


void GRTShellWindow::file_list_activated(mforms::TreeNodeRef node, int column)
{
  if (node)
  {
    std::string path= node->get_tag();
    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()
{
  mforms::TreeNodeRef node(_files_tree->get_selected_node());
  if (node)
  {
    std::string path= node->get_tag();
    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;
}

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

template <class C>
struct CompareNamedObject
{
  bool operator()(C *a, C *b)
  {
    return a->name() < b->name();
  }
};

void GRTShellWindow::refresh_modules_tree()
{
  grt::GRT *grt = _context->get_grt();
  IconManager *im = IconManager::get_instance();
  std::string mod_icon = im->get_icon_path("grt_module.png");;
  std::string fun_icon = im->get_icon_path("grt_function.png");;
  _modules_tree.clear();
  
  std::vector<grt::Module*> modules(grt->get_modules());
  std::sort(modules.begin(), modules.end(), CompareNamedObject<grt::Module>());
  
  for (std::vector<grt::Module*>::const_iterator m = modules.begin(); m != modules.end(); ++m)
  {
    mforms::TreeNodeRef mod_node = _modules_tree.add_node();
    const std::vector<grt::Module::Function> functions((*m)->get_functions());
    
    if ((*m)->description().empty())
      mod_node->set_string(0, (*m)->name());
    else
      mod_node->set_string(0, (*m)->name()+" *");
    mod_node->set_icon_path(0, mod_icon);
    mod_node->set_tag("m");
    
    for (std::vector<grt::Module::Function>::const_iterator f = functions.begin(); f != functions.end(); ++f)
    {
      mforms::TreeNodeRef fun_node = mod_node->add_child();

      fun_node->set_string(0, f->name);
      fun_node->set_icon_path(0, fun_icon);
    }
  }
}


std::string GRTShellWindow::get_module_node_description(const mforms::TreeNodeRef &node)
{
  grt::GRT *grt = _context->get_grt();
  std::string value;
  if (node->get_parent() == _modules_tree.root_node())
  {
    std::string name = node->get_string(0);
    if (!name.empty() && name[name.size()-1] == '*')
      name = name.substr(0, name.size()-2);
    grt::Module *module= grt->get_module(name);
    if (module)
    {
      std::string descr;
      
      descr.append("Module: "+module->name()+"\n");
      descr.append("Path: "+module->path()+"\n");
      descr.append("Language: "+module->get_loader()->get_loader_name()+"\n");
      descr.append("Extends: "+module->extends()+"\n");
      descr.append("Implements: ");
      for (std::vector<std::string>::const_iterator iter= module->get_interfaces().begin();
           iter != module->get_interfaces().end(); ++iter)
      {
        descr.append(*iter).append("\n");
      }
      descr.append("\n\n").append(module->description());
      value = descr;
    }
  }
  else
  {
    std::string name = node->get_parent()->get_string(0);
    if (!name.empty() && name[name.size()-1] == '*')
      name = name.substr(0, name.size()-2);
    grt::Module *module= grt->get_module(name);
    if (module)
    {
      const grt::Module::Function* func= module->get_function(node->get_string(0));
      
      value= base::strfmt("Function:\n    %s %s(%s)\n\n", fmt_type_spec(func->ret_type).c_str(), func->name.c_str(), fmt_arg_spec_list(func->arg_types).c_str());
      value.append("Arguments:\n");
      std::string args;
      
      for (grt::ArgSpecList::const_iterator arg= func->arg_types.begin();
           arg != func->arg_types.end(); ++arg)
      {
        if (!arg->name.empty())
          args.append("    - ").append(arg->name).append(": ").append(arg->doc).append("\n");
        else
          args.append("    - ").append(fmt_type_spec(arg->type)).append("\n");
      }
      value.append(args);
      
      value.append("\n").append(func->description);
    }
  }
  return value;
}

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

void GRTShellWindow::refresh_classes_tree()
{
  _classes_tree.clear();
  switch (_classes_sorting.get_selected_index())
  {
    case 0:
      refresh_classes_tree_by_name();
      break;
    case 1:
      refresh_classes_tree_by_hierarchy();
      break;
    case 2:
      refresh_classes_tree_by_package();
      break;
  }
}


static std::string struct_member_icon(grt::TypeSpec type)
{
  IconManager *im = IconManager::get_instance();
  switch (type.base.type)
  {
    case grt::ListType:
      return im->get_icon_path("grt_list.png");
    case grt::DictType:
      return im->get_icon_path("grt_dict.png");
    case grt::ObjectType:
      return im->get_icon_path("grt_object.png");
    default:
      return im->get_icon_path("grt_simple_type.png");
  }
  return im->get_icon_path("grt_simple_type.png");
  
}

struct SortableClassMember {
  std::string name;
  std::string caption;
  std::string type;
  std::string icon;
  std::string tag;
  
  bool operator < (const SortableClassMember &o) const 
  {
    return name < o.name;
  }
};


static void scan_class_members(mforms::TreeNodeRef node, grt::MetaClass *gstruct)
{
  IconManager *im = IconManager::get_instance();
  std::vector<SortableClassMember> members;
  for (grt::MetaClass::MethodList::const_iterator mem= gstruct->get_methods_partial().begin();
       mem != gstruct->get_methods_partial().end(); ++mem)
  {
    SortableClassMember m;
    m.name = mem->second.name;
    m.caption = gstruct->get_member_attribute(mem->second.name, "caption");
    m.type = mem->second.ret_type.base.type == grt::AnyType ? "void" : grt::fmt_type_spec(mem->second.ret_type);
    m.icon = im->get_icon_path("grt_function.png");
    
    std::string value;
    value= base::strfmt("Function:\n    %s %s(%s)\n", 
                        m.type.c_str(), 
                        mem->second.name.c_str(), fmt_arg_spec_list(mem->second.arg_types).c_str());
    value.append(gstruct->get_member_attribute(mem->second.name, "caption")).append("\n");
    value.append("Arguments:\n");
    std::string args;
    
    for (grt::ArgSpecList::const_iterator arg= mem->second.arg_types.begin();
         arg != mem->second.arg_types.end(); ++arg)
    {
      if (!arg->name.empty())
        args.append("    - ").append(arg->name).append(": ").append(arg->doc).append("\n");
      else
        args.append("    - ").append(fmt_type_spec(arg->type)).append("\n");
    }
    value.append(args);
    
    value.append("\n").append(gstruct->get_member_attribute(mem->second.name, "desc"));
    
    m.tag = value;
    members.push_back(m);
  }
  
  for (grt::MetaClass::MemberList::const_iterator mem= gstruct->get_members_partial().begin();
       mem != gstruct->get_members_partial().end(); ++mem)
  {
    SortableClassMember m;
    m.name = mem->second.name;
    m.caption = gstruct->get_member_attribute(mem->second.name, "caption");
    m.type = grt::fmt_type_spec(mem->second.type);
    m.icon = struct_member_icon(mem->second.type);
    m.tag = base::strfmt("Member:\n    %s %s\n%s\n\n", m.type.c_str(), m.name.c_str(),
                         gstruct->get_member_attribute(mem->second.name, "desc").c_str());
    members.push_back(m); 
  }
  std::sort(members.begin(), members.end());
  
  for (std::vector<SortableClassMember>::const_iterator i = members.begin(); i != members.end(); ++i)
  {
    mforms::TreeNodeRef mnode = node->add_child();
    
    mnode->set_string(0, i->name);
    mnode->set_string(1, i->type);
    mnode->set_string(2, i->caption);
    mnode->set_icon_path(0, i->icon);
    mnode->set_tag(i->tag);
  }
}


void GRTShellWindow::refresh_classes_tree_by_name()
{
  std::list<grt::MetaClass*> metaclasses(_context->get_grt()->get_metaclasses());

  std::string struct_icon = IconManager::get_instance()->get_icon_path("grt_struct.png");
  
  metaclasses.sort(CompareNamedObject<grt::MetaClass>());
  
  for (std::list<grt::MetaClass*>::const_iterator iter= metaclasses.begin(); 
       iter != metaclasses.end(); ++iter)
  {
    grt::MetaClass *gstruct= *iter;
    mforms::TreeNodeRef node;
    
    node = _classes_tree.add_node();
    node->set_tag(base::strfmt("Class:\n    %s %s\n\n%s", gstruct->name().c_str(), 
                               gstruct->parent() ? base::strfmt("(%s)", gstruct->parent()->name().c_str()).c_str() : "",
                               (*iter)->get_attribute("desc").c_str()));
    node->set_string(0, gstruct->name());
    node->set_string(2, gstruct->get_attribute("caption"));
    node->set_icon_path(0, struct_icon);

    scan_class_members(node, gstruct);
  }
}


static void scan_subclasses(const std::list<grt::MetaClass*> &metaclasses, mforms::TreeNodeRef parnode, grt::MetaClass *parent)
{
  std::string struct_icon = IconManager::get_instance()->get_icon_path("grt_struct.png");

  for (std::list<grt::MetaClass*>::const_iterator iter= metaclasses.begin();
       iter != metaclasses.end(); ++iter)
  {
    mforms::TreeNodeRef node;
    if ((*iter)->parent() != parent)
      continue;
    
    node = parnode->add_child();
    node->set_tag((*iter)->get_attribute("desc"));    
    node->set_string(0, (*iter)->name());
    node->set_string(2, (*iter)->get_attribute("caption"));
    node->set_icon_path(0, struct_icon);
    
    scan_class_members(node, *iter);
    
    // add child structs
    scan_subclasses(metaclasses, node, *iter);
  }  
}

void GRTShellWindow::refresh_classes_tree_by_hierarchy()
{
  std::list<grt::MetaClass*> metaclasses(_context->get_grt()->get_metaclasses());
  metaclasses.sort(CompareNamedObject<grt::MetaClass>());
    
  scan_subclasses(metaclasses, _classes_tree.root_node(), _context->get_grt()->get_metaclass(grt::internal::Object::static_class_name()));
}


void GRTShellWindow::refresh_classes_tree_by_package()
{
  IconManager *im = IconManager::get_instance();
  std::map<std::string, mforms::TreeNodeRef> package_nodes;
  std::list<grt::MetaClass*> metaclasses(_context->get_grt()->get_metaclasses());
  metaclasses.sort(CompareNamedObject<grt::MetaClass>());
  
  std::string struct_icon = im->get_icon_path("grt_struct.png");

  for (std::list<grt::MetaClass*>::const_iterator iter= metaclasses.begin();
       iter != metaclasses.end(); ++iter)
  {
    std::string pkgname= (*iter)->name();
    
    std::string::size_type p= pkgname.rfind('.');
    if (p != std::string::npos)
      pkgname= pkgname.substr(0, p);
    else
      pkgname= "";
    
    mforms::TreeNodeRef pkgnode= package_nodes[pkgname];
    if (!pkgnode)
    {
      pkgnode= _classes_tree.add_node();
      pkgnode->set_string(0, pkgname);
      pkgnode->set_icon_path(0, "folder");
      
      package_nodes[pkgname]= pkgnode;
    }
    
    mforms::TreeNodeRef node= pkgnode->add_child();
    node->set_tag((*iter)->get_attribute("desc"));
    node->set_string(0, (*iter)->name());
    node->set_string(2, (*iter)->get_attribute("caption"));
    node->set_icon_path(0, struct_icon);
    
    scan_class_members(node, *iter);
  }
}


std::string GRTShellWindow::get_class_node_description(const mforms::TreeNodeRef& selected)
{
  return selected->get_tag();
}

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

static bool find_expandable_member(const grt::MetaClass::Member *member, bool *expandable)
{
  if (!grt::is_simple_type(member->type.base.type))
    *expandable = true;
  return !*expandable;
}

static void globals_get_node_info(const grt::ValueRef &value, 
                                  std::string &type, 
                                  std::string &icon,
                                  bool &expandable)
{
  IconManager *im = IconManager::get_instance();
  type= grt::type_to_str(value.type());
  expandable = false;

  switch (value.type())
  {
    case grt::ListType:
    {
      grt::BaseListRef l(grt::BaseListRef::cast_from(value));
      std::string struct_name;
      if (l.content_type() != grt::AnyType)
      {
        type+= " [";
        if (l.content_type() == grt::ObjectType)
        {
          if (l.content_class_name().empty())
          {
            type+= "object";
            struct_name= "";
          }
          else
          {
            type+= "object:"+l.content_class_name();
            struct_name= l.content_class_name();
          }
        }
        else
        {
          if (l.content_type() == grt::AnyType)
            type+= "*";
          else
            type+= grt::type_to_str(l.content_type());
        }
        type+= "]";
      }
      if (!struct_name.empty())
        icon= im->get_icon_path(im->get_icon_id(l.get_grt()->get_metaclass(struct_name),
                                                Icon16, "many_$"));
      if (icon.empty())
        icon= im->get_icon_path("grt_list.png");
      
      for (size_t c= l.count(), i= 0; i < c; i++)
      {
        if (!grt::is_simple_type(l[i].type()))
        {
          expandable = true;
          break;
        }
      }      
    }
      break;
      
    case grt::DictType:
    {
      grt::DictRef d(grt::DictRef::cast_from(value));
      if (d.content_type() != grt::AnyType)
      {
        type+= " [";
        if (d.content_type() == grt::ObjectType)
        {
          type+= "object:"+d.content_class_name();
          icon= im->get_icon_path(im->get_icon_id(d.get_grt()->get_metaclass(d.content_class_name()), Icon16));
        }
        else
          type+= grt::type_to_str(d.content_type());
        type+= "]";
      }
      if (icon.empty())
        icon= im->get_icon_path("grt_dict.png");
      
      for (grt::DictRef::const_iterator iter= d.begin(); iter != d.end(); ++iter)
      {
        if (!grt::is_simple_type(iter->second.type()))
        {
          expandable = true;
          break;
        }
      }
    }
      break;
      
    case grt::ObjectType:
    {
      grt::ObjectRef o(grt::ObjectRef::cast_from(value));
      type+= ":" + std::string(o.class_name());
      
      icon= im->get_icon_path(im->get_icon_id(o, Icon16));
      if (icon.empty())
        icon= im->get_icon_path("grt_object.png");
      
      grt::MetaClass *meta= o.get_metaclass();
      
      meta->foreach_member(boost::bind(&find_expandable_member, _1, &expandable));
    }
      break;
      
    default:
      icon= im->get_icon_path("grt_simple_type.png");
      break;
  }
}

static void globals_rescan_list(mforms::TreeNodeRef &node,
                                const std::string &path,
                                const grt::BaseListRef &value)
{
  int c= value.count();
  char buffer[30];
  
  node->remove_children();
  for (int i= 0; i < c; i++)
  {
    grt::ValueRef v= value.get(i);
    std::string label;
    
    sprintf(buffer, "%i", i);
    
    if (v.is_valid() && !grt::is_simple_type(v.type()))
    {
      mforms::TreeNodeRef child = node->add_child();
      std::string type;
      std::string icon;
      bool expandable;
      
      globals_get_node_info(v, type, icon, expandable);
      child->set_tag(buffer);
      child->set_string(0, label.empty() ? buffer : label);
      child->set_string(1, type);
      child->set_icon_path(0, icon);
      if (v.type() == grt::ObjectType && label.empty())
      {
        grt::ObjectRef o(grt::ObjectRef::cast_from(v));
        std::string s = std::string("[")+buffer+"]";
        try
        {
          if (o.has_member("name") && o.get_string_member("name") != "")
            s.append(" ").append(o.get_string_member("name"));
        }
        catch (grt::type_error)
        {
          s.append(" ").append("?");
        }
        child->set_string(0, s);
      }
      
      if (expandable)
        child->add_child();
    }
  }
}


static void globals_rescan_dict(mforms::TreeNodeRef &node,
                                const std::string &path,
                                const grt::DictRef &value)
{
  node->remove_children();
  for (grt::DictRef::const_iterator item= value.begin(); item != value.end(); ++item)
  {
    std::string key(item->first);
    grt::ValueRef v(item->second);
    std::string label;
    
    if (v.is_valid() && !grt::is_simple_type(v.type()))
    {
      mforms::TreeNodeRef child= node->add_child();
      std::string type;
      std::string icon;
      bool expandable;
      
      globals_get_node_info(v, type, icon, expandable);
      child->set_tag(key);
      child->set_string(0, label.empty() ? key : label);
      child->set_string(1, type);
      child->set_icon_path(0, icon);
      if (v.type() == grt::ObjectType && label.empty())
      {
        grt::ObjectRef o(grt::ObjectRef::cast_from(v));
        if (o.has_member("name") && o.get_string_member("name") != "")
          child->set_string(0, o.get_string_member("name"));
        else
          child->set_string(0, "["+child->get_tag()+"]");
      }
      
      if (expandable)
        child->add_child();
    }
  }
}


static bool globals_rescan_member(const grt::MetaClass::Member *mem, mforms::TreeNodeRef &node, const grt::ObjectRef &value)
{
  std::string name(mem->name);
  grt::ValueRef v(value.get_member(name));
  std::string label;
  
  if (v.is_valid() && !is_simple_type(v.type()))
  {
    mforms::TreeNodeRef child = node->add_child();
    std::string type;
    std::string icon;
    bool expandable;
    
    globals_get_node_info(v, type, icon, expandable);
    child->set_tag(name);
    child->set_string(0, label.empty() ? name : label);
    child->set_string(1, type);
    child->set_icon_path(0, icon);
    if (expandable)
      child->add_child();
  }  
  return true;
}

static void globals_rescan_object(mforms::TreeNodeRef &node, 
                                  const std::string &path,
                                  const grt::ObjectRef &value)
{
  grt::MetaClass *meta= value.get_metaclass();
  
  node->remove_children();
  meta->foreach_member(boost::bind(&globals_rescan_member, _1, node, value));
}


static void globals_rescan_value(mforms::TreeNodeRef &node, 
                                 const std::string &path,
                                 const grt::ValueRef &value)
{
  switch (value.type())
  {
    case grt::ListType:
      globals_rescan_list(node, path, grt::BaseListRef::cast_from(value));
      break;
    case grt::DictType:
      globals_rescan_dict(node, path, grt::DictRef::cast_from(value));
      break;
    case grt::ObjectType:
      globals_rescan_object(node, path, grt::ObjectRef::cast_from(value));
      break;
    default:
      break;
  }
}


void GRTShellWindow::refresh_globals_tree()
{
  std::string path = _global_combo.get_string_value();
  
  if (path.empty())
    path = "/";
  
  try
  {
    grt::ValueRef value = _context->get_grt()->get(path);
    if (value.is_valid())
    {
      _global_tree.clear();

      mforms::TreeNodeRef root = _global_tree.add_node();
      std::string type;
      std::string icon;
      bool expandable;
      
      globals_get_node_info(value, type, icon, expandable);
      root->set_string(0, path);
      root->set_string(1, type);
      root->set_icon_path(0, icon);
      root->set_tag(path);
      globals_rescan_value(root, path, value);
//      root->expand();
    }
  }
  catch (const grt::bad_item)
  {
    // ignore
  }
}

void GRTShellWindow::globals_expand_toggle(const mforms::TreeNodeRef &node, bool expanded)
{
  if (expanded)
  {
    grt::ValueRef value = get_global_at_node(node);
    if (value.is_valid())
    {
      mforms::TreeNodeRef mnode = node;
      globals_rescan_value(mnode, mnode->get_tag(), value);
    }
  }
}

grt::ValueRef GRTShellWindow::get_global_at_node(const mforms::TreeNodeRef &node)
{
  return _context->get_grt()->get(get_global_path_at_node(node));
}


std::string GRTShellWindow::get_global_path_at_node(const mforms::TreeNodeRef &node)
{
  std::string path;
  mforms::TreeNodeRef parent = node;
  
  while (parent != _global_tree.root_node())
  {
    if (parent->get_tag() == "/")
      path = "/"+path;
    else
    {
      if (path.empty())
        path = parent->get_tag();
      else
        path = parent->get_tag()+"/"+path;
    }
    parent = parent->get_parent();
  }
  return path;
}


void GRTShellWindow::refresh_global_list()
{
  _global_list.clear();
  if (_inspector)
  {
    for (int c = _inspector->count(), i = 0; i < c; i++)
    {
      mforms::TreeNodeRef node = _global_list.add_node();
      std::string value;
      _inspector->get_field(i, 0, value);
      node->set_string(0, value);
      _inspector->get_field(i, 1, value);
      node->set_string(1, value);
      value = IconManager::get_instance()->get_icon_path(_inspector->get_field_icon(i, 0, Icon16));
      node->set_icon_path(0, value);
    }
  }
}


void GRTShellWindow::refresh_notifs_list()
{
  const std::map<std::string, base::NotificationCenter::NotificationHelp> &info = base::NotificationCenter::get()->get_registered_notifications();
  std::map<std::string, std::vector<std::string> > contexts;
  
  _notifs_tree.clear();
  for (std::map<std::string, base::NotificationCenter::NotificationHelp>::const_iterator i = info.begin();
       i != info.end(); ++i)
    contexts[i->second.context].push_back(i->first);

  for (std::map<std::string, std::vector<std::string> > ::const_iterator iter = contexts.begin();
       iter != contexts.end(); ++iter)
  {
    mforms::TreeNodeRef node = _notifs_tree.add_node();
    node->set_string(0, iter->first);
    node->set_icon_path(0, "folder");
    
    for (std::vector<std::string>::const_iterator n = iter->second.begin(); n != iter->second.end(); ++n)
    {
      mforms::TreeNodeRef nnode = node->add_child();
      nnode->set_string(0, *n);
    }
    node->expand();
  }
}

