/* 
 * Copyright (c) 2009, 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 "wb_context_model.h"
#include "workbench/wb_context_ui.h"
#include "workbench/wb_command_ui.h"
#include "wb_component.h"
#include "wb_component_basic.h"
#include "wb_component_physical.h"
#include "wb_component_logical.h"
#include "model/wb_model_diagram_form.h"
#include "wb_overview_physical.h"
#include "wb_overview_physical_schema.h"
#include "workbench/wb_model_file.h"
#include "model/wb_overview_physical.h"
#include "wbcanvas/model_diagram_impl.h"
#include "wbcanvas/workbench_physical_model_impl.h"
#include "workbench/wb_model_file.h"

#include "grt/clipboard.h"
#include "grt/common.h"
#include "base/file_utilities.h"
#include "base/file_functions.h"
#include "base/string_utilities.h"

#include "mforms/utilities.h"
#include "mforms/menubar.h"

using namespace base;
using namespace bec;
using namespace wb;
using namespace MySQL::Geometry;

static std::map<std::string, std::string> auto_save_files;


WBContextModel::WBContextModel(WBContextUI *wbui)
: _wbui(wbui), _file(0), _auto_save_point(0), _last_auto_save_time(0), _auto_save_timer(NULL)

{  
  _overview= new PhysicalOverviewBE(_wbui->get_wb());
  
  scoped_connect(_wbui->get_wb()->get_grt_manager()->get_clipboard()->signal_changed(),boost::bind(&WBContextModel::selection_changed, this));
  scoped_connect(_overview->signal_selection_changed(),boost::bind(&WBContextModel::selection_changed, this)); // make edit menu captions to update

  CommandUI *cmdui = wbui->get_command_ui();
  boost::function<bool ()> validate = boost::bind(&WBContextModel::has_selected_schema, this);
  cmdui->add_builtin_command("addModelDiagram",
                             boost::bind(&WBContextModel::add_model_diagram, this),
                             boost::bind(&WBContextModel::has_selected_model, this));
  cmdui->add_builtin_command("addModelSchema",
                             boost::bind(&WBContextModel::add_model_schema, this),
                             boost::bind(&WBContextModel::has_selected_model, this));
  cmdui->add_builtin_command("addModelTable",
                             boost::bind(&WBContextModel::add_model_table, this),
                             validate);
  cmdui->add_builtin_command("addModelView",
                             boost::bind(&WBContextModel::add_model_view, this),
                             validate);
  cmdui->add_builtin_command("addModelRoutine",
                             boost::bind(&WBContextModel::add_model_rgroup, this),
                             validate);
  
  base::NotificationCenter::get()->add_observer(this, "GNMainFormChanged");
  
  // setup auto-save for model
  int interval = _wbui->get_wb()->get_root()->options()->options().get_int("workbench:AutoSaveModelInterval", 60);
  if (interval > 0)
    _auto_save_timer = _wbui->get_wb()->get_grt_manager()->run_every(boost::bind(&WBContextModel::auto_save_document, this), interval);
  _auto_save_interval = interval;
  
  scoped_connect(_wbui->get_wb()->get_root()->options()->signal_dict_changed(),boost::bind(&WBContextModel::option_changed, this, _1, _2, _3));
}


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

  if (_doc.is_valid() && _doc->physicalModels().is_valid() && _doc->physicalModels().count() > 0)
    _doc->physicalModels().get(0)->get_data()->set_delegate(NULL);

  if (_auto_save_timer)
    _wbui->get_wb()->get_grt_manager()->cancel_timer(_auto_save_timer);
  CommandUI *cmdui = _wbui->get_command_ui();
  cmdui->remove_builtin_command("addModelDiagram");
  cmdui->remove_builtin_command("addModelSchema");
  cmdui->remove_builtin_command("addModelTable");
  cmdui->remove_builtin_command("addModelView");
  cmdui->remove_builtin_command("addModelRoutine");

  _file= 0;
  delete _overview;
}


void WBContextModel::option_changed(grt::internal::OwnedDict*dict, bool, const std::string&key)
{
  if (key == "workbench:AutoSaveModelInterval" && dict == _wbui->get_wb()->get_wb_options().valueptr())
  {
    auto_save_document();
  }
}


bool WBContextModel::auto_save_document()
{
  WBContext *wb= _wbui->get_wb();
  int interval= wb->get_root()->options()->options().get_int("workbench:AutoSaveModelInterval", 60);
  if (interval <= 0)
    return false;
  
  workbench_DocumentRef doc(wb->get_document());
    
  mdc::Timestamp now= mdc::get_time();
  if (now - _last_auto_save_time > interval
      && _file
      && doc.is_valid() 
      && !wb->get_grt_manager()->get_dispatcher()->get_busy()
      && wb->get_grt()->get_undo_manager()->get_latest_closed_undo_action() != _auto_save_point
      && !wb->get_filename().empty())
  {
    _auto_save_point = wb->get_grt()->get_undo_manager()->get_latest_closed_undo_action();
    _last_auto_save_time = now;
    
    try
    {
      // save the document in the same directory containing the expanded mwb file
      _file->store_document_autosave(wb->get_grt(), doc);
    } 
    catch (std::exception &exc)
    {
      wb->show_exception(_("Could not store document data to autosave file."), exc);
    }
  }
  
  if (interval != _auto_save_interval)
  {
    if (_auto_save_timer)
        wb->get_grt_manager()->cancel_timer(_auto_save_timer);
    // schedule new interval
    _auto_save_timer = wb->get_grt_manager()->run_every(boost::bind(&WBContextModel::auto_save_document, this), interval);
    return false;
  }
  
  return true;
}


void WBContextModel::detect_auto_save_files(const std::string &autosave_dir)
{
  std::map<std::string, std::string> files;

  // look for .mwbd folders with autosave files
  std::list<std::string> autosaves;
  
  try
  {
    autosaves = base::scan_for_files_matching(bec::make_path(autosave_dir, "*.mwbd*"));
  }
  catch (const std::runtime_error &)
  {
    return;
  }

  for (std::list<std::string>::const_iterator d = autosaves.begin(); d != autosaves.end(); ++d)
  { 
    if (base::LockFile::check(bec::make_path(*d, ModelFile::lock_filename.c_str())) != base::LockFile::NotLocked)
      continue;
    
    if (g_file_test(bec::make_path(*d, MAIN_DOCUMENT_AUTOSAVE_NAME).c_str(), G_FILE_TEST_EXISTS))
    {
      gchar *orig_path;
      gsize length;
      if (g_file_get_contents(bec::make_path(*d, "real_path").c_str(),
                              &orig_path, &length, NULL))
      {
        files[std::string(orig_path, length)] = *d;
        g_free(orig_path);
      }
      else
      {
        std::string fname = g_basename(d->c_str());
        fname = fname.substr(0, fname.rfind('.')).append(".mwb");
        // if no real_path file in the autosave, this could be an autosave from an older version
        files[fname] = *d;
      }
    }
    else
    {
      g_message("Found model auto-save %s, but it is empty. Deleting it...", d->c_str());
      base_rmdir_recursively(d->c_str());
    }
  }
  ::auto_save_files = files;
}

std::map<std::string, std::string> WBContextModel::auto_save_files()
{
  return ::auto_save_files;
}


void WBContextModel::unrealize()
{
  _page_settings_conn.disconnect();
  
  // unrealize all models
  if (_doc.is_valid() && _doc->physicalModels().is_valid())
  {
    for (size_t c= _doc->physicalModels().count(), i= 0; i < c; i++)
    {
      _doc->physicalModels().get(i)->get_data()->unrealize();
    }
  }  
}


model_DiagramRef WBContextModel::get_active_model_diagram(bool main_form)
{
  bec::UIForm *form= main_form ? get_wbui()->get_active_main_form() : get_wbui()->get_active_form();
  
  if (dynamic_cast<ModelDiagramForm*>(form))
    return dynamic_cast<ModelDiagramForm*>(form)->get_model_diagram();
  
  return model_DiagramRef();
}


model_ModelRef WBContextModel::get_active_model(bool main_form)
{
  bec::UIForm *form= main_form ? get_wbui()->get_active_main_form() : get_wbui()->get_active_form();
  
  if (dynamic_cast<OverviewBE*>(form))
    return dynamic_cast<OverviewBE*>(form)->get_model();
  else if (dynamic_cast<ModelDiagramForm*>(form))
    return dynamic_cast<ModelDiagramForm*>(form)->get_model_diagram()->owner();
  return model_ModelRef();
}



void WBContextModel::model_created(ModelFile *file, workbench_DocumentRef doc)
{
  _file= file;
  _doc= doc;

  _wbui->get_wb()->get_component<WBComponentLogical>()->setup_logical_model(doc.get_grt(), _doc);
  _wbui->get_wb()->get_component<WBComponentPhysical>()->setup_physical_model(doc.get_grt(), _doc, "Mysql", "5.1.22");
  
  _wbui->get_wb()->foreach_component(boost::bind(&WBComponent::reset_document, _1));
  
  _doc->physicalModels().get(0)->get_data()->set_delegate(this);
  
  _doc->physicalModels()[0]->get_data()->realize();
  
  _wbui->get_wb()->request_refresh(RefreshNewModel, "", 0);
}


void WBContextModel::model_loaded(ModelFile *file, workbench_DocumentRef doc)
{
  _file= file;
  _doc= doc;
  
  _wbui->get_wb()->foreach_component(boost::bind(&WBComponent::reset_document, _1));
  
  _wbui->get_wb()->foreach_component(boost::bind(&WBComponent::document_loaded, _1));
  
  _doc->physicalModels().get(0)->get_data()->set_delegate(this);
    
  _wbui->get_wb()->request_refresh(RefreshNewModel, "", 0);
  
  std::string temp_dir = _file->get_tempdir_path();
  for (std::map<std::string, std::string>::iterator iter= ::auto_save_files.begin();
       iter != ::auto_save_files.end(); ++iter)
  {
    if (iter->second == temp_dir)
    {
      ::auto_save_files.erase(iter);
      _wbui->refresh_home_models();
      break;
    }
  }
}


void WBContextModel::realize()
{
  _page_settings_conn = _doc->pageSettings()->signal_changed()->connect(boost::bind(&WBContextModel::page_settings_changed, this, _1, _2));
  
  _doc->physicalModels()[0]->get_data()->realize();
}


void WBContextModel::page_settings_changed(const std::string &field, const grt::ValueRef &value)
{
  if (field == "paperType")
  {
    update_page_settings();
  }
}




/** 
 ****************************************************************************
 * @brief Update the canvas view according to app.PageSettings 
 *
 * This will update the page size and total view size to reflect changes
 * to the page/print settings.
 ****************************************************************************
 */
