
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2021 Matthias Koefferlein

  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; either version 2 of the License, or
  (at your option) any later version.

  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 "layLayoutViewFunctions.h"
#include "layLayoutView.h"
#include "layCellSelectionForm.h"
#include "layLayoutStatisticsForm.h"
#include "layLayoutPropertiesForm.h"
#include "layHierarchyControlPanel.h"
#include "layLayerControlPanel.h"
#include "layTipDialog.h"
#include "laySelectCellViewForm.h"
#include "layMove.h"
#include "laybasicConfig.h"

#include "dbClipboard.h"
#include "dbRecursiveShapeIterator.h"
#include "dbLayoutUtils.h"

#include <QMessageBox>
#include <QInputDialog>

namespace lay
{

LayoutViewFunctions::LayoutViewFunctions (db::Manager *manager, LayoutView *view)
  : lay::Plugin (view), mp_view (view), mp_manager (manager)
{
  m_del_cell_mode = 0;
  m_move_to_origin_mode_x = 0;
  m_move_to_origin_mode_y = 0;
  m_del_cell_mode = 0;
  m_layer_hier_mode = 0;
  m_duplicate_hier_mode = 2;
  m_clear_before = true;
  m_copy_cva = -1;
  m_copy_cvr = -1;
  m_copy_layera = -1;
  m_copy_layerr = -1;

  m_new_layer_props.layer = 1;
  m_new_layer_props.datatype = 0;
}

LayoutViewFunctions::~LayoutViewFunctions ()
{
  //  .. nothing yet..
}

void
LayoutViewFunctions::menu_activated (const std::string &symbol)
{
  if (symbol == "cm_show_properties") {

    view ()->show_properties (view ());

  } else if (symbol == "cm_delete") {

    view ()->del ();
    //  because a "delete" might involve objects currently edited, we cancel the edit after we have deleted the object
    view ()->cancel ();
    view ()->clear_selection ();

  } else if (symbol == "cm_unselect_all") {
    view ()->select (db::DBox (), lay::Editable::Reset);
  } else if (symbol == "cm_select_all") {
    view ()->select (view ()->full_box (), lay::Editable::Replace);
  } else if (symbol == "cm_select_next_item") {
    view ()->repeat_selection (lay::Editable::Replace);
  } else if (symbol == "cm_select_next_item_add") {
    view ()->repeat_selection (lay::Editable::Add);
  } else if (symbol == "cm_lv_paste") {
    cm_layer_paste ();
  } else if (symbol == "cm_lv_cut") {
    cm_layer_cut ();
  } else if (symbol == "cm_lv_copy") {
    cm_layer_copy ();
  } else if (symbol == "cm_cell_paste") {
    cm_cell_paste ();
  } else if (symbol == "cm_cell_cut") {
    cm_cell_cut ();
  } else if (symbol == "cm_cell_copy") {
    cm_cell_copy ();
  } else if (symbol == "cm_duplicate") {
    do_cm_duplicate (false);
  } else if (symbol == "cm_duplicate_interactive") {
    do_cm_duplicate (true);
  } else if (symbol == "cm_copy") {

    view ()->copy ();
    view ()->clear_selection ();

  } else if (symbol == "cm_paste") {
    do_cm_paste (false);
  } else if (symbol == "cm_paste_interactive") {
    do_cm_paste (true);
  } else if (symbol == "cm_cut") {

    view ()->cut ();
    view ()->cancel (); //  see del() for reason why cancel is after cut
    view ()->clear_selection ();

  } else if (symbol == "cm_zoom_fit_sel") {
    view ()->zoom_fit_sel ();
  } else if (symbol == "cm_zoom_fit") {
    view ()->zoom_fit ();
  } else if (symbol == "cm_pan_left") {
    view ()->pan_left ();
  } else if (symbol == "cm_pan_right") {
    view ()->pan_right ();
  } else if (symbol == "cm_pan_up") {
    view ()->pan_up ();
  } else if (symbol == "cm_pan_down") {
    view ()->pan_down ();
  } else if (symbol == "cm_zoom_in") {
    view ()->zoom_in ();
  } else if (symbol == "cm_zoom_out") {
    view ()->zoom_out ();
  } else if (symbol == "cm_select_current_cell") {

    if (view ()->active_cellview_index () >= 0) {
      lay::LayoutView::cell_path_type path;
      int cvi = view ()->active_cellview_index ();
      view ()->current_cell_path (path);
      view ()->select_cell_fit (path, cvi);
    }

  } else if (symbol == "cm_open_current_cell") {

    if (view ()->active_cellview_index () >= 0) {
      cm_open_current_cell ();
    }

  } else if (symbol == "cm_select_cell") {

    if (view ()->active_cellview_index () >= 0) {

      lay::CellSelectionForm form (0, view (), "cell_selection_form");

      if (form.exec () == QDialog::Accepted &&
          form.selected_cellview_index () >= 0) {
        view ()->select_cell (form.selected_cellview ().combined_unspecific_path (), form.selected_cellview_index ());
        view ()->set_current_cell_path (form.selected_cellview_index (), form.selected_cellview ().combined_unspecific_path ());
        view ()->zoom_fit ();
      }

    }

  } else if (symbol == "cm_new_cell") {
    cm_new_cell ();
  } else if (symbol == "cm_adjust_origin") {
    if (view ()->active_cellview_index () >= 0) {
      cm_align_cell_origin ();
    }
  } else if (symbol == "cm_cell_convert_to_static") {
    if (view ()->active_cellview_index () >= 0) {
      cm_cell_convert_to_static ();
    }
  } else if (symbol == "cm_lay_convert_to_static") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_convert_to_static ();
    }
  } else if (symbol == "cm_lay_move") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_move ();
    }
  } else if (symbol == "cm_lay_scale") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_scale ();
    }
  } else if (symbol == "cm_lay_free_rot") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_free_rot ();
    }
  } else if (symbol == "cm_lay_rot_ccw") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_rot_ccw ();
    }
  } else if (symbol == "cm_lay_rot_cw") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_rot_cw ();
    }
  } else if (symbol == "cm_lay_flip_y") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_flip_y ();
    }
  } else if (symbol == "cm_lay_flip_x") {
    if (view ()->active_cellview_index () >= 0) {
      cm_lay_flip_x ();
    }
  } else if (symbol == "cm_sel_move") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_move ();
    }
  } else if (symbol == "cm_sel_move_to") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_move_to ();
    }
  } else if (symbol == "cm_sel_move_interactive") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_move_interactive ();
    }
  } else if (symbol == "cm_sel_scale") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_scale ();
    }
  } else if (symbol == "cm_sel_free_rot") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_free_rot ();
    }
  } else if (symbol == "cm_sel_rot_ccw") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_rot_ccw ();
    }
  } else if (symbol == "cm_sel_rot_cw") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_rot_cw ();
    }
  } else if (symbol == "cm_sel_flip_y") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_flip_y ();
    }
  } else if (symbol == "cm_sel_flip_x") {
    if (view ()->active_cellview_index () >= 0) {
      cm_sel_flip_x ();
    }
  } else if (symbol == "cm_edit_layer") {
    if (view ()->active_cellview_index () >= 0) {
      cm_edit_layer ();
    }
  } else if (symbol == "cm_delete_layer") {
    if (view ()->active_cellview_index () >= 0) {
      cm_delete_layer ();
    }
  } else if (symbol == "cm_clear_layer") {
    if (view ()->active_cellview_index () >= 0) {
      cm_clear_layer ();
    }
  } else if (symbol == "cm_copy_layer") {
    if (view ()->active_cellview_index () >= 0) {
      cm_copy_layer ();
    }
  } else if (symbol == "cm_new_layer") {
    if (view ()->active_cellview_index () >= 0) {
      cm_new_layer ();
    }
  } else if (symbol == "cm_layout_props") {
    lay::LayoutPropertiesForm lp_form (view (), view (), "layout_props_form");
    lp_form.exec ();
  } else if (symbol == "cm_layout_stats") {
    lay::LayoutStatisticsForm lp_form (view (), view (), "layout_props_form");
    lp_form.exec ();
  } else if (symbol == "cm_reload") {
    cm_reload ();
  } else if (symbol == "cm_inc_max_hier") {
    int new_to = view ()->get_max_hier_levels () + 1;
    view ()->set_hier_levels (std::make_pair (view ()->get_min_hier_levels (), new_to));
  } else if (symbol == "cm_dec_max_hier") {
    int new_to = view ()->get_max_hier_levels () > 0 ? view ()->get_max_hier_levels () - 1 : 0;
    view ()->set_hier_levels (std::make_pair (std::min (view ()->get_min_hier_levels (), new_to), new_to));
  } else if (symbol == "cm_max_hier") {
    view ()->max_hier ();
  } else if (symbol == "cm_max_hier_0") {
    view ()->set_hier_levels (std::make_pair (std::min (view ()->get_min_hier_levels (), 0), 0));
  } else if (symbol == "cm_max_hier_1") {
    view ()->set_hier_levels (std::make_pair (std::min (view ()->get_min_hier_levels (), 0), 1));
  } else if (symbol == "cm_prev_display_state") {
    if (view ()->has_prev_display_state ()) {
      view ()->prev_display_state ();
    }
  } else if (symbol == "cm_next_display_state") {
    if (view ()->has_next_display_state ()) {
      view ()->next_display_state ();
    }
  } else if (symbol == "cm_redraw") {
    view ()->redraw ();
  } else if (symbol == "cm_cell_delete") {
    cm_cell_delete ();
  } else if (symbol == "cm_cell_replace") {
    cm_cell_replace ();
  } else if (symbol == "cm_cell_rename") {
    cm_cell_rename ();
  } else if (symbol == "cm_cell_flatten") {
    cm_cell_flatten ();
  } else if (symbol == "cm_cell_select") {
    cm_cell_select ();
  } else if (symbol == "cm_cell_hide") {
    cm_cell_hide ();
  } else if (symbol == "cm_cell_show") {
    cm_cell_show ();
  } else if (symbol == "cm_cell_show_all") {
    cm_cell_show_all ();
  } else if (symbol == "cm_cell_user_properties") {
    if (view ()->active_cellview_index () >= 0) {
      cm_cell_user_properties ();
    }
  } else if (symbol == "cm_lv_select_all") {
    cm_select_all ();
  } else if (symbol == "cm_lv_new_tab") {
    cm_new_tab ();
  } else if (symbol == "cm_lv_rename_tab") {
    cm_rename_tab ();
  } else if (symbol == "cm_lv_make_invalid") {
    cm_make_invalid ();
  } else if (symbol == "cm_lv_remove_tab") {
    cm_remove_tab ();
  } else if (symbol == "cm_lv_make_valid") {
    cm_make_valid ();
  } else if (symbol == "cm_lv_hide_all") {
    cm_hide_all ();
  } else if (symbol == "cm_lv_hide") {
    cm_hide ();
  } else if (symbol == "cm_lv_show_only") {
    cm_show_only ();
  } else if (symbol == "cm_lv_show_all") {
    cm_show_all ();
  } else if (symbol == "cm_lv_show") {
    cm_show ();
  } else if (symbol == "cm_lv_rename") {
    cm_rename ();
  } else if (symbol == "cm_lv_delete") {
    cm_delete ();
  } else if (symbol == "cm_lv_insert") {
    cm_insert ();
  } else if (symbol == "cm_lv_group") {
    cm_group ();
  } else if (symbol == "cm_lv_ungroup") {
    cm_ungroup ();
  } else if (symbol == "cm_lv_source") {
    cm_source ();
  } else if (symbol == "cm_lv_sort_by_name") {
    cm_sort_by_name ();
  } else if (symbol == "cm_lv_sort_by_ild") {
    cm_sort_by_ild ();
  } else if (symbol == "cm_lv_sort_by_idl") {
    cm_sort_by_idl ();
  } else if (symbol == "cm_lv_sort_by_ldi") {
    cm_sort_by_ldi ();
  } else if (symbol == "cm_lv_sort_by_dli") {
    cm_sort_by_dli ();
  } else if (symbol == "cm_lv_regroup_by_index") {
    cm_regroup_by_index ();
  } else if (symbol == "cm_lv_regroup_by_datatype") {
    cm_regroup_by_datatype ();
  } else if (symbol == "cm_lv_regroup_by_layer") {
    cm_regroup_by_layer ();
  } else if (symbol == "cm_lv_regroup_flatten") {
    cm_regroup_flatten ();
  } else if (symbol == "cm_lv_expand_all") {
    cm_expand_all ();
  } else if (symbol == "cm_lv_add_missing") {
    cm_add_missing ();
  } else if (symbol == "cm_lv_remove_unused") {
    cm_remove_unused ();
  }
}

