/* 
 * Copyright (c) 2009, 2013, 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 "model/wb_user_datatypes.h"
#include "model/wb_history_tree.h"
#include "wbcanvas/model_diagram_impl.h"
#include "wbcanvas/workbench_physical_model_impl.h"
#include "workbench/wb_model_file.h"
#include "grtdb/db_object_helpers.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 "user_defined_type_editor.h"

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


using namespace base;
using namespace bec;
using namespace wb;
using namespace base;

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


WBContextModel::WBContextModel(WBContextUI *wbui)
: _wbui(wbui), _file(0), _current_user_type_editor(0), _locked_view_for_plugin_exec(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);

  cmdui->add_builtin_command("removeFigure",
                             boost::bind(&WBContextModel::remove_figure, this),
                             boost::bind(&WBContextModel::has_selected_figures, this));
  
  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");
  cmdui->remove_builtin_command("removeFigure");
  _file= 0;
  delete _overview;
}


mforms::TreeNodeView *WBContextModel::create_user_type_list()
{
  UserDatatypeList *type_list;

  type_list = new UserDatatypeList(_wbui->get_wb());
  type_list->set_catalog(_wbui->get_wb()->get_document()->physicalModels()[0]->catalog());
  type_list->refresh();

  type_list->scoped_connect(&_udt_list_changed, boost::bind(&UserDatatypeList::refresh, type_list));

  return type_list;
}


mforms::TreeNodeView *WBContextModel::create_history_tree()
{
  HistoryTree *history_tree= new HistoryTree(_wbui->get_wb()->get_grt_manager(), _wbui->get_wb()->get_grt()->get_undo_manager());
  history_tree->refresh();
  return history_tree;
}


grt::GRT *WBContextModel::get_grt()
{
  return _wbui->get_wb()->get_grt();
}


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)
  {
    _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 (!g_file_test(d->c_str(), G_FILE_TEST_IS_DIR))
      continue;

    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))
    {
      std::string path = bec::make_path(*d, "real_path");
      gchar *orig_path;
      gsize length;
      if (g_file_test(path.c_str(), (GFileTest)(G_FILE_TEST_IS_REGULAR|G_FILE_TEST_EXISTS))
          && g_file_get_contents(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;

  std::string target_version = _wbui->get_wb()->get_grt_manager()->get_app_option_string("DefaultTargetMySQLVersion");
  if (target_version.empty())
    target_version = "5.1.22";
  
  _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", target_version);
  
  _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)
{
   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()->revalidate_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())
    {
      base::NotificationInfo info;
      info["form"] = view->form_id();
      info["title"] = view->get_title();
      base::NotificationCenter::get()->send("GNFormTitleDidChange", view, info);
      _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_figures()
{
  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 (view && view->has_selection())
    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()
{
  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::remove_figure()
{
  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 (view)
    view->remove_selection();
}


GrtObjectRef WBContextModel::duplicate_object(const db_DatabaseObjectRef &object, grt::CopyContext &copy_context)
{
  std::set<std::string> skip;
  skip.insert("oldName");
  
  if (object.is_instance(db_Table::static_class_name()))
  {
    db_TableRef table(db_TableRef::cast_from(object));
    
    // copy table
    db_TableRef dbtable(db_TableRef::cast_from(copy_context.copy(table, skip)));
    
    copy_context.update_references();
    
    // post-processing
    // - uniquefy foreign key names
    int max_fk_len = workbench_physical_ModelRef::cast_from(dbtable->owner()->owner()->owner())->rdbms()->maximumIdentifierLength();
    grt::ListRef<db_ForeignKey> fks(dbtable->foreignKeys());
    std::set<std::string> used_fk_names = bec::SchemaHelper::get_foreign_key_names(db_SchemaRef::cast_from(dbtable->owner()));
    for (size_t c= fks.count(), i = 0; i < c; i++)
    {
      db_ForeignKeyRef fk(fks[i]);
      
      fk->name(bec::SchemaHelper::get_unique_foreign_key_name(used_fk_names, fk->name(), max_fk_len));
    }
    
    if (grt::find_named_object_in_list(db_SchemaRef::cast_from(dbtable->owner())->tables(), dbtable->name()).is_valid())
      dbtable->name(grt::get_name_suggestion_for_list_object(db_SchemaRef::cast_from(dbtable->owner())->tables(),
                                                             *dbtable->name()+"_copy"));
    grt::AutoUndo undo(get_grt());
    
    db_SchemaRef::cast_from(dbtable->owner())->tables().insert(dbtable);
    
    undo.end(strfmt(_("Duplicate Table '%s'"), dbtable->name().c_str()));
    
    return dbtable;
  }
  else if (object.is_instance(db_View::static_class_name()))
  {
    db_ViewRef view(db_ViewRef::cast_from(object));
    
    // copy view
    db_ViewRef dbview(db_ViewRef::cast_from(copy_context.copy(view)));
    
    if (grt::find_named_object_in_list(db_SchemaRef::cast_from(dbview->owner())->views(), dbview->name()).is_valid())
      dbview->name(grt::get_name_suggestion_for_list_object(db_SchemaRef::cast_from(dbview->owner())->views(),
                                                            *dbview->name()+"_copy"));
    grt::AutoUndo undo(get_grt());
    
    db_SchemaRef::cast_from(dbview->owner())->views().insert(dbview);
    
    undo.end(strfmt(_("Duplicate View '%s'"), dbview->name().c_str()));

    return dbview;
  }
  else if (object.is_instance(db_RoutineGroup::static_class_name()))
  {
    db_RoutineGroupRef routineGroup(db_RoutineGroupRef::cast_from(object));
    
    // copy routineGroup
    db_RoutineGroupRef dbroutineGroup(db_RoutineGroupRef::cast_from(copy_context.copy(routineGroup)));
    
    if (grt::find_named_object_in_list(db_SchemaRef::cast_from(dbroutineGroup->owner())->routineGroups(), dbroutineGroup->name()).is_valid())
      dbroutineGroup->name(grt::get_name_suggestion_for_list_object(db_SchemaRef::cast_from(dbroutineGroup->owner())->routineGroups(),
                                                                    *dbroutineGroup->name()+"_copy"));
    grt::AutoUndo undo(get_grt());
    
    db_SchemaRef::cast_from(dbroutineGroup->owner())->routineGroups().insert(dbroutineGroup);
    
    undo.end(strfmt(_("Duplicate Routine Group '%s'"), dbroutineGroup->name().c_str()));
    return dbroutineGroup;
  }
  return GrtObjectRef();
}


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;
  bool can_remove= true;
  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);

// this is already implemented, but not yet exposed.. uncomment once the whole thing is working
//  item.enabled= form->can_paste();
//  item.caption= strfmt(_("Duplicate %s"), wb->get_clipboard()->get_content_description().c_str());
//  item.name= "builtin:duplicate";
//  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);
    
    bool has_objects = false;
    bool connections_only = true;
    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_Connection::static_class_name()))
        connections_only = false;
      if (objects[i].is_instance(model_Object::static_class_name()))
      {
        has_objects = true;
        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 (connections_only || !has_objects)
      can_remove = false;

    if (model_objects.count() > 0)
      add_object_plugins_to_popup_menu(model_objects, groups, items);
    else
      can_remove = false;
  }
  
  // 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);
  
  item.caption= label.empty() ? _("Remove Figure") : strfmt(_("Remove Figure %s"), label.c_str());
  item.name= "builtin:removeFigure";
  item.type= MenuAction;
  item.enabled = can_remove;
  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()->revalidate_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____


#ifndef Diagrams_and_Canvas____

//--------------------------------------------------------------------------------
// Canvas Management


void WBContextModel::add_new_diagram(const model_ModelRef &model)
{
  _wbui->get_wb()->show_status_text(_("Creating Diagram..."));
  
 // model_DiagramRef view(model_DiagramRef::cast_from(_wbui->get_wb()->execute_in_grt_thread("Create new diagram",
   //                                                                       boost::bind(&model_Model::addNewDiagram, &model.content(), true))));
  model_DiagramRef view = model->addNewDiagram(true);
  if (view.is_valid())
  {
    view->get_data()->realize();
  }
  
  _wbui->get_wb()->show_status_text(_("Diagram added."));
}


void WBContextModel::switch_diagram(const model_DiagramRef &view)
{
  _wbui->get_wb()->switched_view(view->get_data()->get_canvas_view());
}

#endif // Diagrams_and_Canvas____
#ifndef Canvas_Objects____
/** 
 ****************************************************************************
 * @brief Delete a canvas object from the canvas/model
 * 
 * Will remove the figure from the model and delete all known
 * references to it. There may be some references left, which will cause
 * the GRT object and its bridge to stay alive for some more time, so the
 * bridge is marked as invalid to prevent the canvas item from appearing
 * on screen.
 *
 * @param figure to be removed
 ****************************************************************************
 */