void WBContextModel::update_page_settings()
{
  if (!_doc.is_valid() || !_doc->logicalModel().is_valid())
    return;
  
  grt::ListRef<model_Diagram> views(grt::ListRef<model_Diagram>::cast_from(_doc->logicalModel()->diagrams()));
  for (size_t vc= views.count(), v= 0; v < vc; v++)
  {
    views[v]->get_data()->update_size();
  }

  grt::ListRef<workbench_physical_Model> models(_doc->physicalModels());
  for (size_t c= models.count(), i= 0; i < c; i++)
  {
    views= grt::ListRef<model_Diagram>::cast_from(models[i]->diagrams());
    for (size_t vc= views.count(), v= 0; v < vc; v++)
    {
      views[v]->get_data()->update_from_page_size();
    }
  }
}


cairo_surface_t *WBContextModel::fetch_image(const std::string &file)
{
  return _wbui->get_wb()->get_file()->get_image(file);
}


std::string WBContextModel::attach_image(const std::string &file)
{
  return _wbui->get_wb()->get_file()->add_image_file(file);
}


void WBContextModel::release_image(const std::string &file)
{
  // QQQ
  // _wbui->get_wb()->get_file()->release_image(file);
}


/** 
 ****************************************************************************
 * @brief Delegate method for creating canvas views
 *
 * This is called by the GRT bridge when a new view object is created.
 * It will in turn, call a frontend supplied callback which should create
 * the canvas view (and its viewer) and then return the canvas view.
 *
 * The free_canvas_view() method will be called once the view can be freed.
 * 
 * @param name the name of the canvas. Will be the object-id of the view.
 *****************************************************************************/