void
LayoutViewFunctions::cm_cell_user_properties ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  int cv_index = view ()->active_cellview_index ();
  lay::LayoutView::cell_path_type path;
  view ()->hierarchy_panel ()->current_cell (cv_index, path);

  if (cv_index >= 0 && path.size () > 0) {

    db::Layout &layout = view ()->cellview (cv_index)->layout ();
    db::Cell &cell = layout.cell (path.back ());
    db::properties_id_type prop_id = cell.prop_id ();

    lay::UserPropertiesForm props_form (view ());
    if (props_form.show (view (), cv_index, prop_id)) {

      view ()->transaction (tl::to_string (QObject::tr ("Edit cell's user propertes")));
      cell.prop_id (prop_id);
      view ()->commit ();

    }

  }
}

void
LayoutViewFunctions::cm_cell_replace ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  int cv_index = view ()->active_cellview_index ();
  std::vector<lay::LayoutView::cell_path_type> paths;
  view ()->hierarchy_panel ()->selected_cells (cv_index, paths);

  if (cv_index >= 0 && paths.size () > 0) {

    if (paths.size () > 1) {
      throw tl::Exception (tl::to_string (QObject::tr ("Replace cell cannot be used when multiple cells are selected")));
    }

    db::Layout &layout = view ()->cellview (cv_index)->layout ();

    bool needs_to_ask = false;
    for (std::vector<lay::LayoutView::cell_path_type>::const_iterator p = paths.begin (); p != paths.end () && ! needs_to_ask; ++p) {
      if (layout.is_valid_cell_index (p->back ()) && ! layout.cell (p->back ()).is_leaf ()) {
        needs_to_ask = true;
      }
    }


    lay::ReplaceCellOptionsDialog mode_dialog (view ());

    db::cell_index_type with_cell = paths.front ().back ();
    int mode = needs_to_ask ? m_del_cell_mode : 0;

    if (mode_dialog.exec_dialog (view ()->cellview (cv_index), mode, with_cell)) {

      if (needs_to_ask) {
        m_del_cell_mode = mode;
      }

      if (with_cell != paths.front ().back ()) {

        //  remember the current path
        lay::LayoutView::cell_path_type cell_path (view ()->cellview (cv_index).combined_unspecific_path ());

        view ()->clear_selection ();

        view ()->transaction (tl::to_string (QObject::tr ("Replace cells")));

        //  replace instances of the target cell with the new cell 

        db::Cell &target_cell = layout.cell (paths.front ().back ());

        std::vector<std::pair<db::cell_index_type, db::Instance> > parents;
        for (db::Cell::parent_inst_iterator pi = target_cell.begin_parent_insts (); ! pi.at_end (); ++pi) {
          parents.push_back (std::make_pair (pi->parent_cell_index (), pi->child_inst ()));
        }

        for (std::vector<std::pair<db::cell_index_type, db::Instance> >::const_iterator p = parents.begin (); p != parents.end (); ++p) {
          db::CellInstArray ia = p->second.cell_inst ();
          ia.object ().cell_index (with_cell);
          layout.cell (p->first).replace (p->second, ia);
        }

        std::set<db::cell_index_type> cells_to_delete;
        for (std::vector<lay::LayoutView::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
          if (! p->empty () && layout.is_valid_cell_index (p->back ())) {
            cells_to_delete.insert (p->back ());
            if (mode == 2) {
              layout.cell (p->back ()).collect_called_cells (cells_to_delete);
            }
          }
        }

        //  support a propagation use case:
        std::set<db::cell_index_type> cells_below_replacement_cell;
        cells_below_replacement_cell.insert (with_cell);
        layout.cell (with_cell).collect_called_cells (cells_below_replacement_cell);
        for (std::set<db::cell_index_type>::const_iterator c = cells_below_replacement_cell.begin (); c != cells_below_replacement_cell.end (); ++c) {
          cells_to_delete.erase (*c);
        }

        if (mode == 0 || mode == 2) {
          layout.delete_cells (cells_to_delete);
        } else if (mode == 1) {
          layout.prune_cells (cells_to_delete);
        }

        layout.cleanup ();

        view ()->commit ();

        //  If one of the cells in the path was deleted, establish a valid path

        bool needs_update = false;
        for (size_t i = cell_path.size (); i > 0; ) {
          --i;
          if (! layout.is_valid_cell_index (cell_path [i])) {
            cell_path.erase (cell_path.begin () + i, cell_path.end ());
            needs_update = true;
          }
        }

        if (needs_update) {
          view ()->select_cell (cell_path, cv_index);
        }

      }

    }

  }
}

void
LayoutViewFunctions::cm_lay_convert_to_static ()
{
  //  end move operations, cancel edit operations
  view ()->cancel_edits ();
  view ()->clear_selection ();

  int cv_index = view ()->active_cellview_index ();
  if (cv_index >= 0) {

    db::Layout &layout = view ()->cellview (cv_index)->layout ();

    view ()->transaction (tl::to_string (QObject::tr ("Convert all cells to static")));

    std::vector<db::cell_index_type> cells;
    for (db::Layout::const_iterator c = layout.begin (); c != layout.end (); ++c) {
      cells.push_back (c->cell_index ());
    }

    std::map<db::cell_index_type, db::cell_index_type> cell_map;
    for (std::vector<db::cell_index_type>::const_iterator c = cells.begin (); c != cells.end (); ++c) {
      if (layout.is_valid_cell_index (*c)) {
        db::cell_index_type new_cell = layout.convert_cell_to_static (*c);
        if (new_cell != *c) {
          cell_map.insert (std::make_pair (*c, new_cell));
        }
      }
    }

    //  rewrite instances
    for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) {
      for (db::Cell::const_iterator i = c->begin (); ! i.at_end (); ++i) {
        std::map<db::cell_index_type, db::cell_index_type>::const_iterator cm = cell_map.find (i->cell_index ());
        if (cm != cell_map.end ()) {
          db::CellInstArray ci = i->cell_inst ();
          ci.object ().cell_index (cm->second);
          c->replace (*i, ci);
        }
      }
    }

    layout.cleanup ();

    view ()->commit ();

  }
}

