// -*- C++ -*-
// $RCSfile: meshcmds.C,v $
// $Revision: 1.14 $
// $Author: langer $
// $Date: 2000/10/30 19:23:27 $

/* This software was produced by NIST, an agency of the U.S. government,
 * and by statute is not subject to copyright in the United States.
 * Recipients of this software assume all responsibilities associated
 * with its operation, modification and maintenance. However, to
 * facilitate maintenance we ask that before distributing modifed
 * versions of this software, you first contact the authors at
 * oof_manager@ctcms.nist.gov. 
 */

#include "adaptmesh.h"
#include "amtriangle.h"
#include "elector.h"
#include "fiddlemesh.h"
#include "filename.h"
#include "gfx.h"
#include "goof.h"
#include "imageform.h"
#include "menuDef.h"
#include "meshcmds.h"
#include "meshgroups.h"
#include "mouseclick.h"
#include "memusage.h"

static Menu *meshgroupmenu();
static CommandFn selectnonecmd();
static MeshGroup *getgroup();



int meshexists() {
  if(!current_goof) {
    garcon()->msout << ms_error << "No image!" << endl << ms_normal;
    return 0;
  }
  if(!current_goof->mesh()) {
    garcon()->msout << ms_error << "No mesh!" << endl << ms_normal;
    return 0;
  }
  return 1;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// these functions are called to indicate that the rules for
// transferring pixel properties to triangles have changed, so that if
// properties are being transferred automatically, they should be
// transferred the next time they're needed.

// should be static, but Sun doesn't like it...
void group_rulechange() {
  if(current_goof) {
    AdaptiveMesh *mesh = current_goof->mesh();
    if(mesh) {
      mesh->groups_need_recomputing();
      mesh->inherit_pixel_groups();
      current_goof->redraw();
    }
  }
}

static void groups_timechange(const CharString&) {
  if(current_goof) {
    AdaptiveMesh *mesh = current_goof->mesh();
    if(mesh)
      mesh->groups_timechange();
  }
}

void AdaptiveMesh::groups_timechange() {
  if(grouptransfertime == CONTINUALLY) {
    if(groups_rules_changed > groups_recompute_performed)
      ++groups_recompute_requested;
  }
}

void AdaptiveMesh::groups_need_recomputing() {
  // Called by other routines to indicate that groups are out of date.
  ++groups_rules_changed;
  if(grouptransfertime == CONTINUALLY)
    ++groups_recompute_requested;
}

// should be static, but Sun doesn't like it...
void material_rulechange() {
  if(current_goof) {
    AdaptiveMesh *mesh = current_goof->mesh();
    if(mesh) {
      mesh->materials_need_recomputing();
      mesh->inherit_pixel_materials();
      current_goof->redraw();
    }
  }
}

static void material_timechange(const CharString&) {
  if(current_goof) {
    AdaptiveMesh *mesh = current_goof->mesh();
    if(mesh)
      mesh->material_timechange();
  }
}

void AdaptiveMesh::material_timechange() {
  // If the transfertime has just been changed to CONTINUALLY, then if
  // the rules have changed since the last recomputation, a new
  // computation is required.
  if(materialtransfertime == CONTINUALLY) {
    if(material_rules_changed > material_recompute_performed)
      ++material_recompute_requested;
  }
}

void AdaptiveMesh::materials_need_recomputing() {
  // Called by other routines to indicate that materials are out of
  // date.
  ++material_rules_changed;
  if(materialtransfertime == CONTINUALLY)
    ++material_recompute_requested;
}

// when called from menus, these need a dummy argument
static void group_rulechange(const CharString&) {
  group_rulechange();
}

static void material_rulechange(const CharString&) {
  material_rulechange();
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

class CreateMeshCmd : public CommandM {
private:
  int nx, ny;
public:
  CreateMeshCmd()
    : CommandM("create", "create initial mesh"), nx(1), ny(1)
  {
    AddArgument(this, "nx", nx);
    AddArgument(this, "ny", ny);
  }
  virtual CommandFn func() {
    if(current_goof) {
      if(nx <= 0 || ny <= 0) {
	garcon()->msout << ms_error << "Mesh dimensions must be positive!"
			<< endl << ms_normal;
      }
      else {
	material_rulechange();
	group_rulechange();
	current_goof->new_mesh(nx, ny);
	current_goof->redraw();
      }
    }
  }
  virtual CommandM *clone() const {
    CreateMeshCmd *cmc = new CreateMeshCmd;
    cmc->nx = nx;
    cmc->ny = ny;
    return cmc;
  }
};

static CommandFn destroymesh() {
  if(meshexists()) {
    current_goof->destroy_mesh();
    current_goof->redraw();
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// mesh stack manipulations

static CommandFn undomesh() {
  if(meshexists()) {
    if(!current_goof->prev_mesh())
      garcon()->msout << ms_error << "No previous mesh!" << endl
		      << ms_normal;
    else
      current_goof->redraw();
  }
}

static CommandFn redomesh() {
  if(meshexists()) {
    if(!current_goof->next_mesh())
      garcon()->msout << ms_error << "No next mesh!" << endl << ms_normal;
    else
      current_goof->redraw();
  }
}

static CommandFn oldestmesh() {
  if(meshexists()) {
    current_goof->oldest_mesh();
    current_goof->redraw();
  }
}

static CommandFn newestmesh() {
  if(meshexists()) {
    current_goof->newest_mesh();
    current_goof->redraw();
  }
}

Menu *meshstackmenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new Menu("mesh stack");
  menu->AddCommand("undo", "undo mesh modifications", undomesh);
  menu->AddCommand("redo", "redo previously undone mesh modification",
		   redomesh);
  menu->AddCommand("newest", "display most recent mesh", newestmesh);
  menu->AddCommand("oldest", "display oldest mesh", oldestmesh);
  AddVariable(menu, "stacksize", "number of meshes to retain",
	      Goof::meshstacksize, Goof::set_meshstacksize);
  return menu;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

class MeshOutputCmd : public CommandM {
private:
  FileName filename;
public:
  MeshOutputCmd()
    : CommandM("save", "write an OOF file"),
      filename("out.goof")
    {
      AddArgument(this, "filename", filename);
    }
  virtual CommandFn func() {
    if(meshexists())
      current_goof->mesh()->writegoof(filename);
  }
  virtual CommandM *clone() const {
    MeshOutputCmd *moc = new MeshOutputCmd;
    moc->filename = filename;
    return moc;
  }
};

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

static CommandFn findErange() {
  if(meshexists()) {
    double min, max;
    current_goof->mesh()->Erange(min, max);
    garcon()->msout << "min=" << min << "  max=" << max << endl;
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Mesh Refinement Commands

class RefineCmd : public CommandM {
private:
  RefinementCondition * const condition;
  int iterations;
public:
  RefineCmd(RefinementCondition *rc)
    : condition(rc),
      iterations(1),
      CommandM(rc->name(), "refine by " + rc->name())
    {
      AddArgument(this, "iterations", iterations);
      condition->AddArgs(this);
    }
  virtual ~RefineCmd() { delete condition; }
  virtual CommandFn func() {
    if(meshexists()) {
//        if(AdaptiveMesh::alpha == 1.0 && AMTriangle::divisionmethod == TDM_E) {
//  	garcon()->msout << ms_error
//  			<< "alpha_E=1 is incompatible with division=smallest_E"
//  			<< endl
//  			<< "Mesh not refined!" << endl << ms_normal;
//  	return;
	  
//        }
      if(condition->setup()) {
	current_goof->dup_mesh();
#ifdef MEMUSAGE
	print_memusage("after duplicating mesh");
#endif
	current_goof->mesh()->refine(iterations, *condition);
#ifdef MEMUSAGE
	print_memusage("after refining mesh");
#endif
	current_goof->redraw();
      }
      else
	garcon()->msout << ms_error << "Cannot refine mesh!"
			<< endl << ms_normal;
    }
  }
  virtual CommandM *clone() const {
    RefineCmd *rc = new RefineCmd(condition->clone());
    rc->iterations = iterations;
    return rc;
  }
};


static CommandFn refinegroup() {
  if(getgroup()) {
    current_goof->dup_mesh();
    // Have to get the group again, since it lives in the mesh, and
    // we've duplicated the mesh!
    MeshGroup *g = getgroup();
    g->weed();
    current_goof->mesh()->refine_group(1, g);
    current_goof->redraw();
  }
}

template <>
VarObject *Var<Enum<TriangleDivisionMethod> >::create_varobject(Variable *var,
		Form *form, VarObjectType t, int x, int y, int w, int h)
{
  return new EnumVarObject<TriangleDivisionMethod>(var, form, t, x, y, w, h);
}

static Menu *refinemenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new Menu("refine");
  menu->AddCommand(new RefineCmd(new RefineAll));
  menu->AddCommand(new RefineCmd(new RefineE));
  menu->AddCommand(new RefineCmd(new RefineSelected));
  menu->AddCommand(new RefineCmd(new RefineDepth));
  menu->AddCommand(new RefineCmd(new RefineInterface));
  menu->AddCommand(new RefineCmd(new RefineDoubleInterface));
  menu->AddSubMenu("group", "refine a group of triangles",
		   new GroupMenu<AMTriangle*>("refine group", "refine group",
					      refinegroup));
  AddVariable(menu, "division", "how to divide triangles",
	      AMTriangle::divisionmethod);
  AddVariable(menu, "min_area", "don't divide triangles smaller than this",
	      AdaptiveMesh::min_area);
  AddVariable(menu, "max_divisions",\
 "limit on number of subdivisions per triangle per refinement iteration",
	      AdaptiveMesh::max_divisions);
  return menu;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Group management commands

static MeshGroup *getgroup() {
  if(!meshexists()) return 0;
  CharString whichgroup = garcon()->get_command();
  return current_goof->mesh()->get_group(whichgroup);
}

// add selected triangles to a group

static CommandFn addtriangles() {
  MeshGroup *g = getgroup();
  if(g) {
    AdaptiveMesh *mesh = current_goof->mesh();
    for(AMIterator i(mesh, AMI_SELECTED); !i.end(); ++i)
      g->append((*mesh)[i]);
  }
}

// remove selected triangles from a group

static CommandFn subtracttriangles() {
  MeshGroup *g = getgroup();
  if(g) {
    AdaptiveMesh *mesh = current_goof->mesh();
    for(AMIterator i(mesh, AMI_SELECTED); !i.end(); ++i)
      g->remove((*mesh)[i]);
  }
}

// select triangles in a group

static CommandFn selectgroup() {
  MeshGroup *g = getgroup();
  if(g) {
    current_goof->mesh()->select(*g);
    current_goof->redraw();
  }
}

static CommandFn selectgroup2() {
  MeshGroup *g = getgroup();
  if(g) {
    current_goof->mesh()->select_too(*g);
    current_goof->redraw();
  }
}

static Menu *selectmshgrpmnu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new GroupMenu<AMTriangle*>("select group",
				   "select only triangles in group",
				   selectgroup);
  menu->AddCommand("none", "unselect all triangles", selectnonecmd);
  return menu;
}

static Menu *selectmshgrpmnu2() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new GroupMenu<AMTriangle*>("select group too",
				   "also select all triangles in group",
				   selectgroup2);
  menu->AddCommand("none", "unselect all triangles", selectnonecmd);
  return menu;
}

// create a new group

class NewMeshGroupCmd : public CommandM {
private:
  CharString name;
public:
  NewMeshGroupCmd() : CommandM("new", "create a new group"), 
    name("groupname")
  {
    AddArgument(this, "name", name);
  }
  virtual CommandFn func() {
    if(meshexists()) {
      char *c;
      int modified = 0;
      while(c = (char*) strchr(name, ' ')) { // cast required by Sun
	*c = '_';
	modified = 1;
      }
      if(modified) garcon()->msout << ms_info << "Group name changed to \""
				   << name << "\"" << endl << ms_normal;
      current_goof->mesh()->add_group(name);
    }
  }
  virtual CommandM *clone() const {
    NewMeshGroupCmd *ngc = new NewMeshGroupCmd;
    ngc->name = name;
    return ngc;
  }
};

// get information on a group

static CommandFn infogroup() {
  MeshGroup *g = getgroup();
  if(g) {
    g->weed();
    double grouparea = 0.0;
    for(int i=0; i<g->size(); i++)
      grouparea += (*g)[i]->area();
    garcon()->msout << "Group " << g->query_name() << " contains " << g->size()
		    << " triangles, and "
		    << grouparea << " pixels  ("
		    << 100.*grouparea/
                      (current_goof->query_height()*current_goof->query_width())
		    << "% of total area)" << endl;
  }
}

// transfer groups from pixels to mesh

static CommandFn transfergroups() {
  if(meshexists()) {
    current_goof->mesh()->inherit_pixel_groups(1);
    current_goof->redraw();
  }
}

CommandFn removegrpcmd() {
  MeshGroup *g = getgroup();
  if(g) {
    current_goof->mesh()->remove_group(g);
    material_rulechange();
    group_rulechange();
  }
}

CommandFn removeallgrps() {
  if(meshexists()) {
    current_goof->mesh()->remove_all_groups();
    material_rulechange();
    group_rulechange();
  }
}

static Menu *deletegrpmenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new GroupMenu<AMTriangle*>("delete group", "delete group",
				    removegrpcmd);
  menu->AddCommand("all", "delete all groups", removeallgrps);
  return menu;
}

static Menu *meshgroupmenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new Menu("groups");
  menu->AddCommand(new NewMeshGroupCmd);
  menu->AddSubMenu("select", "select a group of triangles", selectmshgrpmnu());
  menu->AddSubMenu("add", "add selected triangles to a group",
		   new GroupMenu<AMTriangle*>("add to group",
				   "add selected triangles to group",
					      addtriangles));
  menu->AddSubMenu("subtract", "remove selected triangles from a group",
		   new GroupMenu<AMTriangle*>("remove from group",
				   "remove selected triangles from group",
					      subtracttriangles));
  menu->AddSubMenu("delete", "delete a group", deletegrpmenu());
  menu->AddSubMenu("info", "print information about a group",
		   new GroupMenu<AMTriangle*>("group info",
					      "print information about group",
					      infogroup));

  Menu *electmenu = menu->AddSubMenu("election_rules",
			       "criteria for electing groups");
  AddVariable(electmenu, "matl_type", "elect by material type",
	      group_elector.mattype, group_rulechange);
  AddVariable(electmenu, "matl_type/params",
	      "elect by material type and parameters",
	      group_elector.mattypeparam, group_rulechange);
  AddVariable(electmenu, "group_membership", "elect by pixel group membership",
	      group_elector.groupmemb, group_rulechange);

  menu->AddCommand("transfer", "transfer groups from pixels to triangles",
		   transfergroups);
  AddVariable(menu, "transfer_method",
	      "how to transfer groups from pixels to triangles",
	      grouptransfermethod, group_rulechange);
  AddVariable(menu, "transfer_time",
	      "when to transfer groups from pixels to triangles",
	      grouptransfertime, groups_timechange);
  AddVariable(menu, "transfer_replace",
	      "replace existing groups or add to them?",
	      grouptransferreplace);
  return menu;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// transfer materials from pixels to mesh

static CommandFn transfermaterials() {
  if(meshexists()) {
    current_goof->mesh()->inherit_pixel_materials(1);
    current_goof->redraw();
  }
}

static Menu *meshmaterialmenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new Menu("mesh");

  AddVariable(menu, "transfer_method",
	      "how to transfer groups from pixels to triangles",
	      materialtransfermethod, material_rulechange);
  AddVariable(menu, "transfer_time",
	      "when to transfer groups from pixels to triangles",
	      materialtransfertime, material_timechange);
  menu->AddCommand("transfer", "transfer materials from pixels to triangles",
		   transfermaterials);

  Menu *electmenu = menu->AddSubMenu("election_rules",
			       "criteria for electing materials");
  AddVariable(electmenu, "group_membership", "elect by pixel group membership",
	      material_elector.groupmemb, material_rulechange);
  return menu;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// Triangle selection commands

static CommandFn selectallcmd() {
  if(meshexists()) {
    current_goof->mesh()->select_all();
    current_goof->redraw();
  }
}


static CommandFn selectnonecmd() {
  if(meshexists()) {
    current_goof->mesh()->unselect_all();
    current_goof->redraw();
  }
}

static CommandFn selectdimples() {
  if(meshexists()) {
    int n = current_goof->mesh()->select_dimples();
    garcon()->msout << ms_info << "Selected " << n << " additional "
		    << (n==1? "triangle." : "triangles.")
		    << endl << ms_normal;
    current_goof->redraw();
  }
}

static CommandFn selectthumbs() {
  if(meshexists()) {
    int n = current_goof->mesh()->unselect_sorethumbs();
    garcon()->msout << ms_info << "Unselected " << n
		    << (n==1? " triangle." :" triangles.")
		    << endl << ms_normal;
    current_goof->redraw();
  }
}

static CommandFn select_interface() {
  if(meshexists()) {
    int n = current_goof->mesh()->select_interface();
    garcon()->msout << ms_info << "Selected " << n
		    << (n==1?" triangle." : " triangles.")
		    << endl << ms_normal;
    current_goof->redraw();
  }
}

static CommandFn select_neighbors() {
  // select neighbors of selected triangles. Mostly for debugging.
  if(meshexists()) {
    int n = current_goof->mesh()->select_neighbors();
    garcon()->msout << ms_info << "Selected " << n
		    << (n==1? " triangle." : " triangles.")
		    << endl << ms_normal;
    current_goof->redraw();
  }
}

class SelectECmd : public CommandM {
private:
  double min, max;
public:
  SelectECmd()
    : min(0), max(1),
      CommandM("E", "select triangles with a range of E values")
    {
      AddArgument(this, "min", min);
      AddArgument(this, "max", max);
    }
  virtual CommandFn func() {
    if(meshexists()) {
      int n = current_goof->mesh()->selectE(min, max);
      garcon()->msout << ms_info << "Selected " << n
		      << (n==1? " triangle." : " triangles.")
		      << endl << ms_normal;
      current_goof->redraw();
    }
  }
  virtual CommandM *clone() const {
    SelectECmd *sec = new SelectECmd;
    sec->min = min;
    sec->max = max;
    return sec;
  }
};

static Menu *selectmenu() {
  static Menu *menu = 0;
  if(menu) return menu;
  menu = new Menu("select");
  menu->AddCommand("all", "select all triangles", selectallcmd);
  menu->AddCommand("none", "select no triangles", selectnonecmd);
  menu->AddCommand(new SelectECmd);
  menu->AddCommand("dimples", "select triangles with 2 or 3 selected neighbors",
		   selectdimples);
  menu->AddCommand("thumbs",
		   "unselect triangles with fewer than 2 selected neighbors",
		   selectthumbs);
  menu->AddCommand("interface",
	       "select all triangles with neighbors of a different material",
		   select_interface);
  menu->AddCommand("neighbors",
		   "select all neighbors of currently selected triangles",
		   select_neighbors);
  menu->AddSubMenu("group", "select mesh groups", selectmshgrpmnu());
  menu->AddSubMenu("group_too", "select additional mesh groups",
		   selectmshgrpmnu2());
  return menu;
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

static CommandFn sanity() {
  if(meshexists())
    current_goof->mesh()->sanity();
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

Menu *meshmenu() {
  static Menu *menu = 0;
  if(menu) return menu;

  Enum<GroupTransferMethod>::name(GTM_ELECTION, "election");
  Enum<GroupTransferMethod>::name(GTM_ALLPIXELS, "all");
  Enum<GroupTransferMethod>::name(GTM_CENTERPIXEL, "center");
  Enum<MaterialTransferMethod>::name(MTM_ELECTION, "election");
  Enum<MaterialTransferMethod>::name(MTM_CENTERPIXEL, "center");
  Enum<TransferTime>::name(CONTINUALLY, "automatic");
  Enum<TransferTime>::name(EXPLICITLY, "on_demand");

  menu = new Menu("adaptive mesh");
  menu->AddCommand(new CreateMeshCmd);
  menu->AddCommand("destroy", "destroy the mesh", destroymesh);
  menu->AddCommand(new FiddleCmd);
  menu->AddCommand(new FiddleDownHillCmd);
  menu->AddCommand(new FiddleLaplaceCmd);
  menu->AddCommand("swap_two", "swap two triangles", swapedges);
  menu->AddCommand("swap_worst", "make swaps that lower energy", swapworst);
  menu->AddCommand("E_range", "find range of E values", findErange);
  menu->AddCommand(new MeshOutputCmd);
  menu->AddSubMenu("groups", "manage triangle groups", meshgroupmenu());
  menu->AddSubMenu("materials", "transfer materials from pixels to mesh",
		   meshmaterialmenu());
  menu->AddSubMenu("refine", "refine the mesh", refinemenu());
  menu->AddSubMenu("select", "select triangles", selectmenu());
  menu->AddSubMenu("stack", "manipulate stack of old meshes", meshstackmenu());
  AddVariable(menu, "alpha_E", "balance between homogeneity and shape",
	      AdaptiveMesh::alpha);
  AddVariable(menu, "redraw", "redraw mesh continuously?",
	      AdaptiveMesh::continuous_redraw);
  menu->AddCommand("sanity_check", "check mesh", sanity);
  return menu;
}

template <>
VarObject *Var<Enum<GroupTransferMethod> >::create_varobject(Variable *var,
	Form *form, VarObjectType t, int x, int y, int w, int h)
{
  return new EnumVarObject<GroupTransferMethod>(var, form, t, x, y, w, h);
}

template <>
VarObject *Var<Enum<MaterialTransferMethod> >::create_varobject(Variable *var,
	Form *form, VarObjectType t, int x, int y, int w, int h)
{
  return new EnumVarObject<MaterialTransferMethod>(var, form, t, x, y, w, h);
}

template <>
VarObject *Var<Enum<TransferTime> >::create_varobject(Variable *var,
	Form *form, VarObjectType t, int x, int y, int w, int h)
{
  return new EnumVarObject<TransferTime>(var, form, t, x, y, w, h);
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

void select_mesh_triangle(const MeshCoord &where, int shiftkey) {
  if(meshexists()) {
    if(!shiftkey)
      current_goof->mesh()->unselect_all();
    AMTriangle *clicked = current_goof->mesh()
      ->smallest_triangle_containing(where);
    if(clicked && clicked->active())
      clicked->toggle_select();
  }
}


void select_mesh_triangles(const MeshCoord &c1, const MeshCoord &c2,
			   int shiftkey)
{
  if(meshexists()) {
    double xmin, xmax, ymin, ymax;
    if(c1.x < c2.x) {
      xmin = c1.x;
      xmax = c2.x;
    }
    else {
      xmin = c2.x;
      xmax = c1.x;
    }
    if(c1.y < c2.y) {
      ymin = c1.y;
      ymax = c2.y;
    }
    else {
      ymin = c2.y;
      ymax = c1.y;
    }

    AdaptiveMesh *mesh = current_goof->mesh();
    if(!shiftkey)
      mesh->unselect_all();

    for(AMIterator i(mesh, AMI_ALL); !i.end(); ++i) {
      AMTriangle *tri = (*mesh)[i];
      if(tri->active()) {
	MeshCoord ctr(tri->center());
	if(ctr.x >= xmin && ctr.x <= xmax && ctr.y >= ymin && ctr.y <= ymax) {
	  if(shiftkey)
	    tri->toggle_select();
	  else
	    tri->select();
	}
      }
    }
  }
}

//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//

// include template definitions

#include "enumio.C"