mdc::CanvasView *WBContextModel::create_diagram(const model_DiagramRef &view)
{
  return _wbui->get_wb()->execute_in_main_thread<mdc::CanvasView*>("create_diagram", boost::bind(&WBContextModel::create_diagram_main, this, view));
}

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

/**
 * Notification trigger for canvas view destructions. Can be called via two different paths.
 * If called from the front end (because the UI caused closing the editor) then the diagram form
 * is already unregistered and we don't need to call the UI again (it was the trigger after all).
 */
void WBContextModel::free_canvas_view(mdc::CanvasView *view)
{
  ModelDiagramForm* diagram = get_diagram_form(view);
  if (diagram != NULL)
  {
    // This function is expected to be called from the main thread.
    notify_diagram_destroyed(diagram);

    // Notify front end so it can close its editor for this view.
    if (_wbui->get_wb()->get_grt_manager()->in_main_thread())
      _wbui->get_wb()->destroy_view(view);
    else
      _wbui->get_wb()->execute_in_main_thread<void>("destroy view", 
      boost::bind(_wbui->get_wb()->destroy_view, view));
  }
}

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

mdc::CanvasView *WBContextModel::create_diagram_main(const model_DiagramRef &diagram_reference)
{
  ModelDiagramForm *diagram= 0;
  WBContext *wb= _wbui->get_wb();
  
  FOREACH_COMPONENT(wb->_components, iter)
  {
    if (diagram_reference.is_instance((*iter)->get_diagram_class_name())
        && (*iter)->get_diagram_class_name() != model_Diagram::static_class_name())
    {
      diagram= new ModelDiagramForm(*iter, diagram_reference);
      break;
    }
  }
  
  // fallback
  if (!diagram)
    diagram= new ModelDiagramForm(wb->get_component_named("basic"), diagram_reference);
  
  if (!diagram)
  {
    mforms::Utilities::show_error("Internal error adding a new diagram.", "Unknown diagram type.", _("Close"));
    return 0;
  }
  
  scoped_connect(diagram_reference->signal_objectActivated(),(boost::bind(&WBContextModel::activate_canvas_object, this, _1, _2)));
    
  scoped_connect(diagram_reference->signal_list_changed(),boost::bind(&WBContextModel::diagram_object_list_changed, this, _1, _2, _3, diagram));
  
  register_diagram_form(diagram);
  
  // Forward creation to front end.
  mdc::CanvasView *view= wb->create_diagram(diagram_reference);
  if (view)
  {
    diagram->attach_canvas_view(view);
    
    notify_diagram_created(diagram);
    
    // use this signal instead of selection_change from canvas so that when the callback is called
    // the grt diagram object already reflects the selection changes
    scoped_connect(diagram_reference->get_data()->signal_selection_changed(),boost::bind(&WBContextModel::selection_changed, this));

    wb->request_refresh(RefreshNewDiagram, diagram_reference.id(), (NativeHandle)view->get_user_data());
  }
  else
  {
    delete diagram;
    mforms::Utilities::show_error("Internal error adding a new diagram.", "Frontend did not return a diagram.", _("Close"));
  }
  
  if (getenv("DEBUG_CANVAS"))
    view->enable_debug(true);
  
  return view;
}