void
LayoutViewFunctions::cm_cell_convert_to_static ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  int cv_index = view ()->active_cellview_index ();
  std::vector<lay::LayoutView::cell_path_type> paths;
  view ()->hierarchy_panel ()->selected_cells (cv_index, paths);

  if (cv_index >= 0 && paths.size () > 0) {

    db::Layout &layout = view ()->cellview (cv_index)->layout ();

    //  remember the current path
    lay::LayoutView::cell_path_type cell_path (view ()->cellview (cv_index).combined_unspecific_path ());

    view ()->clear_selection ();

    view ()->transaction (tl::to_string (QObject::tr ("Convert cells to static")));

    std::map<db::cell_index_type, db::cell_index_type> cell_map;

    for (std::vector<lay::LayoutView::cell_path_type>::iterator p = paths.begin (); p != paths.end (); ++p) {
      if (! p->empty () && layout.is_valid_cell_index (p->back ())) {
        db::cell_index_type new_cell = layout.convert_cell_to_static (p->back ());
        if (new_cell != p->back ()) {
          cell_map.insert (std::make_pair (p->back (), new_cell));
          p->back () = new_cell;
        }
      }
    }

    //  rewrite instances
    for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) {
      for (db::Cell::const_iterator i = c->begin (); ! i.at_end (); ++i) {
        std::map<db::cell_index_type, db::cell_index_type>::const_iterator cm = cell_map.find (i->cell_index ());
        if (cm != cell_map.end ()) {
          db::CellInstArray ci = i->cell_inst ();
          ci.object ().cell_index (cm->second);
          c->replace (*i, ci);
        }
      }
    }

    layout.cleanup ();

    view ()->commit ();

    //  If one of the cells in the path was deleted, establish a valid path

    bool needs_update = false;
    for (size_t i = cell_path.size (); i > 0; ) {
      --i;
      if (! layout.is_valid_cell_index (cell_path [i])) {
        cell_path.erase (cell_path.begin () + i, cell_path.end ());
        needs_update = true;
      }
    }

    if (needs_update) {
      view ()->select_cell (cell_path, cv_index);
    }

  }
}

static void
collect_cells_to_delete (const db::Layout &layout, const db::Cell &cell, std::set<db::cell_index_type> &called)
{
  //  don't delete proxies - they are deleted later when the layout is cleaned
  for (db::Cell::child_cell_iterator cc = cell.begin_child_cells (); ! cc.at_end (); ++cc) {
    if (called.find (*cc) == called.end () && !layout.cell (*cc).is_proxy ()) {
      called.insert (*cc);
      collect_cells_to_delete (layout, layout.cell (*cc), called);
    }
  }
}

void
LayoutViewFunctions::cm_cell_delete ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  int cv_index = view ()->active_cellview_index ();
  std::vector<lay::LayoutView::cell_path_type> paths;
  view ()->hierarchy_panel ()->selected_cells (cv_index, paths);

  if (cv_index >= 0 && paths.size () > 0) {

    db::Layout &layout = view ()->cellview (cv_index)->layout ();

    bool needs_to_ask = false;
    for (std::vector<lay::LayoutView::cell_path_type>::const_iterator p = paths.begin (); p != paths.end () && ! needs_to_ask; ++p) {
      if (layout.is_valid_cell_index (p->back ()) && ! layout.cell (p->back ()).is_leaf ()) {
        needs_to_ask = true;
      }
    }

    int mode = m_del_cell_mode;
    if (! needs_to_ask) {
      mode = 0;
    }

    lay::DeleteCellModeDialog mode_dialog (view ());
    if (! needs_to_ask || mode_dialog.exec_dialog (mode)) {

      if (needs_to_ask) {
        m_del_cell_mode = mode;
      }

      //  remember the current path
      lay::LayoutView::cell_path_type cell_path (view ()->cellview (cv_index).combined_unspecific_path ());

      view ()->clear_selection ();

      std::set<db::cell_index_type> cells_to_delete;
      for (std::vector<lay::LayoutView::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
        if (! p->empty () && layout.is_valid_cell_index (p->back ())) {
          cells_to_delete.insert (p->back ());
          if (mode == 2) {
            collect_cells_to_delete (layout, layout.cell (p->back ()), cells_to_delete);
          }
        }
      }

      view ()->transaction (tl::to_string (QObject::tr ("Delete cells")));

      if (mode == 0 || mode == 2) {
        layout.delete_cells (cells_to_delete);
      } else if (mode == 1) {
        layout.prune_cells (cells_to_delete);
      }

      layout.cleanup ();

      view ()->commit ();

      //  If one of the cells in the path was deleted, establish a valid path

      bool needs_update = false;
      for (size_t i = cell_path.size (); i > 0; ) {
        --i;
        if (! layout.is_valid_cell_index (cell_path [i])) {
          cell_path.erase (cell_path.begin () + i, cell_path.end ());
          needs_update = true;
        }
      }

      if (needs_update) {
        view ()->select_cell (cell_path, cv_index);
      }

    }

  }
}

void 
LayoutViewFunctions::cm_layer_copy ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->copy ();
  }
}

void 
LayoutViewFunctions::cm_layer_cut ()
{
  if (view ()->control_panel ()) {
    db::Transaction trans (manager (), tl::to_string (QObject::tr ("Cut Layers")));
    view ()->control_panel ()->cut ();
  }
}

void 
LayoutViewFunctions::cm_layer_paste ()
{
  if (view ()->control_panel ()) {
    db::Transaction trans (manager (), tl::to_string (QObject::tr ("Paste Layers")));
    view ()->control_panel ()->paste ();
  }
}

void
LayoutViewFunctions::cm_cell_cut ()
{
  if (view ()->hierarchy_panel ()) {
    //  TODO: currently the hierarchy panel's cut function does it's own transaction handling.
    //  Otherwise the cut function is not working propertly.
    view ()->hierarchy_panel ()->cut ();
  }
}

void
LayoutViewFunctions::cm_cell_paste ()
{
  if (view ()->hierarchy_panel ()) {
    db::Transaction trans (manager (), tl::to_string (QObject::tr ("Paste Cells")));
    view ()->hierarchy_panel ()->paste ();
  }
}

void
LayoutViewFunctions::cm_cell_copy ()
{
  if (view ()->hierarchy_panel ()) {
    view ()->hierarchy_panel ()->copy ();
  }
}

void
LayoutViewFunctions::cm_cell_flatten ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  tl_assert (view ()->is_editable ());

  int cv_index = view ()->active_cellview_index ();
  if (cv_index >= 0) {

    const lay::CellView &cv = view ()->cellview (cv_index);
    if (cv.is_valid ()) {

      std::vector<HierarchyControlPanel::cell_path_type> paths;
      view ()->hierarchy_panel ()->selected_cells (cv_index, paths);
      if (paths.empty ()) {
        throw tl::Exception (tl::to_string (QObject::tr ("No cells selected for flattening")));
      }

      for (std::vector<HierarchyControlPanel::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
        if (p->size () > 0 && cv->layout ().cell (p->back ()).is_proxy ()) {
          throw tl::Exception (tl::to_string (QObject::tr ("Cannot use this function on a PCell or library cell")));
        }
      }

      FlattenInstOptionsDialog options_dialog (view ());

      int flatten_insts_levels = -1;
      bool prune = true;
      if (options_dialog.exec_dialog (flatten_insts_levels, prune) && flatten_insts_levels != 0) {

        bool supports_undo = true;

        if (manager () && manager ()->is_enabled ()) {

          lay::TipDialog td (QApplication::activeWindow (), 
                             tl::to_string (QObject::tr ("Undo buffering for the following operation can be memory and time consuming.\nChoose \"Yes\" to use undo buffering or \"No\" for no undo buffering. Warning: in the latter case, the undo history will be lost.\n\nChoose undo buffering?")), 
                             "flatten-undo-buffering",
                             lay::TipDialog::yesnocancel_buttons);

          lay::TipDialog::button_type button = lay::TipDialog::null_button;
          td.exec_dialog (button);
          if (button == lay::TipDialog::cancel_button) {
            return;
          }

          supports_undo = (button == lay::TipDialog::yes_button);

        } else {
          supports_undo = false;
        }

        view ()->cancel_edits ();
        view ()->clear_selection ();

        if (manager ()) {
          if (! supports_undo) {
            manager ()->clear ();
          } else {
            manager ()->transaction (tl::to_string (QObject::tr ("Flatten cell")));
          }
        }

        db::Layout &layout = cv->layout ();

        std::set<db::cell_index_type> child_cells;
        for (std::vector<HierarchyControlPanel::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
          if (p->size () > 0) {
            layout.cell (p->back ()).collect_called_cells (child_cells);
          }
        }

        //  don't flatten cells which are child cells of the cells to flatten
        std::set<db::cell_index_type> cells_to_flatten;
        for (std::vector<HierarchyControlPanel::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
          if (p->size () > 0 && child_cells.find (p->back ()) == child_cells.end ()) {
            cells_to_flatten.insert (p->back ());
          }
        }

        for (std::set<db::cell_index_type>::const_iterator c = cells_to_flatten.begin (); c != cells_to_flatten.end (); ++c) {
          db::Cell &target_cell = layout.cell (*c);
          layout.flatten (target_cell, flatten_insts_levels, prune);
        }

        layout.cleanup ();

        if (supports_undo && manager ()) {
          manager ()->commit ();
        }

      }

    }

  }
}

void
LayoutViewFunctions::cm_cell_rename ()
{
  if (! view ()->hierarchy_panel ()) {
    return;
  }

  int cv_index = view ()->active_cellview_index ();
  lay::LayoutView::cell_path_type path;
  view ()->hierarchy_panel ()->current_cell (cv_index, path);

  if (cv_index >= 0 && path.size () > 0) {

    lay::RenameCellDialog name_dialog (view ());

    db::Layout &layout = view ()->cellview (cv_index)->layout ();
    std::string name (layout.cell_name (path.back ()));
    if (name_dialog.exec_dialog (layout, name)) {

      view ()->transaction (tl::to_string (QObject::tr ("Rename cell")));
      layout.rename_cell (path.back (), name.c_str ());
      view ()->commit ();

    }

  }
}

void
LayoutViewFunctions::cm_cell_select ()
{
  if (view ()->hierarchy_panel ()) {
    view ()->hierarchy_panel ()->cm_cell_select ();
  }
}

void
LayoutViewFunctions::cm_open_current_cell ()
{
  view ()->set_current_cell_path (view ()->active_cellview_index (), view ()->cellview (view ()->active_cellview_index ()).combined_unspecific_path ());
}