bool WBContextModel::delete_object(model_ObjectRef object)
{
  model_DiagramRef view(model_DiagramRef::cast_from(object->owner()));
  
  FOREACH_COMPONENT(_wbui->get_wb()->_components, iter)
  {
    if ((*iter)->handles_figure(object))
      return (*iter)->delete_model_object(object, false);
  }
  return false;
}


bool WBContextModel::remove_figure(model_ObjectRef object)
{
  model_DiagramRef view(model_DiagramRef::cast_from(object->owner()));
  
  FOREACH_COMPONENT(_wbui->get_wb()->_components, iter)
  {
    if ((*iter)->handles_figure(object))
      return (*iter)->delete_model_object(object, true);
  }
  return false;
}

#endif // Canvas_Objects____

model_DiagramRef WBContextModel::get_view_with_id(const std::string &id)
{
  return model_DiagramRef::cast_from(get_wbui()->get_wb()->get_grt()->find_object_by_id(id, "/wb/doc"));
}

bool WBContextModel::delete_diagram(const model_DiagramRef &view)
{
  grt::AutoUndo undo(get_wbui()->get_wb()->get_grt());
  view->owner()->diagrams().remove_value(view);
  undo.end(strfmt(_("Delete Diagram '%s'"), view->name().c_str()));
  
#ifdef _WIN32
  // in windows, a diagram is released as soon as the tab is closed. That means
  // if a user closes a diagram before deleting it, the diagram is released before 
  // it's removed from the list of diagrams in the list. That will cause the overview
  // to be refreshed too early and with outdated contents. We force another explicit refresh
  // here to workaround that.
  if (get_wbui()->get_physical_overview())
    get_wbui()->get_physical_overview()->send_refresh_diagram(model_DiagramRef());
#endif
  return true;
}