void WBContextModel::activate_canvas_object(const model_ObjectRef &object, int flags)
{
  bool newwindow= flags & 1;
  
  FOREACH_COMPONENT(_wbui->get_wb()->_components, iter)
  {
    if ((*iter)->handles_figure(object))
      (*iter)->activate_canvas_object(object, newwindow);
  }
}


void WBContextModel::register_diagram_form(ModelDiagramForm *view)
{
  _model_forms[view->get_model_diagram().id()]= view;
}


ModelDiagramForm *WBContextModel::get_diagram_form(mdc::CanvasView *view)
{
  for (std::map<std::string,ModelDiagramForm*>::const_iterator iter= _model_forms.begin();
       iter != _model_forms.end(); ++iter)
  {
    if (iter->second->get_view() == view)
      return iter->second;
  }
  return 0;
}


void WBContextModel::notify_diagram_created(ModelDiagramForm *view)
{
    scoped_connect(view->get_model_diagram()->signal_changed(),
        boost::bind(&WBContextModel::diagram_object_changed, this, _1, _2, view));
    
  // now called from wb_component_physical.cpp:model_list_changed
  //_wbui->get_physical_overview()->send_refresh_diagram(model_DiagramRef());
}


void WBContextModel::notify_diagram_destroyed(ModelDiagramForm *diagram)
{
  if (diagram != NULL)
  {
    std::string id= diagram->get_model_diagram().id();
    delete diagram;
    _model_forms.erase(id);
  }

  // now called from wb_component_physical.cpp:model_list_changed
  //_wbui->get_physical_overview()->send_refresh_diagram(model_DiagramRef());
}


void WBContextModel::handle_notification(const std::string &name, void *sender, base::NotificationInfo &info)
{
  if (name == "GNMainFormChanged")
    update_current_diagram(_wbui->get_active_main_form());
}


void WBContextModel::update_current_diagram(bec::UIForm *form)
{
  ModelDiagramForm* dform= dynamic_cast<ModelDiagramForm*>(form);
  if (dform)
  {
    model_DiagramRef diagram(dform->get_model_diagram());
    if (diagram.is_valid() && diagram->owner().is_valid())
      diagram->owner()->currentDiagram(diagram);    
    
    (*_wbui->get_command_ui()->signal_validate_edit_menu_items())();
  }
}