void
LayoutViewFunctions::cm_cell_hide ()
{
  if (view ()->hierarchy_panel ()) {

    std::vector<HierarchyControlPanel::cell_path_type> paths;
    view ()->hierarchy_panel ()->selected_cells (view ()->active_cellview_index (), paths);

    view ()->transaction (tl::to_string (QObject::tr ("Hide cell")));

    for (std::vector<HierarchyControlPanel::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
      if (! p->empty ()) {
        view ()->hide_cell (p->back (), view ()->active_cellview_index ());
      }
    }

    view ()->commit ();

  }
}

void
LayoutViewFunctions::cm_cell_show ()
{
  if (view ()->hierarchy_panel ()) {

    std::vector<HierarchyControlPanel::cell_path_type> paths;
    view ()->hierarchy_panel ()->selected_cells (view ()->active_cellview_index (), paths);

    view ()->transaction (tl::to_string (QObject::tr ("Show cell")));

    for (std::vector<HierarchyControlPanel::cell_path_type>::const_iterator p = paths.begin (); p != paths.end (); ++p) {
      if (! p->empty ()) {
        view ()->show_cell (p->back (), view ()->active_cellview_index ());
      }
    }

    view ()->commit ();

  }
}

void
LayoutViewFunctions::cm_cell_show_all ()
{
  if (view ()->hierarchy_panel ()) {
    view ()->transaction (tl::to_string (QObject::tr ("Show all cells")));
    view ()->show_all_cells ();
    view ()->commit ();
  }
}

void
LayoutViewFunctions::cm_select_all ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_select_all ();
  }
}

void
LayoutViewFunctions::cm_new_tab ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_new_tab ();
  }
}

void
LayoutViewFunctions::cm_remove_tab ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_remove_tab ();
  }
}

void
LayoutViewFunctions::cm_rename_tab ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_rename_tab ();
  }
}

void
LayoutViewFunctions::cm_make_invalid ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_make_invalid ();
  }
}

void
LayoutViewFunctions::cm_make_valid ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_make_valid ();
  }
}

void
LayoutViewFunctions::cm_hide ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_hide ();
  }
}

void
LayoutViewFunctions::cm_hide_all ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_hide_all ();
  }
}

void
LayoutViewFunctions::cm_show_only ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_show_only ();
  }
}

void
LayoutViewFunctions::cm_show_all ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_show_all ();
  }
}

void
LayoutViewFunctions::cm_show ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_show ();
  }
}

void
LayoutViewFunctions::cm_rename ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_rename ();
  }
}

void
LayoutViewFunctions::cm_delete ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_delete ();
  }
}

void
LayoutViewFunctions::cm_insert ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_insert ();
  }
}

void
LayoutViewFunctions::cm_group ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_group ();
  }
}

void
LayoutViewFunctions::cm_ungroup ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_ungroup ();
  }
}

void
LayoutViewFunctions::cm_source ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_source ();
  }
}

void
LayoutViewFunctions::cm_sort_by_name ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_sort_by_name ();
  }
}

void
LayoutViewFunctions::cm_sort_by_ild ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_sort_by_ild ();
  }
}

void
LayoutViewFunctions::cm_sort_by_idl ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_sort_by_idl ();
  }
}

void
LayoutViewFunctions::cm_sort_by_ldi ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_sort_by_ldi ();
  }
}

void
LayoutViewFunctions::cm_sort_by_dli ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_sort_by_dli ();
  }
}

void
LayoutViewFunctions::cm_regroup_by_index ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_regroup_by_index ();
  }
}

void
LayoutViewFunctions::cm_regroup_by_datatype ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_regroup_by_datatype ();
  }
}

void
LayoutViewFunctions::cm_regroup_by_layer ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_regroup_by_layer ();
  }
}

void
LayoutViewFunctions::cm_regroup_flatten ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_regroup_flatten ();
  }
}

void
LayoutViewFunctions::cm_expand_all ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_expand_all ();
  }
}

void
LayoutViewFunctions::cm_add_missing ()
{
  if (view ()->control_panel ()) {
    view ()->control_panel ()->cm_add_missing ();
  }
}

void
LayoutViewFunctions::cm_remove_unused ()
{
  view ()->remove_unused_layers ();
}

void
LayoutViewFunctions::do_cm_duplicate (bool interactive)
{
  //  Do duplicate simply by concatenating copy & paste currently.
  //  Save the clipboard state before in order to preserve the current content
  db::Clipboard saved_clipboard;
  db::Clipboard::instance ().swap (saved_clipboard);

  try {
    view ()->copy ();
    view ()->clear_selection ();
    view ()->cancel ();
    if (interactive) {
      view ()->paste_interactive ();
    } else {
      view ()->paste ();
    }
    db::Clipboard::instance ().swap (saved_clipboard);
  } catch (...) {
    db::Clipboard::instance ().swap (saved_clipboard);
    throw;
  }
}

void
LayoutViewFunctions::do_cm_paste (bool interactive)
{
  if (! db::Clipboard::instance ().empty ()) {
    view ()->cancel ();
    view ()->clear_selection ();
    if (interactive) {
      view ()->paste_interactive ();
    } else {
      view ()->paste ();
    }
  }
}

void
LayoutViewFunctions::cm_new_cell ()
{
  static double s_new_cell_window_size = 2.0;
  static std::string s_new_cell_cell_name;

  NewCellPropertiesDialog cell_prop_dia (view ());
  if (cell_prop_dia.exec_dialog (& view ()->cellview (view ()->active_cellview_index ())->layout (), s_new_cell_cell_name, s_new_cell_window_size)) {

    db::cell_index_type new_ci = view ()->new_cell (view ()->active_cellview_index (), s_new_cell_cell_name.c_str ());
    view ()->select_cell (new_ci, view ()->active_cellview_index ());

    db::DBox zb = db::DBox (-0.5 * s_new_cell_window_size, -0.5 * s_new_cell_window_size, 0.5 * s_new_cell_window_size, 0.5 * s_new_cell_window_size);
    if (view ()->get_max_hier_levels () < 1 || view ()->get_min_hier_levels () > 0) {
      view ()->zoom_box_and_set_hier_levels (zb, std::make_pair (0, 1));
    } else {
      view ()->zoom_box (zb);
    }

  }
}

//  TODO: this constant is defined in MainWindow.cc too ...
const int max_dirty_files = 15;

void
LayoutViewFunctions::cm_reload ()
{
  std::vector <int> selected;

  if (view ()->cellviews () > 1) {

    lay::SelectCellViewForm form (0, view (), tl::to_string (QObject::tr ("Select Layouts To Reload")));
    form.select_all ();

    if (form.exec () == QDialog::Accepted) {
      selected = form.selected_cellviews ();
    }

  } else if (view ()->cellviews () > 0) {
    selected.push_back (0);
  }

  if (selected.size () > 0) {

    int dirty_layouts = 0;
    std::string dirty_files;

    for (std::vector <int>::const_iterator i = selected.begin (); i != selected.end (); ++i) {

      const lay::CellView &cv = view ()->cellview (*i);

      if (cv->layout ().is_editable () && cv->is_dirty ()) {
        ++dirty_layouts;
        if (dirty_layouts == max_dirty_files) {
          dirty_files += "\n...";
        } else if (dirty_layouts < max_dirty_files) {
          if (! dirty_files.empty ()) {
            dirty_files += "\n";
          }
          dirty_files += cv->name ();
        }
      }

    }

    bool can_reload = true;
    if (dirty_layouts != 0) {

      QMessageBox mbox (view ());
      mbox.setText (tl::to_qstring (tl::to_string (QObject::tr ("The following layouts need saving:\n\n")) + dirty_files + "\n\nPress 'Reload Without Saving' to reload anyhow and discard changes."));
      mbox.setWindowTitle (QObject::tr ("Save Needed"));
      mbox.setIcon (QMessageBox::Warning);
      QAbstractButton *yes_button = mbox.addButton (QObject::tr ("Reload Without Saving"), QMessageBox::YesRole);
      mbox.addButton (QMessageBox::Cancel);

      mbox.exec ();

      can_reload = (mbox.clickedButton() == yes_button);

    }

    if (can_reload) {

      //  Actually reload
      for (std::vector <int>::const_iterator i = selected.begin (); i != selected.end (); ++i) {
        view ()->reload_layout (*i);
      }

    }

  }
}

void
LayoutViewFunctions::do_transform (const db::DCplxTrans &tr)
{
  //  end move operations, cancel edit operations
  view ()->cancel_edits ();
  view ()->lay::Editables::transform (tr);
}