void WBContextModel::begin_plugin_exec()
{
  // lock the canvas so that it doesn't keep refreshing all the time.
  // XXX we have to find some way to allow plugins control refresh freezing
  ModelDiagramForm *view= dynamic_cast<ModelDiagramForm*>(get_wbui()->get_active_main_form());
  _locked_view_for_plugin_exec = 0;
  
  if (view) 
  {
    _locked_view_for_plugin_exec = view->get_view();
    _locked_view_for_plugin_exec->lock_redraw();
  }  
}


void WBContextModel::end_plugin_exec()
{
  // form can get destroyed after a plugin is executed
  if (_locked_view_for_plugin_exec && get_diagram_form(_locked_view_for_plugin_exec))
    _locked_view_for_plugin_exec->unlock_redraw();
  _locked_view_for_plugin_exec = 0;
}


/**
 * Called by the front end when the user data type editor has been closed. We can then remove our reference to it.
 */
static void userTypeEditorClosed(UserDefinedTypeEditor **editor_ptr)
{
  *editor_ptr= NULL;
}

void WBContextModel::show_user_type_editor(workbench_physical_ModelRef model)
{
  if (_current_user_type_editor == NULL)
  {
    _current_user_type_editor = new UserDefinedTypeEditor(get_wbui(), model);
    scoped_connect(_current_user_type_editor->signal_closed(), boost::bind(userTypeEditorClosed, &_current_user_type_editor));
  }
  _current_user_type_editor->show_modal(NULL, NULL);
}