void WBContextModel::diagram_object_changed(const std::string &member, const grt::ValueRef &ovalue, ModelDiagramForm *view)
{
  if (member == "name")
  {
    if (view->get_model_diagram().is_valid())
    {
      _wbui->get_wb()->request_refresh(RefreshViewName, view->get_model_diagram()->name(), (NativeHandle)view->get_view()->get_user_data());
      _wbui->get_physical_overview()->send_refresh_diagram(view->get_model_diagram());
    }
  }
  else if (member == "zoom")
  {
    _wbui->get_wb()->request_refresh(RefreshZoom, "");
  }
}


void WBContextModel::diagram_object_list_changed(grt::internal::OwnedList *list, bool added, const grt::ValueRef &value, ModelDiagramForm *vform)
{
  if (vform == _wbui->get_active_main_form())
  {
    if (vform->get_model_diagram()->selection().valueptr() == list)
      _wbui->get_wb()->request_refresh(RefreshSelection, "", reinterpret_cast<NativeHandle>(vform->get_frontend_data()));
  }
}


bool WBContextModel::has_selected_schema()
{
  PhysicalOverviewBE *active_form = dynamic_cast<PhysicalOverviewBE*>(_wbui->get_active_main_form());
  if (active_form == _overview && _overview->get_active_schema_node())
    return true;
  
  return false;
}

bool WBContextModel::has_selected_model()
{
  if (_wbui->get_active_main_form() == _overview)
    return true;
  return false;
}

void WBContextModel::add_model_schema()
{
  _wbui->get_wb()->get_component<WBComponentPhysical>()->add_new_db_schema(workbench_physical_ModelRef::cast_from(get_active_model(true)));
}

void WBContextModel::add_model_diagram()
{
  _wbui->get_wb()->add_new_diagram(get_active_model(true));
}

void WBContextModel::add_model_table()
{
  _overview->get_active_schema_node()->add_new_db_table(_wbui->get_wb());
}


void WBContextModel::add_model_view()
{
  _overview->get_active_schema_node()->add_new_db_view(_wbui->get_wb());
}


void WBContextModel::add_model_rgroup()
{
  _overview->get_active_schema_node()->add_new_db_routine(_wbui->get_wb());
}


void WBContextModel::update_plugin_arguments_pool(ArgumentPool &args)
{
  model_ModelRef model(get_active_model(true));

  if (!model.is_valid())
    return;

  args.add_entries_for_object("", model, "model.Model");
  args.add_entries_for_object("activeModel", model, "model.Model");

  if (workbench_physical_ModelRef::can_wrap(model))
  {
    workbench_physical_ModelRef pmodel(workbench_physical_ModelRef::cast_from(model));
    args.add_entries_for_object("", pmodel->catalog(), "db.Catalog");
    args.add_entries_for_object("activeCatalog", pmodel->catalog(), "db.Catalog");
  }
  
  ModelDiagramForm *view;
  model_DiagramRef diagram(get_active_model_diagram(false));
  if (!diagram.is_valid()) // in case an editor in a diagram tab is active
  {
    diagram = get_active_model_diagram(true);
    view = dynamic_cast<ModelDiagramForm*>(_wbui->get_active_main_form());
  }
  else
    view= dynamic_cast<ModelDiagramForm*>(_wbui->get_active_form());
  
  if (diagram.is_valid())
  {
    args.add_entries_for_object("", diagram, "model.Diagram");
    args.add_entries_for_object("activeDiagram", diagram, "model.Diagram");
    
    if (view)
    {
      // diagram selection
      grt::ListRef<model_Object> selection(view->get_selection());

      args.add_list_for_selection("activeDiagram", selection);
      
      if (selection.count() == 1)
      {
        model_ObjectRef object(selection[0]);
      
        //args.add_entries_for_object("", object, "model.Object");
        args.add_entries_for_object("", object, "GrtObject");
        
        // check if object represented by this is wanted
        FOREACH_COMPONENT(_wbui->get_wb()->_components, iter)
        {
          if ((*iter)->handles_figure(object))
          {
            grt::ObjectRef fobject((*iter)->get_object_for_figure(object));
            if (fobject.is_valid())
              args.add_entries_for_object("", fobject, "db.DatabaseObject");
          }
        }
      }
    }
  }

  // overview selection
  OverviewBE *overview= dynamic_cast<OverviewBE*>(_wbui->get_active_form());
  if (overview)
  {
    grt::ListRef<GrtObject> selection(overview->get_selection());
    
    if (selection.count() == 1)
    {
      GrtObjectRef object(selection[0]);
      
      args.add_entries_for_object("", object, "GrtObject");
    }
  }
}