void
LayoutViewFunctions::transform_layout (const db::DCplxTrans &tr_mic)
{
  //  end move operations, cancel edit operations
  view ()->cancel_edits ();
  view ()->clear_selection ();

  int cv_index = view ()->active_cellview_index ();
  if (cv_index >= 0) {

    db::Layout &layout = view ()->cellview (cv_index)->layout ();

    db::ICplxTrans tr (db::DCplxTrans (1.0 / layout.dbu ()) * tr_mic * db::DCplxTrans (layout.dbu ()));

    bool has_proxy = false;
    for (db::Layout::const_iterator c = layout.begin (); ! has_proxy && c != layout.end (); ++c) {
      has_proxy = c->is_proxy ();
    }

    if (has_proxy && 
        QMessageBox::question (view (),
                               QObject::tr ("Transforming PCells Or Library Cells"), 
                               QObject::tr ("The layout contains PCells or library cells or both.\n"
                                            "Any changes to such cells may be lost when their layout is refreshed later.\n"
                                            "Consider using 'Convert all cells to static' before transforming the layout.\n"
                                            "\n"
                                            "Would you like to continue?\n"
                                            "Choose 'Yes' to continue anyway. Choose 'No' to cancel."), 
                               QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
      return;
    }

    view ()->transaction (tl::to_string (QObject::tr ("Transform layout")));
    layout.transform (tr);
    view ()->commit ();

  }
}

void 
LayoutViewFunctions::cm_lay_flip_x ()
{
  transform_layout (db::DCplxTrans (db::FTrans::m90));
}

void 
LayoutViewFunctions::cm_lay_flip_y ()
{
  transform_layout (db::DCplxTrans (db::FTrans::m0));
}

void 
LayoutViewFunctions::cm_lay_rot_ccw ()
{
  db::DCplxTrans tr (db::DFTrans::r90);
  transform_layout (db::DCplxTrans (db::FTrans::r90));
}

void 
LayoutViewFunctions::cm_lay_rot_cw ()
{
  transform_layout (db::DCplxTrans (db::FTrans::r270));
}

void 
LayoutViewFunctions::cm_lay_free_rot ()
{
  bool ok = false;
  QString s = QInputDialog::getText (QApplication::activeWindow (), 
                                     QObject::tr ("Free rotation"), 
                                     QObject::tr ("Rotation angle in degree (counterclockwise)"), 
                                     QLineEdit::Normal, QString::fromUtf8 ("0.0"), 
                                     &ok);

  if (ok) {

    double angle = 0.0;
    tl::from_string (tl::to_string (s), angle);

    transform_layout (db::DCplxTrans (1.0, angle, false, db::DVector ()));

  }
}

void 
LayoutViewFunctions::cm_lay_scale ()
{
  bool ok = false;
  QString s = QInputDialog::getText (QApplication::activeWindow (), 
                                     QObject::tr ("Scaling"), 
                                     QObject::tr ("Scaling factor"), 
                                     QLineEdit::Normal, QString::fromUtf8 ("1.0"), 
                                     &ok);

  if (ok) {

    double scale = 0.0;
    tl::from_string (tl::to_string (s), scale);

    transform_layout (db::DCplxTrans (scale));

  }
}

void 
LayoutViewFunctions::cm_lay_move ()
{
  lay::MoveOptionsDialog options (view ());
  if (options.exec_dialog (m_move_dist)) {
    transform_layout (db::DCplxTrans (m_move_dist));
  }
}

void 
LayoutViewFunctions::cm_sel_flip_x ()
{
  db::DCplxTrans tr (db::DFTrans::m90);
  db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
  if (! sel_bbox.empty ()) {
    tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
  }
  do_transform (tr);
}

void 
LayoutViewFunctions::cm_sel_flip_y ()
{
  db::DCplxTrans tr (db::DFTrans::m0);
  db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
  if (! sel_bbox.empty ()) {
    tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
  }
  do_transform (tr);
}

void 
LayoutViewFunctions::cm_sel_rot_ccw ()
{
  db::DCplxTrans tr (db::DFTrans::r90);
  db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
  if (! sel_bbox.empty ()) {
    tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
  }
  do_transform (tr);
}

void 
LayoutViewFunctions::cm_sel_rot_cw ()
{
  db::DCplxTrans tr (db::DFTrans::r270);
  db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
  if (! sel_bbox.empty ()) {
    tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
  }
  do_transform (tr);
}

void 
LayoutViewFunctions::cm_sel_free_rot ()
{
  bool ok = false;
  QString s = QInputDialog::getText (QApplication::activeWindow (), 
                                     QObject::tr ("Free rotation"), 
                                     QObject::tr ("Rotation angle in degree (counterclockwise)"), 
                                     QLineEdit::Normal, QString::fromUtf8 ("0.0"), 
                                     &ok);

  if (ok) {

    double angle = 0.0;
    tl::from_string (tl::to_string (s), angle);

    db::DCplxTrans tr = db::DCplxTrans (1.0, angle, false, db::DVector ());
    db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
    if (! sel_bbox.empty ()) {
      tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
    }
    do_transform (tr);

  }
}

void 
LayoutViewFunctions::cm_sel_scale ()
{
  bool ok = false;
  QString s = QInputDialog::getText (QApplication::activeWindow (), 
                                     QObject::tr ("Scaling"), 
                                     QObject::tr ("Scaling factor"), 
                                     QLineEdit::Normal, QString::fromUtf8 ("1.0"), 
                                     &ok);

  if (ok) {

    double scale = 0.0;
    tl::from_string (tl::to_string (s), scale);

    db::DCplxTrans tr = db::DCplxTrans (scale);
    db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
    if (! sel_bbox.empty ()) {
      tr = db::DCplxTrans (sel_bbox.center () - db::DPoint ()) * tr * db::DCplxTrans (db::DPoint () - sel_bbox.center ());
    }
    do_transform (tr);

  }
}

void
LayoutViewFunctions::cm_sel_move_interactive ()
{
  if (view ()->move_service ()->begin_move ()) {
    view ()->switch_mode (-1);  //  move mode
  }
}

void 
LayoutViewFunctions::cm_sel_move_to ()
{
  db::DBox sel_bbox (view ()->lay::Editables::selection_bbox ());
  if (sel_bbox.empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Nothing selected to move")));
  }

  double x = sel_bbox.left () + (sel_bbox.width () * (1 + m_move_to_origin_mode_x) * 0.5);
  double y = sel_bbox.bottom () + (sel_bbox.height () * (1 + m_move_to_origin_mode_y) * 0.5);
  db::DPoint move_target (x, y);

  lay::MoveToOptionsDialog options (view ());
  if (options.exec_dialog (m_move_to_origin_mode_x, m_move_to_origin_mode_y, move_target)) {

    x = sel_bbox.left () + (sel_bbox.width () * (1 + m_move_to_origin_mode_x) * 0.5);
    y = sel_bbox.bottom () + (sel_bbox.height () * (1 + m_move_to_origin_mode_y) * 0.5);

    do_transform (db::DCplxTrans (move_target - db::DPoint (x, y)));

  }
}

void 
LayoutViewFunctions::cm_sel_move ()
{
  lay::MoveOptionsDialog options (view ());
  if (options.exec_dialog (m_move_dist)) {
    do_transform (db::DCplxTrans (m_move_dist));
  }
}

void
LayoutViewFunctions::cm_copy_layer ()
{
  struct { int *cv; int *layer; } specs [] = {
    { &m_copy_cva, &m_copy_layera },
    { &m_copy_cvr, &m_copy_layerr }
  };

  for (unsigned int i = 0; i < sizeof (specs) / sizeof (specs[0]); ++i) {

    int &cv = *(specs[i].cv);
    int &layer = *(specs[i].layer);

    if (cv >= int (view ()->cellviews ())) {
      cv = -1;
    }

    int index = view ()->active_cellview_index ();
    if (cv < 0) {
      cv = index;
    }

    if (cv < 0 || ! view ()->cellview (cv)->layout ().is_valid_layer ((unsigned int) layer)) {
      layer = -1;
    }

  }

  lay::DuplicateLayerDialog dialog (view ());
  if (dialog.exec_dialog (view (), m_copy_cva, m_copy_layera, m_copy_cvr, m_copy_layerr, m_duplicate_hier_mode, m_clear_before)) {

    bool supports_undo = true;

    if (manager () && manager ()->is_enabled ()) {

      lay::TipDialog td (QApplication::activeWindow (), 
                         tl::to_string (QObject::tr ("Undo buffering for the following operation can be memory and time consuming.\nChoose \"Yes\" to use undo buffering or \"No\" for no undo buffering. Warning: in the latter case, the undo history will be lost.\n\nChoose undo buffering?")), 
                         "copy-layer-undo-buffering",
                         lay::TipDialog::yesnocancel_buttons);

      lay::TipDialog::button_type button = lay::TipDialog::null_button;
      td.exec_dialog (button);
      if (button == lay::TipDialog::cancel_button) {
        return;
      }

      supports_undo = (button == lay::TipDialog::yes_button);

    } else {
      supports_undo = false;
    }

    view ()->cancel ();

    if (manager ()) {
      if (! supports_undo) {
        manager ()->clear ();
      } else {
        manager ()->transaction (tl::to_string (QObject::tr ("Duplicate layer"))); 
      }
    }

    try {

      bool same_layout = (&view ()->cellview (m_copy_cvr)->layout () == &view ()->cellview (m_copy_cva)->layout ());
      if (same_layout && m_copy_layera == m_copy_layerr) {
        throw tl::Exception (tl::to_string (QObject::tr ("Source and target layer must not be identical for duplicate operation")));
      }

      if (m_duplicate_hier_mode == 0) {

        //  clear the result layer for all called cells in flat mode
        if (m_clear_before) {
          std::set<db::cell_index_type> called_cells;
          called_cells.insert (view ()->cellview (m_copy_cvr).cell_index ());
          view ()->cellview (m_copy_cvr).cell ()->collect_called_cells (called_cells);
          for (std::set<db::cell_index_type>::const_iterator c = called_cells.begin (); c != called_cells.end (); ++c) {
            view ()->cellview (m_copy_cvr)->layout ().cell (*c).shapes (m_copy_layerr).clear ();
          }
        }

        db::Cell &target_cell = *view ()->cellview (m_copy_cvr).cell ();

        if (! same_layout) {

          //  flat mode (different layouts)
          db::PropertyMapper pm (view ()->cellview (m_copy_cvr)->layout (), view ()->cellview (m_copy_cva)->layout ());
          for (db::RecursiveShapeIterator si (view ()->cellview (m_copy_cva)->layout (), *view ()->cellview (m_copy_cva).cell (), m_copy_layera); ! si.at_end (); ++si) {
            target_cell.shapes (m_copy_layerr).insert (*si, si.trans (), pm);
          }

        } else {

          //  flat mode (same layouts)
          tl::ident_map<db::Layout::properties_id_type> pm1;
          db::Shapes &res = target_cell.shapes (m_copy_layerr);
          
          db::Layout &layout = view ()->cellview (m_copy_cvr)->layout ();
          try {

            //  using update/start_layout and end_changes improves the performance since changing the 
            //  shapes collection will invalidate the layout and cause updates inside the RecursiveShapeIerator
            layout.update ();
            layout.start_changes ();
            for (db::RecursiveShapeIterator si (view ()->cellview (m_copy_cva)->layout (), *view ()->cellview (m_copy_cva).cell (), m_copy_layera); ! si.at_end (); ++si) {
              res.insert (*si, si.trans (), pm1);
            }
            layout.end_changes ();

          } catch (...) {
            layout.end_changes ();
            throw;
          }

        }
        
      } else if (m_duplicate_hier_mode == 1) {

        db::Cell &cell = *view ()->cellview (m_copy_cva).cell ();
        db::Cell &target_cell = *view ()->cellview (m_copy_cvr).cell ();

        if (m_clear_before) {
          target_cell.clear (m_copy_layerr);
        } 

        if (m_copy_cvr == m_copy_cva) {

          //  current cell only mode: identical cell
          cell.copy (m_copy_layera, m_copy_layerr);

        } else if (! same_layout) {

          //  current cell only mode (different layouts)
          db::PropertyMapper pm (view ()->cellview (m_copy_cvr)->layout (), view ()->cellview (m_copy_cva)->layout ());
          for (db::Shapes::shape_iterator si = view ()->cellview (m_copy_cva).cell ()->shapes (m_copy_layera).begin (db::ShapeIterator::All); ! si.at_end (); ++si) {
            target_cell.shapes (m_copy_layerr).insert (*si, pm);
          }

        } else {

          //  current cell only mode (same layouts, but different cells)
          for (db::Shapes::shape_iterator si = view ()->cellview (m_copy_cva).cell ()->shapes (m_copy_layera).begin (db::ShapeIterator::All); ! si.at_end (); ++si) {
            target_cell.shapes (m_copy_layerr).insert (*si);
          }

        }
        
      } else if (m_duplicate_hier_mode == 2) {

        //  subcells cell by cell - source and target layout must be identical
        std::set<db::cell_index_type> called_cells;
        view ()->cellview (m_copy_cva).cell ()->collect_called_cells (called_cells);
        called_cells.insert (view ()->cellview (m_copy_cva).cell_index ());

        db::Layout &layout = view ()->cellview (m_copy_cva)->layout ();
        for (std::set<db::cell_index_type>::const_iterator c = called_cells.begin (); c != called_cells.end (); ++c) {
          db::Cell &cell = layout.cell (*c);
          if (m_clear_before) {
            cell.clear (m_copy_layerr);
          } 
          cell.copy (m_copy_layera, m_copy_layerr);
        }

      }

      if (manager () && supports_undo) {
        manager ()->commit ();
      }

    } catch (...) {
      if (manager () && supports_undo) {
        manager ()->commit ();
      }
      throw;
    }

  }
}

void
LayoutViewFunctions::cm_new_layer ()
{
  int index = view ()->active_cellview_index ();

  if (index >= 0 && int (view ()->cellviews ()) > index) {

    const lay::CellView &cv = view ()->cellview (index);

    lay::NewLayerPropertiesDialog prop_dia (view ());
    if (prop_dia.exec_dialog (cv, m_new_layer_props)) {

      for (unsigned int l = 0; l < cv->layout ().layers (); ++l) {
        if (cv->layout ().is_valid_layer (l) && cv->layout ().get_properties (l).log_equal (m_new_layer_props)) {
          throw tl::Exception (tl::to_string (QObject::tr ("A layer with that signature already exists: ")) + m_new_layer_props.to_string ());
        }
      }

      view ()->transaction (tl::to_string (QObject::tr ("New layer")));

      unsigned int l = cv->layout ().insert_layer (m_new_layer_props);
      std::vector <unsigned int> nl;
      nl.push_back (l);
      view ()->add_new_layers (nl, index);
      view ()->update_content ();

      view ()->commit ();

    }

  }
}

void
LayoutViewFunctions::cm_align_cell_origin ()
{
  int cv_index = view ()->active_cellview_index ();
  if (cv_index >= 0) {

    const db::Cell *cell = view ()->cellview (cv_index).cell ();
    if (! cell) {
      return;
    }
    if (cell->is_proxy ()) {
      throw tl::Exception (tl::to_string (QObject::tr ("Cannot use this function on a PCell or library cell")));
    }

    lay::AlignCellOptionsDialog dialog (view ());
    if (dialog.exec_dialog (m_align_cell_options)) {

      view ()->clear_selection ();

      view ()->transaction (tl::to_string (QObject::tr ("Align cell origin")));

      db::Box bbox;

      if (m_align_cell_options.visible_only) {
        for (lay::LayerPropertiesConstIterator l = view ()->begin_layers (); !l.at_end (); ++l) {
          if (! l->has_children () && l->layer_index () >= 0 && l->cellview_index () == cv_index && l->visible (true /*real*/)) {
            bbox += cell->bbox (l->layer_index ());
          }
        }
      } else {
        bbox = cell->bbox ();
      }

      db::Coord refx, refy;
      switch (m_align_cell_options.mode_x) {
      case -1:
        refx = bbox.left ();
        break;
      case 1:
        refx = bbox.right ();
        break;
      default:
        refx = bbox.center ().x ();
        break;
      }
      switch (m_align_cell_options.mode_y) {
      case -1:
        refy = bbox.bottom ();
        break;
      case 1:
        refy = bbox.top ();
        break;
      default:
        refy = bbox.center ().y ();
        break;
      }

      db::Layout &layout = view ()->cellview (cv_index)->layout ();
      db::Cell &nc_cell = layout.cell (cell->cell_index ());

      db::Trans t (db::Vector (-refx + db::coord_traits<db::Coord>::rounded (m_align_cell_options.xpos / layout.dbu ()), -refy + db::coord_traits<db::Coord>::rounded (m_align_cell_options.ypos / layout.dbu ())));

      for (unsigned int i = 0; i < layout.layers (); ++i) {
        if (layout.is_valid_layer (i)) {
          db::Shapes &shapes = nc_cell.shapes (i);
          for (db::Shapes::shape_iterator s = shapes.begin (db::ShapeIterator::All); ! s.at_end (); ++s) {
            shapes.transform (*s, t);
          }
        }
      }

      for (db::Cell::const_iterator inst = nc_cell.begin (); ! inst.at_end (); ++inst) {
        nc_cell.transform (*inst, t);
      }

      if (m_align_cell_options.adjust_parents) {

        std::vector<std::pair<db::Cell *, db::Instance> > insts_to_modify;
        for (db::Cell::parent_inst_iterator pi = nc_cell.begin_parent_insts (); ! pi.at_end (); ++pi) {
          insts_to_modify.push_back (std::make_pair (& layout.cell (pi->parent_cell_index ()), pi->child_inst ()));
        }

        db::Trans ti (db::Vector (refx, refy));
        for (std::vector<std::pair<db::Cell *, db::Instance> >::const_iterator im = insts_to_modify.begin (); im != insts_to_modify.end (); ++im) {
          im->first->transform (im->second, db::Trans (db::Vector (im->second.complex_trans ().trans (db::Vector (refx, refy)))));
        }

      }

      view ()->commit ();

    }

  }
}

void
LayoutViewFunctions::cm_edit_layer ()
{
  lay::LayerPropertiesConstIterator sel = view ()->current_layer ();
  if (sel.is_null ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("No layer selected for editing it's properties")));
  }

  int index = sel->cellview_index ();
  if (sel->has_children () || index < 0 || int (view ()->cellviews ()) <= index || sel->layer_index () < 0) {
    throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected for editing it's properties")));
  }

  const lay::CellView &cv = view ()->cellview (index);

  db::LayerProperties layer_props = cv->layout ().get_properties ((unsigned int) sel->layer_index ());

  lay::NewLayerPropertiesDialog prop_dia (view ());
  if (prop_dia.exec_dialog (cv, layer_props)) {

    for (unsigned int l = 0; l < cv->layout ().layers (); ++l) {
      if (cv->layout ().is_valid_layer (l) && int (l) != sel->layer_index () && cv->layout ().get_properties (l).log_equal (layer_props)) {
        throw tl::Exception (tl::to_string (QObject::tr ("A layer with that signature already exists: ")) + layer_props.to_string ());
      }
    }

    view ()->transaction (tl::to_string (QObject::tr ("Edit layer")));

    cv->layout ().set_properties (sel->layer_index (), layer_props);

    lay::LayerProperties lp (*sel);
    lay::ParsedLayerSource s = lp.source (false);
    s.layer (layer_props.layer);
    s.datatype (layer_props.datatype);
    if (! layer_props.name.empty ()) {
      s.name (layer_props.name);
    } else {
      s.clear_name ();
    }
    lp.set_source (s);
    view ()->set_properties (sel, lp);

    view ()->update_content ();

    view ()->commit ();

  }
}