/**
 * Adds a list of common menu entries to the menu item list. Returns the number of items added.
 */
int WBContextModel::get_object_list_popup_items(bec::UIForm *form, 
                                                const std::vector<bec::NodeId> &nodes,
                                                const grt::ListRef<GrtObject> &objects,
                                                const std::string &label, const std::list<std::string> &groups,
                                                bec::MenuItemList &items)
{
  size_t initial_count= items.size();
  bec::TreeModel* model= dynamic_cast<bec::TreeModel*>(form);
  WBContext *wb= _wbui->get_wb();

  // Start with clipboard commands.
  // First check if all items in the selection list are deletable (used for cut and delete items).
  bec::MenuItem item;
  bool can_delete= objects->count() > 0;
  bool can_copy= can_delete;
  if (model != NULL)
    for (std::vector<bec::NodeId>::const_iterator iterator= nodes.begin(); iterator != nodes.end(); iterator++)
    {
      if (can_delete && !model->is_deletable(*iterator))
        can_delete= false;
      if (can_copy && !model->is_copyable(*iterator))
        can_copy= false;
    }
  
  item.checked= false;
  item.enabled= can_copy && can_delete;
  item.caption= label.empty() ? _("Cut") : strfmt(_("Cut %s"), label.c_str());
  item.name= "builtin:cut";
  item.type= MenuAction;
  items.push_back(item);
  
  item.enabled= can_copy;
  item.caption= label.empty() ? _("Copy") : strfmt(_("Copy %s"), label.c_str());
  item.name= "builtin:copy";
  item.type= MenuAction;
  items.push_back(item);
  
  item.enabled= form->can_paste();
  item.caption= strfmt(_("Paste %s"), wb->get_clipboard()->get_content_description().c_str());
  item.name= "builtin:paste";
  item.type= MenuAction;
  items.push_back(item);
  
  item.type= MenuSeparator;
  items.push_back(item);
  
  // Plugin dependent menu entries.
  size_t old_count= items.size();
  
  if (objects.count() > 0)
  {
    add_object_plugins_to_popup_menu(objects, groups, items);
    
    grt::ListRef<GrtObject> model_objects(wb->get_grt());
    for (size_t c= objects.count(), i= 0; i < c; i++)
    {
      if (objects[i].is_instance(model_Object::static_class_name()))
      {
        FOREACH_COMPONENT(wb->_components, compo)
        {
          GrtObjectRef model_object((*compo)->get_object_for_figure(model_ObjectRef::cast_from(objects[i])));
          
          if (model_object.is_valid())
          {
            model_objects.insert(model_object);
            break;
          }
        }
      }
    }
    
    if (model_objects.count() > 0)
      add_object_plugins_to_popup_menu(model_objects, groups, items);
  }
  
  // At the end of the list there is the delete command.
  item.checked= false;
  item.enabled= can_delete;
  
  // Add a separator item if we added plugin specific menu commands above.
  if (old_count != items.size())
  {
    item.type= MenuSeparator;
    items.push_back(item);
  }
  item.caption= label.empty() ? _("Delete") : strfmt(_("Delete %s"), label.c_str());
  item.name= "builtin:delete";
  item.type= MenuAction;
  items.push_back(item);
  
  return int(items.size() - initial_count);
}


struct sortplugin
{
  bool operator ()(const app_PluginRef &a, const app_PluginRef &b) const
  {
    return a->rating() < b->rating();
  }
};


int WBContextModel::add_object_plugins_to_popup_menu(const grt::ListRef<GrtObject> &objects,
                                                     const std::list<std::string> &groups,
                                                     bec::MenuItemList &items)
{
  bec::ArgumentPool argpool;
  _wbui->get_wb()->update_plugin_arguments_pool(argpool);
  if (objects.count() > 0)
    argpool.add_entries_for_object("", objects[0], "GrtObject");

  int count= 0;
  // look for plugins that take this object type as input
  std::vector<app_PluginRef> plugins(_wbui->get_wb()->get_plugin_manager()->get_plugins_for_objects(grt::ObjectListRef::cast_from(objects)));

  // sort by rating  
  std::sort(plugins.begin(), plugins.end(), sortplugin());
  
  for (std::vector<app_PluginRef>::const_iterator iter= plugins.begin(); iter != plugins.end(); ++iter)
  {
    bool match= false;
    //    bool matchPrefix= false;
    
    for (size_t c= (*iter)->groups().count(), i= 0; i < c; i++)
    {
      std::string str= (*iter)->groups().get(i);
      for (std::list<std::string>::const_iterator ctx= groups.begin();
           ctx != groups.end(); ++ctx)
      {
        if ((*ctx)[ctx->size()-1] == '*' && g_strncasecmp(ctx->c_str(), str.c_str(), (guint) ctx->size() - 1) == 0)
        {
          match= true;
          break;
        }
        else if ((*ctx)[ctx->size() - 1] != '*' && g_strcasecmp(ctx->c_str(), str.c_str()) == 0)
        {
          match= true;
          break;
        }
      }
    }
    if (!match)
      continue;
    
    bec::MenuItem item;
    
    item.type= MenuAction;
    item.caption= *(*iter)->caption() + ((*iter)->pluginType()=="gui"?"...":"");
    item.checked= false;
    item.enabled= _wbui->get_wb()->get_grt_manager()->check_plugin_runnable(*iter, argpool);
    item.shortcut= "";
    item.name= "plugin:"+*(*iter)->name();
    if (item.caption.empty())
      item.caption= item.name;
    items.push_back(item);
    count++;
    
    if ((*iter)->groups().get_index("catalog/Editors") != grt::BaseListRef::npos ||
        (*iter)->groups().get_index("model/Editors") != grt::BaseListRef::npos)
    {
      app_PluginRef plugin(_wbui->get_wb()->get_plugin_manager()->get_plugin("wb.edit.editSelectedFigureInNewWindow"));
      
      item.caption= _("Edit in New Window...");
      item.name= "plugin:"+*plugin->name();
      item.type= MenuAction;
      // state for this item will be the same as for the previous one
      //item.enabled= check_plugin_runnable(plugin, "", objects);
      items.push_back(item);
      count++;
    }
  }
  
  return count;
}


void WBContextModel::history_changed()
{
  std::string undo_description(_wbui->get_wb()->get_grt()->get_undo_manager()->undo_description());
  std::string redo_description(_wbui->get_wb()->get_grt()->get_undo_manager()->redo_description());

  std::list<bec::UIForm*> forms;
  forms.push_back(_overview);
  for (std::map<std::string, ModelDiagramForm*>::const_iterator iter= _model_forms.begin();
       iter != _model_forms.end(); ++iter)
    forms.push_back(iter->second);
  
  mforms::MenuItem *item;
  
  for (std::list<bec::UIForm*>::const_iterator iter = forms.begin(); iter != forms.end(); ++iter)
  {
    bec::UIForm *form = *iter;
    mforms::MenuBar *menu = form->get_menubar();
    if (menu)
    {
      item = menu->find_item("undo");
      if (item)
      {
        if (!undo_description.empty())
          item->set_title(strfmt(_("Undo %s"), undo_description.c_str()));
        else
          item->set_title(_("Undo"));
      }
      item = menu->find_item("redo");
      if (item)
      {
        if (!redo_description.empty())
          item->set_title(strfmt(_("Redo %s"), redo_description.c_str()));
        else
          item->set_title(_("Redo"));
      }
    }      
  }
}


void WBContextModel::selection_changed()
{
  if (!_wbui->get_wb()->get_grt_manager()->in_main_thread())
  {
    _wbui->get_wb()->get_grt_manager()->run_once_when_idle(boost::bind(&WBContextModel::selection_changed, this));
    return;
  }
    
  bec::Clipboard *clip = _wbui->get_wb()->get_clipboard();
    
  std::list<bec::UIForm*> forms;
  forms.push_back(_overview);
  for (std::map<std::string, ModelDiagramForm*>::const_iterator iter= _model_forms.begin();
       iter != _model_forms.end(); ++iter)
    forms.push_back(iter->second);
  
  mforms::MenuItem *item;

  for (std::list<bec::UIForm*>::const_iterator iter = forms.begin(); iter != forms.end(); ++iter)
  {
    bec::UIForm *form = *iter;
    mforms::MenuBar *menu = form->get_menubar();
    if (menu)
    {
      std::string target(form->get_edit_target_name());
      std::string content(clip->get_content_description());
      
      item = menu->find_item("copy");
      if (item)
      {
        if (!target.empty())
          item->set_title(strfmt(_("Copy %s"), target.c_str()));
        else
          item->set_title(_("Copy"));
      }
      
      item = menu->find_item("cut");
      if (item)
      {
        if (!target.empty())
          item->set_title(strfmt(_("Cut %s"), target.c_str()));
        else
          item->set_title(_("Cut"));
      }
      
      item = menu->find_item("delete");
      if (item)
      {
        if (!target.empty())
          item->set_title(strfmt(_("Delete %s"), target.c_str()));
        else
          item->set_title(_("Delete"));
      }

      item = menu->find_item("paste");
      if (item)
      {
        if (!content.empty())
          item->set_title(strfmt(_("Paste %s"), content.c_str()));
        else
          item->set_title(_("Paste"));
      }
    }
  }
  (*_wbui->get_command_ui()->signal_validate_edit_menu_items())();
}