void
LayoutViewFunctions::cm_delete_layer ()
{
  std::vector<lay::LayerPropertiesConstIterator> sel = view ()->selected_layers ();
  std::sort (sel.begin (), sel.end (), CompareLayerIteratorBottomUp ());

  //  collect valid layers
  std::vector<lay::LayerPropertiesConstIterator> valid_sel;
  std::set<std::pair<db::Layout *, unsigned int> > valid_layers;
  for (std::vector<lay::LayerPropertiesConstIterator>::const_iterator si = sel.begin (); si != sel.end (); ++si) {
    int cv_index = (*si)->cellview_index ();
    const lay::CellView &cv = view ()->cellview (cv_index);
    if (!(*si)->has_children () && cv_index >= 0 && int (view ()->cellviews ()) > cv_index && (*si)->layer_index () >= 0 && cv.is_valid ()) {
      valid_sel.push_back (*si);
      valid_layers.insert (std::make_pair (&cv->layout (), (*si)->layer_index ()));
    }
  }

  if (valid_sel.empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("No or no valid layer selected for deleting them")));
  }

  view ()->cancel_edits ();
  view ()->clear_selection ();

  view ()->transaction (tl::to_string (QObject::tr ("Delete layers")));

  //  Hint: delete_layer must come before the layers are actually deleted in because
  //  for undo this must be the last thing to do (otherwise the layout is not propertly set up)

  for (std::vector<lay::LayerPropertiesConstIterator>::const_iterator si = valid_sel.begin (); si != valid_sel.end (); ++si) {
    lay::LayerPropertiesConstIterator lp = *si;
    view ()->delete_layer (lp);
  }

  for (std::set<std::pair<db::Layout *, unsigned int> >::const_iterator li = valid_layers.begin (); li != valid_layers.end(); ++li) {

    unsigned int layer_index = li->second;
    db::Layout *layout = li->first;

    for (db::Layout::iterator c = layout->begin (); c != layout->end (); ++c) {
      c->shapes (layer_index).clear ();
    }

    layout->delete_layer (layer_index);

  }

  view ()->update_content ();

  view ()->commit ();
}