#ifndef BasicExport____

void WBContextModel::export_png(const std::string &path)
{
  ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wbui->get_active_main_form());
  if (form)
  {
    _wbui->get_wb()->show_status_text(strfmt(_("Exporting to %s..."), path.c_str()));
    try
    {
      form->get_view()->export_png(path, true);
      _wbui->get_wb()->show_status_text(strfmt(_("Exported diagram image to %s"), path.c_str()));
    }
    catch (const std::exception &exc)
    {
      _wbui->get_wb()->show_status_text(_("Could not export to PNG file."));
      _wbui->get_wb()->show_exception(_("Export to PNG"), exc);
    }
  }
  else
    _wbui->get_wb()->show_error(_("Cannot Export Diagram"), _("Current diagram cannot be exported as image, please select a diagram first."));
}


void WBContextModel::export_pdf(const std::string &path)
{
  ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wbui->get_active_main_form());
  if (form)
  {
    Size size= form->get_view()->get_total_view_size();
    double scale= _wbui->get_wb()->get_document()->pageSettings()->scale();
    
    size.width= size.width / scale * 2.834;
    size.height= size.height / scale * 2.834;
    
    _wbui->get_wb()->show_status_text(strfmt(_("Exporting full model diagram to %s..."), path.c_str()));
    
    try
    {
      form->get_view()->export_pdf(path, size);
      _wbui->get_wb()->show_status_text(strfmt(_("Exported PDF to %s"), path.c_str()));
    }
    catch (const std::exception &exc)
    {
      _wbui->get_wb()->show_status_text(_("Could not export to PDF"));
      _wbui->get_wb()->show_exception(_("Export to PDF"), exc);
    }
  }
  else
    _wbui->get_wb()->show_error(_("Cannot Export Diagram"), _("Current diagram cannot be exported as image, please select a diagram first."));
}


void WBContextModel::export_svg(const std::string &path)
{
  ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wbui->get_active_main_form());
  if (form)
  {
    Size size= form->get_view()->get_total_view_size();
    double scale= _wbui->get_wb()->get_document()->pageSettings()->scale();
    
    size.width= MM_TO_PT(size.width / scale);
    size.height= MM_TO_PT(size.height / scale);
    
    _wbui->get_wb()->show_status_text(strfmt(_("Exporting full model diagram to %s..."), path.c_str()));
    
    try
    {
      form->get_view()->export_svg(path, size);
      _wbui->get_wb()->show_status_text(strfmt(_("Exported SVG to %s"), path.c_str()));
    }
    catch (const std::exception &exc)
    {
      _wbui->get_wb()->show_status_text(_("Could not export to SVG"));
      _wbui->get_wb()->show_exception(_("Export to SVG"), exc);
    }
  }
  else
    _wbui->get_wb()->show_error(_("Cannot Export Diagram"), _("Current diagram cannot be exported as image, please select a diagram first."));
}


void WBContextModel::export_ps(const std::string &path)
{
  ModelDiagramForm *form= dynamic_cast<ModelDiagramForm*>(_wbui->get_active_main_form());
  if (form)
  {
    Size size= form->get_view()->get_total_view_size();
    double scale= _wbui->get_wb()->get_document()->pageSettings()->scale();
    
    size.width= MM_TO_PT(size.width / scale);
    size.height= MM_TO_PT(size.height / scale);
    
    _wbui->get_wb()->show_status_text(strfmt(_("Exporting full model diagram to %s..."), path.c_str()));
    
    try
    {
      form->get_view()->export_ps(path, size);
      _wbui->get_wb()->show_status_text(strfmt(_("Exported PS to %s"), path.c_str()));
    }
    catch (std::exception &exc)
    {
      _wbui->get_wb()->show_status_text(_("Could not export to PS file"));
      _wbui->get_wb()->show_exception(_("Export to PS File"), exc);
    }
  }
  else
    _wbui->get_wb()->show_error(_("Cannot Export Diagram"), _("Current diagram cannot be exported as image, please select a diagram first."));
}

#endif // BasicExport____