void
LayoutViewFunctions::cm_clear_layer ()
{
  std::vector<lay::LayerPropertiesConstIterator> sel = view ()->selected_layers ();
  if (sel.empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("No layer selected for clearing")));
  }

  lay::ClearLayerModeDialog mode_dialog (view ());
  if (mode_dialog.exec_dialog (m_layer_hier_mode)) {

    view ()->cancel_edits ();
    view ()->clear_selection ();

    view ()->transaction (tl::to_string (QObject::tr ("Clear layer")));

    for (std::vector<lay::LayerPropertiesConstIterator>::const_iterator si = sel.begin (); si != sel.end (); ++si) {

      if (! (*si)->has_children () && (*si)->layer_index () >= 0 && view ()->cellview ((*si)->cellview_index ()).is_valid ()) {

        int layer_index = (*si)->layer_index ();
        const lay::CellView &cv = view ()->cellview ((*si)->cellview_index ());

        if (m_layer_hier_mode == 0) {
          cv.cell ()->clear ((unsigned int) layer_index);
        } else if (m_layer_hier_mode == 1) {

          cv.cell ()->clear ((unsigned int) layer_index);

          std::set <db::cell_index_type> called_cells;
          cv.cell ()->collect_called_cells (called_cells);
          for (std::set <db::cell_index_type>::const_iterator cc = called_cells.begin (); cc != called_cells.end (); ++cc) {
            cv->layout ().cell (*cc).clear ((unsigned int) layer_index);
          }

        } else {
          cv->layout ().clear_layer ((unsigned int) layer_index);
        }

      }

    }

    view ()->commit ();

  }
}

// ------------------------------------------------------------
//  Declaration of the "plugin" for the menu entries

class LayoutViewPluginDeclaration
  : public lay::PluginDeclaration
{
public:
  virtual void get_menu_entries (std::vector<lay::MenuEntry> &menu_entries) const
  {
    std::string at;

    //  secret menu entries
    at = "@secrets.end";
    menu_entries.push_back (lay::menu_item ("cm_paste_interactive", "paste_interactive:edit", at, tl::to_string (QObject::tr ("Paste Interactive"))));
    menu_entries.push_back (lay::menu_item ("cm_duplicate_interactive", "duplicate_interactive:edit", at, tl::to_string (QObject::tr ("Duplicate Interactive"))));
    menu_entries.push_back (lay::menu_item ("cm_sel_move_interactive", "sel_move_interactive:edit", at, tl::to_string (QObject::tr ("Move Interactive"))));
    menu_entries.push_back (lay::menu_item ("cm_select_next_item", "select_next_item:edit", at, tl::to_string (QObject::tr ("Select Next Item(Space)"))));
    menu_entries.push_back (lay::menu_item ("cm_select_next_item_add", "select_next_item_add:edit", at, tl::to_string (QObject::tr ("Select Next Item too(Shift+Space)"))));

    at = "edit_menu.edit_options_group";
    menu_entries.push_back (lay::menu_item ("cm_undo", "undo:edit", at, tl::to_string (QObject::tr ("Undo(Ctrl+Z)"))));
    menu_entries.push_back (lay::menu_item ("cm_redo", "redo:edit", at, tl::to_string (QObject::tr ("Redo(Ctrl+Y)"))));

    menu_entries.push_back (lay::separator ("basic_group", at));
    menu_entries.push_back (lay::submenu ("layout_menu:edit:edit_mode", at, tl::to_string (QObject::tr ("Layout"))));
    {
      std::string at = "edit_menu.layout_menu.end";
      menu_entries.push_back (lay::menu_item ("cm_lay_flip_x", "lay_flip_x:edit_mode", at, tl::to_string (QObject::tr ("Flip Horizontally"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_flip_y", "lay_flip_y:edit_mode", at, tl::to_string (QObject::tr ("Flip Vertically"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_rot_cw", "lay_rot_cw:edit_mode", at, tl::to_string (QObject::tr ("Rotate Clockwise"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_rot_ccw", "lay_rot_ccw:edit_mode", at, tl::to_string (QObject::tr ("Rotate Counterclockwise"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_free_rot", "lay_free_rot:edit_mode", at, tl::to_string (QObject::tr ("Rotation By Angle"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_scale", "lay_scale:edit_mode", at, tl::to_string (QObject::tr ("Scale"))));
      menu_entries.push_back (lay::menu_item ("cm_lay_move", "lay_move:edit_mode", at, tl::to_string (QObject::tr ("Move By"))));
      menu_entries.push_back (lay::separator ("cellop_group", at));
      menu_entries.push_back (lay::menu_item ("cm_lay_convert_to_static", "lay_convert_to_static:edit_mode", at, tl::to_string (QObject::tr ("Convert All Cells To Static"))));
    }

    menu_entries.push_back (lay::submenu ("cell_menu:edit:edit_mode", at, tl::to_string (QObject::tr ("Cell"))));
    {
      std::string at = "edit_menu.cell_menu.end";
      menu_entries.push_back (lay::menu_item ("cm_new_cell", "new_cell:edit:edit_mode", at, tl::to_string (QObject::tr ("New Cell"))));
      menu_entries.push_back (lay::menu_item ("cm_cell_delete", "delete_cell:edit:edit_mode", at, tl::to_string (QObject::tr ("Delete Cell"))));
      menu_entries.push_back (lay::menu_item ("cm_cell_rename", "rename_cell:edit:edit_mode", at, tl::to_string (QObject::tr ("Rename Cell"))));
      menu_entries.push_back (lay::menu_item ("cm_cell_replace", "replace_cell:edit:edit_mode", at, tl::to_string (QObject::tr ("Replace Cell"))));
      menu_entries.push_back (lay::menu_item ("cm_cell_flatten", "flatten_cell:edit:edit_mode", at, tl::to_string (QObject::tr ("Flatten Cell"))));
      menu_entries.push_back (lay::separator ("ops_group", at));
      menu_entries.push_back (lay::menu_item ("cm_adjust_origin", "adjust_cell_origin:edit:edit_mode", at, tl::to_string (QObject::tr ("Adjust Origin"))));
      menu_entries.push_back (lay::menu_item ("cm_cell_convert_to_static", "convert_cell_to_static:edit_mode", at, tl::to_string (QObject::tr ("Convert Cell To Static"))));
      menu_entries.push_back (lay::separator ("props_group", at));
      menu_entries.push_back (lay::menu_item ("cm_cell_user_properties", "user_properties", at, tl::to_string (QObject::tr ("User Properties"))));
    }

    menu_entries.push_back (lay::submenu ("layer_menu:edit:edit_mode", at, tl::to_string (QObject::tr ("Layer"))));
    {
      std::string at = "edit_menu.layer_menu.end";
      menu_entries.push_back (lay::menu_item ("cm_new_layer", "new_layer:edit:edit_mode", at, tl::to_string (QObject::tr ("New Layer"))));
      menu_entries.push_back (lay::menu_item ("cm_clear_layer", "clear_layer:edit:edit_mode", at, tl::to_string (QObject::tr ("Clear Layer"))));
      menu_entries.push_back (lay::menu_item ("cm_delete_layer", "delete_layer:edit:edit_mode", at, tl::to_string (QObject::tr ("Delete Layer"))));
      menu_entries.push_back (lay::menu_item ("cm_copy_layer", "copy_layer:edit:edit_mode", at, tl::to_string (QObject::tr ("Copy Layer"))));
      menu_entries.push_back (lay::menu_item ("cm_edit_layer", "edit_layer:edit:edit_mode", at, tl::to_string (QObject::tr ("Edit Layer Specification"))));
    }

    menu_entries.push_back (lay::submenu ("selection_menu:edit", at, tl::to_string (QObject::tr ("Selection"))));
    {
      std::string at = "edit_menu.selection_menu.end";
      menu_entries.push_back (lay::menu_item ("cm_sel_flip_x", "sel_flip_x", at, tl::to_string (QObject::tr ("Flip Horizontally"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_flip_y", "sel_flip_y", at, tl::to_string (QObject::tr ("Flip Vertically"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_rot_cw", "sel_rot_cw", at, tl::to_string (QObject::tr ("Rotate Clockwise"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_rot_ccw", "sel_rot_ccw", at, tl::to_string (QObject::tr ("Rotate Counterclockwise"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_free_rot", "sel_free_rot", at, tl::to_string (QObject::tr ("Rotation By Angle"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_scale", "sel_scale", at, tl::to_string (QObject::tr ("Scale"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_move", "sel_move", at, tl::to_string (QObject::tr ("Move By"))));
      menu_entries.push_back (lay::menu_item ("cm_sel_move_to", "sel_move_to", at, tl::to_string (QObject::tr ("Move To"))));
    }

    menu_entries.push_back (lay::separator ("utils_group", at));
    menu_entries.push_back (lay::submenu ("utils_menu:edit:edit_mode", at, tl::to_string (QObject::tr ("Utilities"))));

    menu_entries.push_back (lay::separator ("misc_group", at));
    menu_entries.push_back (lay::menu_item ("cm_delete", "delete:edit", at, tl::to_string (QObject::tr ("Delete(Del)"))));
    menu_entries.push_back (lay::menu_item ("cm_show_properties", "show_properties:edit", at, tl::to_string (QObject::tr ("Properties(Q)"))));

    menu_entries.push_back (lay::separator ("cpc_group", at));
    menu_entries.push_back (lay::menu_item ("cm_copy", "copy:edit", at, tl::to_string (QObject::tr ("Copy(Ctrl+C)"))));
    menu_entries.push_back (lay::menu_item ("cm_cut", "cut:edit", at, tl::to_string (QObject::tr ("Cut(Ctrl+X)"))));
    menu_entries.push_back (lay::menu_item ("cm_paste", "paste:edit", at, tl::to_string (QObject::tr ("Paste(Ctrl+V)"))));
    menu_entries.push_back (lay::menu_item ("cm_duplicate", "duplicate:edit", at, tl::to_string (QObject::tr ("Duplicate(Ctrl+B)"))));

    menu_entries.push_back (lay::separator ("modes_group", at));
    menu_entries.push_back (lay::submenu ("mode_menu", at, tl::to_string (QObject::tr ("Mode"))));

    menu_entries.push_back (lay::submenu ("select_menu", at, tl::to_string (QObject::tr ("Select"))));
    {
      std::string at = "edit_menu.select_menu.end";
      menu_entries.push_back (lay::menu_item ("cm_select_all", "select_all", at, tl::to_string (QObject::tr ("Select All"))));
      menu_entries.push_back (lay::menu_item ("cm_unselect_all", "unselect_all", at, tl::to_string (QObject::tr ("Unselect All"))));
      menu_entries.push_back (lay::separator ("edit_select_basic_group", at));
      menu_entries.push_back (lay::menu_item ("lv:enable_all", "enable_all", at, tl::to_string (QObject::tr ("Enable All"))));
      menu_entries.push_back (lay::menu_item ("lv:disable_all", "disable_all", at, tl::to_string (QObject::tr ("Disable All"))));
      menu_entries.push_back (lay::separator ("edit_select_individual_group", at));
    };

    menu_entries.push_back (lay::separator ("cancel_group", at));
    menu_entries.push_back (lay::menu_item ("cm_cancel", "cancel", at, tl::to_string (QObject::tr ("Cancel(Esc)"))));

    at = "bookmark_menu.end";
    menu_entries.push_back (lay::submenu ("goto_bookmark_menu", at, tl::to_string (QObject::tr ("Goto Bookmark"))));
    menu_entries.push_back (lay::menu_item ("cm_bookmark_view", "bookmark_view", at, tl::to_string (QObject::tr ("Bookmark This View"))));

    menu_entries.push_back (lay::separator ("bookmark_mgm_group", at));
    menu_entries.push_back (lay::menu_item ("cm_manage_bookmarks", "manage_bookmarks", at, tl::to_string (QObject::tr ("Manage Bookmarks"))));
    menu_entries.push_back (lay::menu_item ("cm_load_bookmarks", "load_bookmarks", at, tl::to_string (QObject::tr ("Load Bookmarks"))));
    menu_entries.push_back (lay::menu_item ("cm_save_bookmarks", "save_bookmarks", at, tl::to_string (QObject::tr ("Save Bookmarks"))));
    menu_entries.push_back (lay::submenu ("open_recent_menu_bookmarks", at, tl::to_string (QObject::tr ("Recent Bookmark Files"))));

    at = "zoom_menu.end";
    menu_entries.push_back (lay::submenu ("global_trans", at, tl::to_string (QObject::tr ("Global Transformation"))));
    {
      std::string at = "zoom_menu.global_trans.end";
      menu_entries.push_back (lay::config_menu_item ("r0", at, tl::to_string (QObject::tr ("\\(r0\\)<:/r0.png>")), cfg_global_trans, "?r0 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("r90", at, tl::to_string (QObject::tr ("\\(r90\\)<:/r90.png>")), cfg_global_trans, "?r90 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("r180", at, tl::to_string (QObject::tr ("\\(r180\\)<:/r180.png>")), cfg_global_trans, "?r180 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("r270", at, tl::to_string (QObject::tr ("\\(r270\\)<:/r270.png>")), cfg_global_trans, "?r270 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("m0", at, tl::to_string (QObject::tr ("\\(m0\\)<:/m0.png>")), cfg_global_trans, "?m0 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("m45", at, tl::to_string (QObject::tr ("\\(m45\\)<:/m45.png>")), cfg_global_trans, "?m45 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("m90", at, tl::to_string (QObject::tr ("\\(m90\\)<:/m90.png>")), cfg_global_trans, "?m90 *1 0,0"));
      menu_entries.push_back (lay::config_menu_item ("m135", at, tl::to_string (QObject::tr ("\\(m135\\)<:/m135.png>")), cfg_global_trans, "?m135 *1 0,0"));
    }

    menu_entries.push_back (lay::separator ("hier_group", at));
    menu_entries.push_back (lay::menu_item ("cm_max_hier", "max_hier", at, tl::to_string (QObject::tr ("Full Hierarchy(*)"))));
    menu_entries.push_back (lay::menu_item ("cm_max_hier_0", "max_hier_0", at, tl::to_string (QObject::tr ("Box Only(0)"))));
    menu_entries.push_back (lay::menu_item ("cm_max_hier_1", "max_hier_1", at, tl::to_string (QObject::tr ("Top Level Only(1)"))));
    menu_entries.push_back (lay::menu_item ("cm_inc_max_hier", "inc_max_hier", at, tl::to_string (QObject::tr ("Increment Hierarchy(+)"))));
    menu_entries.push_back (lay::menu_item ("cm_dec_max_hier", "dec_max_hier", at, tl::to_string (QObject::tr ("Decrement Hierarchy(-)"))));

    menu_entries.push_back (lay::separator ("zoom_group", at));
    menu_entries.push_back (lay::menu_item ("cm_zoom_fit", "zoom_fit", at, tl::to_string (QObject::tr ("Zoom Fit(F2)"))));
    menu_entries.push_back (lay::menu_item ("cm_zoom_fit_sel", "zoom_fit_sel", at, tl::to_string (QObject::tr ("Zoom Fit Selection(Shift+F2)"))));
    menu_entries.push_back (lay::menu_item ("cm_zoom_in", "zoom_in", at, tl::to_string (QObject::tr ("Zoom In(Ctrl++)"))));
    menu_entries.push_back (lay::menu_item ("cm_zoom_out", "zoom_out", at, tl::to_string (QObject::tr ("Zoom Out(Ctrl+-)"))));
    /* disabled because that interferes with the use of the arrow keys for moving the selection
    MenuLayoutEntry::separator ("pan_group");
    menu_entries.push_back (lay::menu_item ("cm_pan_up", "pan_up", at, tl::to_string (QObject::tr ("Pan Up(Up)"))));
    menu_entries.push_back (lay::menu_item ("cm_pan_down", "pan_down", at, tl::to_string (QObject::tr ("Pan Down(Down)"))));
    menu_entries.push_back (lay::menu_item ("cm_pan_left", "pan_left", at, tl::to_string (QObject::tr ("Pan Left(Left)"))));
    menu_entries.push_back (lay::menu_item ("cm_pan_right", "pan_right", at, tl::to_string (QObject::tr ("Pan Right(Right)"))));
    */

    menu_entries.push_back (lay::separator ("redraw_group", at));
    menu_entries.push_back (lay::menu_item ("cm_redraw", "redraw", at, tl::to_string (QObject::tr ("Redraw"))));
    menu_entries.push_back (lay::separator ("state_group", at));
    menu_entries.push_back (lay::menu_item_copy ("cm_prev_display_state", "prev_display_state", at, "@toolbar.prev_display_state"));
    menu_entries.push_back (lay::menu_item_copy ("cm_next_display_state", "next_display_state", at, "@toolbar.next_display_state"));

    menu_entries.push_back (lay::separator ("select_group", at));
    menu_entries.push_back (lay::menu_item ("cm_select_cell", "select_cell:edit", at, tl::to_string (QObject::tr ("Select Cell"))));
    menu_entries.push_back (lay::menu_item ("cm_select_current_cell", "select_current_cell", at, tl::to_string (QObject::tr ("Show As New Top(Ctrl+S)"))));
    menu_entries.push_back (lay::menu_item ("cm_goto_position", "goto_position", at, tl::to_string (QObject::tr ("Goto Position(Ctrl+G)"))));

    //  Add a hook for inserting new items after the modes
    menu_entries.push_back (lay::separator ("end_modes", "@toolbar.end"));

  }

  bool menu_activated (const std::string &symbol) const
  {
    if (symbol == "lv:enable_all") {

      for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
        cls->set_editable_enabled (true);
      }
      return true;

    } else if (symbol == "lv:disable_all") {

      for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
        cls->set_editable_enabled (false);
      }
      return true;

    } else {
      return false;
    }
  }

  void implements_primary_mouse_modes (std::vector<std::pair<std::string, std::pair<std::string, int> > > &modes)
  {
    std::vector <std::string> mode_titles;
    lay::LayoutView::intrinsic_mouse_modes (&mode_titles);

    int mode_id = 0;
    for (std::vector <std::string>::const_iterator t = mode_titles.begin (); t != mode_titles.end (); ++t, --mode_id) {
      //  modes: pair(title, pair(insert_pos, id))
      modes.push_back (std::make_pair (*t, std::make_pair ("edit_menu.mode_menu.end;@toolbar.end_modes", mode_id)));
    }
  }

  lay::Plugin *create_plugin (db::Manager *manager, Dispatcher *, LayoutView *view) const
  {
    return new LayoutViewFunctions (manager, view);
  }
};

static tl::RegisteredClass<lay::PluginDeclaration> config_decl (new LayoutViewPluginDeclaration (), -10, "LayoutViewPlugin");

} // namespace lay
