/*

    GEO, Postgres GIS (Geografic Information System) frontend

    Copyright (C) 1991  TNO Institute for Perception, The Netherlands
			Written by: Tom Vijlbrief

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.


Note that parts of this distribution are taken from the ET++
distribution. See the et2.2/ subdirectories for details.

===============================================================================
Tom Vijlbrief
TNO Institute for Perception		Phone: +31 34 63 562 11
P.O. Box 23				Fax:   +31 34 63 539 77
3769 ZG  Soesterberg			E-mail: tom@izf.tno.nl
The Netherlands				or: uunet!hp4nl.nluug.nl!tnosoes!tom
===============================================================================

*/

static char *sccs_id= (sccs_id, "@(#)DrawView.c	3.14 7/28/92");

//$DrawView$
#include "Class.h"
#include "DrawView.h"
#include "ObjList.h"
#include "Commands.h"
#include "Menu.h"
#include "Document.h"
#include "TextShape.h"
#include "TextCmd.h"
#include "Group.h"
#include "CmdNo.h"
#include "CollView.h"
#include "ClipBoard.h"
#include "System.h"
#include "Window.h"
#include "WindowPort.h"
#include "Alert.h"
#include "FileDialog.h"
#include "ObjInt.h"
#include "ImageShape.h"
#include "PictureShape.h"
#include "Picture.h"

#include "DrawDocument.h"
#include "Army.h"
#include "Geo.h"
#include "Query.h"
#include "IconShape.h"
#include "PolyShape.h"
#include "QuelInfo.h"
#include "TableDialog.h"
#include "MenuItems.h"

extern Clipper *gClipper;

#ifdef ET25
const int cPOLYGONMENU  = 1125,
	  cGridSize     = 8;
#else
const int cARRANGEMENU  = 1121,
	  cOPTIONMENU  = 1124,
	  cPOLYGONMENU  = 1125,
	  cGridSize     = 8;
#endif
	  

class AsyncPortalHandler: public SysEvtHandler {
  static bool	is_active;
  DrawView	*dv;
  int		delay_cnt;
  int		call_rate;
  bool		terminated;
public:
  AsyncPortalHandler(DrawView *the_dv, int n_sec): SysEvtHandler(0)
	{ dv= the_dv; call_rate= 5 * n_sec; }
  bool HasInterest()
	{ return !terminated && !is_active && !dv->IsBusy() && dv->IsOpen(); }
  void Terminate()
	{ terminated= TRUE; }
  void Notify(SysEventCodes, int) {
	if (++delay_cnt >= call_rate) {
	    is_active= TRUE;
	    delay_cnt= 0;
	    dv->AsyncRefresh();
	    is_active= FALSE;
	}
  }
};

bool	AsyncPortalHandler::is_active;


//---- DrawView ----------------------------------------------------------------

extern bool	GEONOOVER;

static int	ap_count= 0;


MetaImpl(DrawView, (TP(activeTextView), TP(currShape), T(lastClick), T(grid),
	TB(showGrid), TP(shapes), TP(selection),
	0));

DrawView::DrawView(Document *dp, Point extent, ObjList *sl,
	float Long_Min, float Long_Max, float Lat_Min, float Lat_Max)
	    : (dp, extent, 0, Long_Min, Long_Max, Lat_Min, Lat_Max)
{
    showGrid= TRUE;
    grid= 1; // cGridSize;
    activeText= 0;
    activeTextView= 0;
    lastClick = Point(100);
    shapes= sl;
    currShape= 0;
    selection= new ObjList;
    armyDialog= new ArmySymbolDialog;
}


DrawView::~DrawView()
{
    SafeDelete(selection);
    SafeDelete(armyDialog);

    if (ap_handler) {
#if 0
      ap_handler->Remove();
#else
      ((AsyncPortalHandler*) ap_handler)->Terminate();
#endif
      ap_handler= 0;
    }
}

//---- initializing ------------------------------------------------------------

void DrawView::ChangeShapes()
{
    SetSelection(0);

    Iter next(GetMapOverlayCol()->MakeIterator());
    TextItem *layer;
    ObjList *shl= 0;

    while (layer= (TextItem *)next()) {
	    bool	new_load;
	    shl= geo->GetOverlayCol(layer, &new_load);
	    if (new_load) {
		shl->ForEach(Shape,SetContainer)(this);
	    }
	    shl->ForEach(Shape,AreaChanged)();
    }
    if (shl) {
	if (shapes_alloc) {
	  shapes->FreeAll(); // Should be empty ?
	  delete shapes;
	  shapes_alloc= 0;
	}
	shapes= shl;
    } else if (!shapes_alloc) {
	shapes= new ObjList;
	shapes_alloc= TRUE;
    }
}


//---- drawing -----------------------------------------------------------------


void DrawView::Invalidate(ObjList *ol)
{
    Rectangle bbox;
    Iter next(ol);
    Shape *s;
    
    HourOn();

    while (s= (Shape*)next())
	  bbox.Merge(s->InvalRect());
    InvalidateRect(bbox);

    HourOff();
}


void DrawView::TopDamaged()
{
#ifdef ET25
#else
    if (::gBatch) {
#endif
#ifdef ET25
	SeqCollection *c= GetMapOverlayCol();
#else
	Collection *c= GetMapOverlayCol();
#endif
	int	n_overlay= c->Size();
	if (n_overlay) {
		TextItem 	*layer= (TextItem*) c->At(n_overlay-1);
		bool		new_load;

		Invalidate(geo->GetOverlayCol(layer, &new_load));
	}
#ifdef ET25
#else
    } else
	InvalidateRect(Rectangle(0,0,1,1)); // Hack !!!
#endif
}


void DrawView::DisplayCol(Rectangle r, Collection *cp)
{
    Iter next(Guard(cp,ObjList));
    Shape *s;
	    
    bool new_area= AreaIsChanged();

    int old_blink_col= blink_col;
    blink_col= 0;

    while (s= (Shape*) next()) {
      if (new_area)
	s->AreaChanged();
      s->DrawAll(r);
    }
    blink_col= old_blink_col;
}



void DrawView::DrawOverlays(Rectangle r)
{
    Collection	*c= GetMapOverlayCol();
    Iter	next(c->MakeIterator());
    int		n_overlay= c->Size();
    int		cnt= 0;
    TextItem	*layer;
    Shape	*s;

    while ((layer= (TextItem *)next()) || n_overlay == 0) {

	    cnt++;
	    if (blink_count && cnt != n_overlay)
		continue;

	    bool	new_load;
	    ObjList	*lshapes= cnt == n_overlay || n_overlay == 0 ?
				shapes :
				geo->GetOverlayCol(layer, &new_load);

#ifdef ET25
	    Iter previous(lshapes,FALSE);
#else
	    RevIter previous(lshapes);
#endif
	    
	    bool new_area= AreaIsChanged();
	    int col= blink_count ? cur_blink_col: 0;

	    while (s= (Shape*) previous()) {
		if (new_area)
		    s->AreaChanged();
		s->DrawAll(r, col, FALSE);
	    }

	    if (n_overlay == 0)
	      break;
    }

    if (! gPrinting && selection) {
	Iter next(selection);

	while (s= (Shape*)next())
	    if (! s->GetDeleted())
		s->Highlight(On);
    }
}

void DrawView::Draw(Rectangle r)
{
    if (Loading())
	return;
    
    is_busy= TRUE;

    if (q_blink_layer) {
      MapView::Draw(r);
      return;
    }

    HourOn();

    MapView::Draw(r);
    DrawOverlays(r);

    HourOff();

    is_busy= FALSE;
}

//---- shape list management ---------------------------------------------------
    
void DrawView::Insert(Shape *s)
{
    SetSelection(0);
    s->SetContainer(this);
#ifdef ET25
    shapes->AddAt(s,0);
#else
    shapes->Insert(s);
#endif
    selection->Add(s);
    InvalidateRect(s->InvalRect());
    ShowSelection();
}

void DrawView::Remove(Shape *s)
{
    shapes->RemovePtr(s);
}

void DrawView::InsertShapes(ObjList *ol, bool tofront= TRUE)
{
#ifdef ET25
    Iter previous(ol,FALSE);
#else
    RevIter previous(ol);
#endif
    Shape *s;
    
    SetSelection(0);
    while (s= (Shape*) previous()) {
	if (! s->IsKindOf(Shape))
	    continue;
	s->SetContainer(this);
	if (tofront)
#ifdef ET25
	    shapes->AddAt(s,0);
#else
	    shapes->Insert(s);
#endif
	else
	    shapes->Add(s);
	selection->Add(s);
    }
    Invalidate(ol);
    ShowSelection();
}

Shape *DrawView::FindShape(Point p)
{
    Iter next(shapes);
    Shape *s;
    
    HourOn();

    while (s= (Shape*)next())
	if (s->ContainsPoint1(p)) {
	    HourOff();
	    return s;
	}
    HourOff();
    return 0;
}

QueryShape *DrawView::FindQueryShape(Point p, TextItem **ti)
{
    HourOn();

    OrdCollection	*c= GetQueryCol();
    Iter	next(c->MakeReversedIterator());
    TextItem	*layer, *nearlayer;
    QueryShape	*s, *nears= 0;
    int		mindist= 10000;

    const int	maxdist= 20;
    const Rectangle minr(p.x - maxdist, p.y - maxdist, 2*maxdist, 2*maxdist);

    while (layer= (TextItem *)next()) {
	    if (quel->DontPick(layer))
		continue;

	    ObjList	*qshapes= quel->GetQueryCol(layer, FALSE, FALSE, FALSE);
	    Iter	next(qshapes->MakeReversedIterator());
	    while (s= (QueryShape*)next()) {
		if (!s->GetBBox().Intersects(minr))
		    continue;
		int dist= s->Distance(p);
		if (dist >= 0 && dist < mindist) {
		    mindist= dist;
		    nears= s;
		    nearlayer= layer;
		}
	    }
    }
    HourOff();
    if (nears && mindist < maxdist) {
	char m[100];

	*ti= nearlayer;
	char *layer_name= nearlayer->AsString();
	if (strcmp(layer_name, "NoName") == 0)
	  layer_name= quel->GetTableName(nearlayer);
	sprintf(m, "Found Object in layer: %s", layer_name);
	SetMessage(m);
	return nears;
    }
    SetMessage("No Object Found");
    return 0;
}

Shape *DrawView::FindHandle(Point p, int *nh)
{
    Iter next(selection);
    Shape *s;
    int handle;
    
    while (s= (Shape*)next())
	if ((handle= s->PointOnHandle(p)) >= 0) {
	    *nh= handle;
	    return s;
	}
    return 0;
}


// Geo-ops

static Point		grab_point;
static GeoPos		grab_gp;
static QueryShape	*sel_shape;

WindowPort		*map_grab_port;
#ifdef ET25
#else
extern WindowPort	*grabport;
#endif

void DrawView::InitGrab()
{
  GrSetPort(map_grab_port= (WindowPort*) GetWindow()->GetPortDesc());
#ifdef ET25
  WindowPort *oldgrab= WindowSystem::grabport;
  WindowSystem::grabport= map_grab_port;
  while (map_grab_port)
    gSystem->InnerLoop();
  WindowSystem::grabport= oldgrab;
#else
  WindowPort *oldgrab= grabport;
  grabport= map_grab_port;
  while (map_grab_port)
    gSystem->InnerLoop();
  grabport= oldgrab;
#endif
}

void DrawView::EndGrab()
{
  map_grab_port= 0;
}

static bool	makenewpoint;

bool DrawView::MakeNewPoint(char *p, bool match, Rectangle *inval_r)
{
  const int	MAX_DIM= 96;

  match_point= match;
  makenewpoint= TRUE;
  InitGrab();
  *inval_r= Rectangle(grab_point.x-MAX_DIM/2, grab_point.y-MAX_DIM/2,
	MAX_DIM, MAX_DIM);
  // sprintf(p, "(%f,%f)", XToLong(grab_point.x), YToLat(grab_point.y));
  sprintf(p, "(%f,%f)", grab_gp.GetLong(), grab_gp.GetLat());
  makenewpoint= FALSE;
  GrSetCursor(GetCursor(grab_point));
  PerformCommand(gResetUndo);
  match_point= FALSE;
  return TRUE;
}


static bool selecting_shape;

inline bool SelectingShape() {
  return selecting_shape;
}


QueryShape * DrawView::SelectShape()
{
  selecting_shape= TRUE;
  InitGrab();
  selecting_shape= FALSE;
  return sel_shape;
}


static int FloatLen(float f)
{
#if 0
  int len= 3; // 1 digit, a point and a delimiter: 7.,
  if (f < 0) {
    len++;
    f= -f;
  }
  if (f >= 10)
    len++;
  if (f >= 100)
    len++;
  return len;
#else
  char str[100];

  sprintf(str, "%.0f", f);
  return strlen(str)+1;
#endif
}


static bool FormatPath(PolyShape *ps, char *p)
{
    const int	MaxAttrLen= 8000;
    int		np= ps->GetPtCnt();

    if (np <= 1)
      return FALSE;

    int	i, len= 0;
    for (i= 0; i < np; i++) {
      len+= FloatLen(ps->gpts[i].GetLong());
      len+= FloatLen(ps->gpts[i].GetLat());
    }
    int ndec= (MaxAttrLen-len)/(2*np);
    if (ndec < 1) {
      ShowAlert(eAlertNote, "Sorry, the path is too long (> %d)", MaxAttrLen);
      return FALSE;
    } else if (ndec > 6)
      ndec= 6;

    sprintf(p, "(%d,%d,", ps->pts[0] == ps->pts[np-1] ? 1: 0, np);
    for (i= 0; i < np; i++) {
      char pstr[50];
      sprintf(pstr, "%.*f,%.*f%c",
	ndec, ps->gpts[i].GetLong(),
	ndec, ps->gpts[i].GetLat(),
	i == np-1 ? ')': ',');
      strcat(p, pstr);
    }
    return TRUE;
}


const int BBoxExpand= 5;

bool DrawView::MakeNewPath(char *p, bool match, Rectangle *inval_r)
{
  match_point= match;
  map_grab_port= (WindowPort*) 1; // Fool RequestTool
  RequestTool(4);
  UpdateEvent();
  InitGrab();
  UpdateEvent();
  bool retval;
  if (gisShape) {
    PolyShape	*ps= Guard(gisShape, PolyShape);
    *inval_r= gisShape->bbox.Expand(BBoxExpand);
    retval= FormatPath(ps, p);
  } else
    retval= FALSE;

  delete gisShape;
  gisShape= 0;
  PerformCommand(gResetUndo);
  match_point= FALSE;
  return retval;
}


bool		path_edit;
static Shape	*edit_shape;

bool DrawView::EditPath(char *p, bool match, Rectangle *inval_r)
{
  char		*pp= p;
  int		np;
  PolyShape	*sh= new PolyShape();

  match_point= match;
  sh->SetContainer(this);
  edit_shape= sh;
  pp+= 3;
  np= atoi(pp);

  sh->npts= sh->size= np;
  sh->pts= new Point[np];
  sh->gpts= new GeoPos[np];
  for (int pcnt= 0; np > 0; np--, pcnt++) {
    float	lng, lat;

    while(*pp++ != ',')
	;
    lng= atof(pp);
    while(*pp++ != ',')
	;
    lat= atof(pp);

    //sh->AddPt(GeoToPoint(lng, lat));
    sh->pts[pcnt]= sh->gpts[pcnt]= GeoPos(sh, lng, lat);
  }
  sh->CalcBBox();
  sh->Invalidate();
  sh->Shape::Init(sh->bbox.NW(), sh->bbox.SE());

  Insert(sh);
  sh->SetSplit(TRUE);
  UpdateEvent();
  path_edit= FALSE;
  InitGrab();

  *inval_r= sh->bbox.Expand(BBoxExpand);
  Remove(sh);
  SetSelection(0);
  UpdateEvent();
  edit_shape= 0;
  bool retval;
  if (path_edit) {
    retval= FormatPath(sh, p);
  } else
    retval= FALSE;
  PerformCommand(gResetUndo);
  delete sh;
  match_point= FALSE;
  return retval;
}

//---- misc --------------------------------------------------------------------

void DrawView::Point2Grid(Point *np)  // constrain to viewsize and align to grid
{
    Point p= Min(GetExtent(), Max(gPoint3, *np));
    *np= ((p+grid/2) / grid) * grid;
}

void DrawView::ConstrainScroll(Point *p)
{
    if (activeTextView)
	activeTextView->ConstrainScroll(p);
}

void DrawView::RequestTool(int tix)
{
    Control(GetId(), 0, (void*) tix);
}

void DrawView::SetTool(Object *cs)
{
    Shape *oldShape= currShape;

    if (cs && cs->IsKindOf(Shape)) {
	if (GetMapOverlayCol()->Size() == 0 && !IsGrabbing()) {
		ShowAlert(eAlertNote,"There is not a top overlay:\nCreate (or Load) an overlay before proceeding.");
		RequestTool(0);
	        return;
	}
	// ((DrawDocument*) GetDocument())->DoMakeInfo();
	currShape= (Shape*) cs;
    } else
	currShape= 0;
	
    // enter text mode
    if ((oldShape == 0 || !oldShape->IsKindOf(TextShape)) &&
				currShape && currShape->IsKindOf(TextShape)) {
	PerformCommand(gResetUndo);
	SetSelection(0);
    }

    // leave text mode
    if ((currShape == 0 || !currShape->IsKindOf(TextShape)) &&
				oldShape && oldShape->IsKindOf(TextShape)) {
	PerformCommand(gResetUndo);
	SetSelection(0);
	SetActiveText(0);
    }

    if (currShape && currShape->IsKindOf(ArmyShape)) {
	SetSelection(0);
	armyDialog->Show();
	if (!armyDialog->Valid())
	    RequestTool(0);
    }
}


void DrawView::SetMessage(char *m)
{
  ((DrawDocument*) GetDocument())->SetMessage(m);
}


void DrawView::ShowInfo(TrackPhase tp, char *fmt, ...)
{
    if (tp == eTrackRelease)
	Control(GetId(), 123, "");
    else {
	char buf[100];
	va_list ap;
	va_start(ap, fmt);
	vsprintf(buf, fmt, ap);
	va_end(ap);
	Control(GetId(), 123, buf);
    }
}


//---- Selection ---------------------------------------------------------------

Shape *DrawView::OneSelected()
{
    if (Selected() != 1)
	return 0;
    return (Shape*)selection->First();
}

ObjList *DrawView::GetCopyOfSelection()
{
    //RemoveDeleted();
    return (ObjList*)selection->Clone();
}

ObjList *DrawView::GetDeepCopyOfSelection()
{
    //RemoveDeleted();
    return (ObjList*)selection->DeepClone();
}

ObjList *DrawView::SetSelection(ObjList *newsel)
{
    ObjList *oldsel= selection;
    
    if (selection->Size()) {
	selection->ForEach(Shape,SetSplit)(FALSE);
	Invalidate(selection);
	SafeDelete(selection);
	selection= 0;
	UpdateEvent();
    }
    if (newsel) {
	selection= (ObjList*) newsel->Clone();
	Invalidate(selection);
	ShowSelection();   
    } else
	selection= new ObjList;
    return oldsel;
}

void DrawView::SetDeleted(ObjList* ol, bool b)
{
    ol->ForEach(Shape,SetDeleted)(b);
    Invalidate(ol);
    selection->RemoveAll(ol); // tom
}

void DrawView::RemoveDeleted()
{
    Iter next(shapes);
    register Shape *s;

    while (s= (Shape*) next())
	if (s->IsGarbage()) {
	    Object *tmp= shapes->Remove(s);
	    delete tmp;
	}
}

void DrawView::ShowSelection()
{
    Rectangle bbox= BoundingBox();
    RevealRect(bbox.Expand(8), bbox.extent/3);
}

void DrawView::SelectInRect(Rectangle r)
{
    Iter next(shapes);
    register Shape *s;
    
    while (s= (Shape*)next())
	if (!s->GetDeleted() && r.ContainsRect(s->bbox))
	    selection->Add(s);
    Invalidate(shapes);
}

Rectangle DrawView::BoundingBox()
{
    Rectangle bbox;
    Iter next(selection);
    Shape *s;
    
    while (s= (Shape*)next())
	bbox.Merge(s->bbox);
    return bbox;
}

bool DrawView::HasSelection()
{
    return (bool) (activeTextView || selection->Size() > 0);
}

//---- text --------------------------------------------------------------------

void DrawView::SetActiveText(TextShape *tp)
{
    if (activeText) {
	selection->Remove(activeText);
	// activeText->Invalidate();
#ifdef ET25
	activeTextView->ClearSelection();
#else
	activeTextView->SetNoSelection();
#endif
	activeText= 0;
	activeTextView= 0;
	// UpdateEvent();
    }
    activeText= tp;
    activeTextView= 0;
    if (activeText) {
	activeTextView= activeText->GetTextView();
	if (! selection->ContainsPtr(activeText))
	    selection->Add(activeText);
	// activeText->Invalidate();
    }
    UpdateEvent();
}

//---- event handling ----------------------------------------------------------


Command *DrawView::DoMiddleButtonDownCommand(Point p, Token t, int)
{
    // a NOP command so Doc will ask for save:
    GetDocument()->PerformCommand(new Command(12345));
    return new AreaSelector(this, overView);
}

class WaitButUpCommand: public Command {
  TextItem	*ti;
  DrawView	*dv;
  QueryShape	*qs;
  int		oldcol;
public:
  MetaDef(WaitButUpCommand);
  WaitButUpCommand(DrawView *d, TextItem *t, QueryShape *i);
  Command *TrackMouse(TrackPhase, Point, Point, Point);
};

MetaImpl0(WaitButUpCommand);

WaitButUpCommand::WaitButUpCommand(DrawView *d, TextItem *t, QueryShape *i):
    ti(t), dv(d), qs(i)
{
  oldcol= qs->GetColor();
  qs->SetColor(NrInks()-1);		// last color
  dv->InvalidateRect(qs->GetBBox());
  dv->UpdateEvent();
};


Command *WaitButUpCommand::TrackMouse(TrackPhase atp, Point ap, Point, Point np)
{
  switch (atp) {
  case eTrackExit:
  case eTrackRelease:
    qs->SetColor(oldcol);
    dv->InvalidateRect(qs->GetBBox());
    dv->UpdateEvent();
    if (Abs(ap-np) < gPoint3) {
      dv->SetMessage("");
      if (!SelectingShape()) {

	EditInfo	*ei;
	QuelInfo	*qi;
	bool		do_edit;

	if (ei= (qi= GetInfo(TRUE))->GetEditInfo(ti->AsString())) {
	  typedef int (*pif)(QueryShape*, DrawView*, TextItem*);
	  pif pf= (pif) GetFuncPtr(ei->File(), ei->Func());
	  if (pf)
	    do_edit= (*pf)(qs,dv,ti);
	  else
	    do_edit= FALSE;
	} else
	  do_edit= TRUE;

	if (do_edit)
          dv->GetQueries()->EditShape(ti, qs->GetOid(),
	    (Object*) dv->GetDialogView()->GetQueryDialog());
      }
    } else {
      dv->SetMessage("Object selection cancelled (moved mouse)");
      sel_shape= 0;
    }
    dv->EndGrab();
    return gNoChanges;
  default:
    return this;
  }
}


class GrabPointCommand: public Command {
  DrawView	*dv;
public:
  GrabPointCommand(DrawView *d) { dv= d; }
  Command *TrackMouse(TrackPhase, Point, Point, Point);
};


Command *GrabPointCommand::TrackMouse(TrackPhase atp, Point ap, Point, Point np)
{
  switch (atp) {
  case eTrackExit:
  case eTrackRelease:

    if (! (dv->MatchPoint() && dv->FindPoint(np, &grab_point, &grab_gp))) {
      grab_gp= GeoPos(0, dv->XToLong(np.x), dv->YToLat(np.y));
      grab_point= np;
    }

    dv->EndGrab();
    return gNoChanges;
  default:
    return this;
  }
}

Command *DrawView::DoLeftButtonDownCommand(Point p, Token t, int)
{
    if (makenewpoint && IsGrabbing())
	return new GrabPointCommand(this);
      
    Shape	*ShapeUnderMouse, *s;
    int		handle;
    SketchModes sm= eSMDefault;
    bool	inselection;
    
    if (t.Flags & eFlgShiftKey)
	sm|= eSMSquare;
    if (t.Flags & eFlgCntlKey)
	sm|= eSMCenter;
	
    if (currShape == 0 || !currShape->IsKindOf(TextShape))  // exit text mode
	SetActiveText(0);
	
    QueryShape *is;
    TextItem *ti;

    if (SelectingShape() || !IsGrabbing()) {
      if (is= (QueryShape*) FindQueryShape(p, &ti)) {
	sel_shape= is;
	SetSelection(0);
	return new WaitButUpCommand(this, ti, is);
      } else if (GEONOOVER) {
	return gNoChanges;
      } else {
	ShapeUnderMouse= FindShape(p);
      }
    } else if (edit_shape) {
      ShapeUnderMouse= FindShape(p);
      if (ShapeUnderMouse != edit_shape)
	ShapeUnderMouse= 0;
    } else
      ShapeUnderMouse= 0;

    lastClick= p;
    
    if (currShape) {    // not pointer mode
	if (currShape->IsKindOf(TextShape) && ShapeUnderMouse
			&& ShapeUnderMouse->IsKindOf(TextShape)) {  // Text Mode
	    SetActiveText((TextShape*) ShapeUnderMouse);
	    return activeTextView->DispatchEvents(p, t, focus);
	}
	SetSelection(0);
	return currShape->NewSketcher(this, sm);
    }
    
    if (s= FindHandle(p, &handle))
	return s->NewStretcher(this, handle);

    if (ShapeUnderMouse && (t.Flags & eFlgCntlKey)
    && ShapeUnderMouse->IsKindOf(PolyShape)) {
	if (((PolyShape*)ShapeUnderMouse)->InjectPoint(p))
	  return gNoChanges;
    }

    if (ShapeUnderMouse == 0) {
	if (IsGrabbing()) {
	  EndGrab();
	  return gNoChanges;
	}
	SetSelection(0);
	return new ShapeSelector(this);
    } 
    
    inselection= selection->ContainsPtr(ShapeUnderMouse);
    if (! inselection) {
	if (! (t.Flags & eFlgShiftKey))
	    SetSelection(0);
	selection->Add(ShapeUnderMouse);
	((DrawDocument*)GetDocument())->ShowInfo(ShapeUnderMouse);
	ShapeUnderMouse->Invalidate();
    } else {
	if (t.Flags & eFlgShiftKey) {
	    selection->Remove(ShapeUnderMouse);
	    ShapeUnderMouse->Invalidate();
	}
    }
    if (selection->ContainsPtr(ShapeUnderMouse)) {
	if ((handle= ShapeUnderMouse->PointOnHandle(p)) >= 0)
	    return ShapeUnderMouse->NewStretcher(this, handle);
	return new ShapeDragger(this, ShapeUnderMouse);
    }
    return gNoChanges;
}

#ifdef ET25
Command *DrawView::DoKeyCommand(int code, Token t)
#else
Command *DrawView::DoKeyCommand(int code, Point lp, Token t)
#endif
{
    TextShape *ts;
    
    if (GetMapOverlayCol()->Size() == 0) {
	    return gNoChanges;
    }

    if (activeTextView)
#ifdef ET25
	return activeTextView->DispatchEvents(t.Pos, t, focus);
#else
	return activeTextView->DispatchEvents(lp, t, focus);
#endif

#ifdef ET25
    if (code == '\b')
	 // return new DeleteCommand(this);
	return new SCutCopyCommand(this, cDELETE, "delete");
#else
    if (code == gBackspace)
	return new SCutCopyCommand(this, cDELETE, "delete");
#endif
	
    //----- enter text mode and create an attached TextShape
    Shape *chief= OneSelected();
    RequestTool(1);
    
    //---- on text shape and selected hence append typed character at end
    if (chief && chief->IsKindOf(TextShape)) { 
	SetSelection(0);
	SetActiveText((TextShape*)chief);
	activeTextView->SetSelection(cMaxInt, cMaxInt);
#ifdef ET25
	return activeTextView->DispatchEvents(t.Pos, t, focus);
#else
	return activeTextView->DispatchEvents(lp, t, focus);
#endif
    }
    
    //---- create new textshape
    SetSelection(0);
    ts= (TextShape*) currShape->Clone();
    ts->SetContainer(this);
    if (chief) {
	Rectangle textRect= chief->GetTextRect();
	ts->Init(textRect.NW(), textRect.SE());
	ts->MakeDependentOn(chief); // make the text object dependend if there is a chief
    } else
	ts->Init(lastClick, lastClick);
    Insert(ts);
    SetActiveText(ts);
    
#ifdef ET25
    return activeTextView->DoKeyCommand(code, t);
#else
    return activeTextView->DoKeyCommand(code, lp, t);
#endif
}

#ifdef ET25
Command *DrawView::DoCursorKeyCommand(EvtCursorDir cd, Token t)
#else
Command *DrawView::DoCursorKeyCommand(EvtCursorDir cd, Point p, Token t)
#endif
{
    if (activeTextView)  
#ifdef ET25
	return activeTextView->DoCursorKeyCommand(cd, t);
#else
	return activeTextView->DoCursorKeyCommand(cd, p, t);
#endif
	
    if (selection->Size() > 0) {
	Point delta= t.CursorPoint() * grid;
	Rectangle bb= BoundingBox() + delta;
	delta+= bb.AmountToTranslateWithin(GetExtent());
	return new CursorMoveCommand(this, delta);
    }
#ifdef ET25
    return View::DoCursorKeyCommand(cd, t);
#else
    return View::DoCursorKeyCommand(cd, p, t);
#endif
}

GrCursor DrawView::GetCursor(Point lp)
{
    if (makenewpoint)
	return eCrsCross;
    if (currShape)
	return currShape->SketchCursor();
    return View::GetCursor(lp);
}

//---- menus -------------------------------------------------------------------

#ifdef ET25
Menu *DrawView::MakeMenu(int menuId)
#else
void DrawView::DoCreateMenu(Menu *menu)
#endif
{
#ifdef ET25
    Menu *m;
    int i= 0;

    switch (menuId) {
    case cARRANGEMENU:
        m= new Menu("Arrange");
        m->SetId(cARRANGEMENU);
        m->AppendItems( "Bring to Front",   cTOFRONT,
                        "Send to Back",     cTOBACK,
                        "-",
                        "Group",            cGROUP,
                        "Ungroup",          cUNGROUP,
                        "Connect",          cCONNECT,
                        0);
        break;

    case cPENINKMENU:
        m= new Menu("Pen Ink", FALSE, FALSE, 0, 8);
        m->SetId(cPENINKMENU);
        m->AppendItem("None", cFIRSTPPAT);
        for (i= 1; InkPalette[i]; i++)
            m->Append(new PatternItem(cFIRSTPPAT+i, InkPalette[i]));
        break;

    case cINKMENU:
        m= new Menu("Ink", FALSE, FALSE, 0, 8);
        m->SetId(cINKMENU);
        m->AppendItem("None", cFIRSTPAT);
        for (i= 1; InkPalette[i]; i++)
            m->Append(new PatternItem(cFIRSTPAT+i, InkPalette[i]));
        break;

    case cLINEMENU:
        m= new Menu("Lines", FALSE, FALSE);
        m->SetId(cLINEMENU);
        for (i= 0; i < 4; i++)
            m->Append(new MenuButtonItem(cFIRSTPEN+i,
                    new LineStyleItem(cIdNone, 1, (GrLineCap)i)));
        m->AppendItems("-", 0);
        m->Append(new MenuButtonItem(cFIRSTPENSIZE, "Hairline"));
        for (i= 1; i <= 30; i++)
            m->Append(new MenuButtonItem(cFIRSTPENSIZE+i,
                    new LineStyleItem(cIdNone, i)));
        break;

    case cOPTIONMENU:
        m= new Menu("Options", FALSE, FALSE);
        m->SetId(cOPTIONMENU);
        m->AppendItems("Grid Off",          cGRID,
                       "Hide Grid",         cSHWGRID,
                       0);
        break;

    }
    return m;
#else
    View::DoCreateMenu(menu);

    if (!GEONOOVER) {
      Menu *m;
      menu->InsertItemsAfter(cLASTEDIT, 
		    "delete",       cDELETE,
		    "-",
		    "dup",          cDUP,
		    "edit shape",   cSPLIT,
		    "connect",      cCONNECT,
		    0);

      m= new Menu("arrange");
      m->AppendItems(
		    "bring to front",   cTOFRONT,
		    "send to back",     cTOBACK,
#ifndef ARMY
		    "-",
		    "group",            cGROUP,
		    "ungroup",          cUNGROUP,
#endif
		    0);
      menu->AppendMenu(m, cARRANGEMENU);

#ifndef ARMY
      m= new Menu("options");
      m->AppendItems(
		    "grid off",     cGRID,
		    "hide grid",    cSHWGRID,
		    0);
      menu->AppendMenu(m, cOPTIONMENU);
    
      m= new Menu("polygons");
      m->AppendItems(
		    "Polygon",      cFIRSTSPLINE,
		    "Bezier",       cFIRSTSPLINE + 1,
		    "Spline 2",     cFIRSTSPLINE + 2,
		    "Spline 3",     cFIRSTSPLINE + 3,
		    0);
      menu->AppendMenu(m, cPOLYGONMENU);
#endif
    }
    menu->AppendItems(
		    "-",
		    "Add Next", cADDNEXT,
		    0);

    DoDynMenu(0);

    menu->SetFlag(eMenuNoScroll);
#endif
}    


void DrawView::BuildDynMenu()
{
#ifdef ET25
#else
    if (!menu_build) {
      menu_build= TRUE;

      // Force CreateMenu cause this takes a while and we don't
      // want the user to wait when he presses the right (menu) button
      // the first time.

      // This also allows us to change the behaviour of DrawView before
      // the right menu button is pressed.

      Menu *m= GetMenu();
#ifdef ET25
#else
      DoCreateMenu(m);
      m->ResetFlag(eMenuIsNew);
#endif

      extern char *GEOAP;
      if (GEOAP && ap_count++ == 0) {
	char *p= rindex(GEOAP, ',');
	if (p) {
	    int n_sec= atoi(p+1);
	    if (n_sec > 0)
	      gSystem->AddTimeoutHandler(ap_handler= new AsyncPortalHandler(
		this, n_sec));
	}
      }
    }
#endif
}


void DrawView::DoDynMenu(int p)
{
    if (GEOBROWSE)
      return;

    QuelInfo		*qi= GetInfo();
    Iter		next= qi->GetMenuIter();
    typedef void	(*pmf)(DrawView *, Menu *, int);
    pmf			mp;
    ObjInt		*oi;

    while (oi= (ObjInt*) next()) {
      mp= (pmf) int(*oi);
      (*mp)(this, GetMenu(), p);
    }
}

void DrawView::DoSetupMenu(Menu *menu)
{
    if (!GEONOOVER) {
      if (activeTextView) {
	if (!activeTextView->Caret())
	    menu->EnableItem(cDELETE);
      } else {
	int n= selection->Size();
    
	if (n > 0) {
	    Shape *s;
    
	    menu->EnableItems(cDELETE, cDUP, cTOFRONT, cTOBACK, cARRANGEMENU,
			    cPOLYGONMENU, cFIRSTSPLINE,
			    cFIRSTSPLINE+1, cFIRSTSPLINE+2, cFIRSTSPLINE+3, 0);
	    if (s= OneSelected()) {
		if (s->IsKindOf(Group))
		    menu->EnableItem(cUNGROUP);
		if (s->CanSplit())
		    menu->EnableItem(cSPLIT);
	    } else if (n >= 2)
		menu->EnableItems(cCONNECT, cGROUP, 0);
	}
      }
      menu->ReplaceItem(cGRID, (grid == cGridSize) ? "grid off" : "grid on");
      menu->ReplaceItem(cSHWGRID, showGrid ? "hide grid" : "show grid");

      menu->EnableItems(cOPTIONMENU, cGRID, cSHWGRID, cBLINK, 0);
    }
    if (cur_next)
	menu->EnableItem(cADDNEXT);
    View::DoSetupMenu(menu);
    if (IsGrabbing())
      menu->DisableAll();

    DoDynMenu(-1);
}


Command *DrawView::DoMenuCommand(int cmd)
{
    DoDynMenu(cmd);

    Shape *p= OneSelected();

    switch(cmd) {
    case cARRANGEMENU:
    case cOPTIONMENU:
    case cPOLYGONMENU:
	break;
    case cTOFRONT:
	return new FrontBackCommand(this, cmd, "bring to front");

    case cTOBACK:
	return new FrontBackCommand(this, cmd, "bring to back");

    case cDELETE:
	if (activeTextView)
	    return new CutCopyCommand(activeTextView, cCUT, "delete text");
	return new SCutCopyCommand(this, cmd, "delete");

#ifdef ET25
#else
    case cCUT:
    case cCOPY:
#ifdef ET23
	gClipBoard->SetType(cDocTypeET.AsString() /* aw */);
#else
	gClipBoard->SetType((char*) cDocTypeET);
#endif
	View::DoMenuCommand(cmd);
	if (activeTextView)
	    return activeTextView->DoMenuCommand(cmd);
	return new SCutCopyCommand(this, cmd);
#endif

    case cDUP:
	return new DupCommand(this);
	
    case cGROUP:
	return new GroupCommand(this);
	
    case cUNGROUP:
	return new UngroupCommand(this, (Group*)p);
	
    case cSPLIT:
	if (p)
	    p->SetSplit(TRUE);
	break;
	
    case cCONNECT:
	return new ConnectCommand(this);
	
    case cGRID:
	grid= (grid == 1) ? cGridSize : 1;
	break;
	
    case cSHWGRID:
	showGrid= ! showGrid;
	ForceRedraw();
	break;

    case cBLINK:
	blink_count= 10;
	blink_col= 3;
	break;

    case cADDNEXT:
	if (cur_next)
	  cur_next->DoNext();
	break;

    default:
#if 0
	if (cmd >= cFIRSTSPLINE && cmd <= cLASTSPLINE)
	    return new PropertyCommand(this, eShapeSmooth, cmd-cFIRSTSPLINE, "set smooth");
#endif

	return View::DoMenuCommand(cmd);
    }
    return gNoChanges;
}

//---- clipboard ----------------------------------------------------------------

#ifdef ET25
#else
void DrawView::SelectionToClipboard(char *type, ostream &os)
{
    if (activeTextView)
	activeTextView->SelectionToClipboard(type, os);
#ifdef ET23
    else if (type == cDocTypeET)
#else
    else if (strcmp(type, cDocTypeET) == 0)
#endif
	os << selection SP;
}

Command *DrawView::PasteData(char *type, istream &is)
{
    if (activeTextView)
	return activeTextView->PasteData(type, is);
    
    
    Shape *ns= 0;

#ifdef ET23
    if (type == cDocTypeET) {
#else
    if (strcmp(type, cDocTypeET) == 0) {
#endif
	Object *op;
	is >> op;
	if (op) {
	    if (op->IsKindOf(ObjList))
		return new SPasteCommand(this, (ObjList*)op, lastClick);
	    if (op->IsKindOf(Picture))
		ns= new PictureShape((Picture*)op);
	}
#ifdef ET23
    } else if (type == cDocTypeSunBitmap) {
#else
    } else if (strcmp(type, cDocTypeBitmap) == 0) {
#endif
	Bitmap *bm= 0;
	is >> bm;
	if (bm)
	    ns= new ImageShape(bm);
    }
    if (ns)
	return new SPasteCommand(this, ns, lastClick);
    return gNoChanges;
}

#ifdef ET23
bool DrawView::CanPaste(Symbol type)
#else
bool DrawView::CanPaste(char *type)
#endif
{
    if (activeTextView)
	return activeTextView->CanPaste(type);
#ifdef ET23
    return type == cDocTypeET || type == cDocTypeETBitmap;
#else
    return strismember(type, cDocTypeET, cDocTypeBitmap, 0);
#endif
}
#endif

void DrawView::NewContent()
{
  // shapes->ForEach(Shape,AreaChanged)();
  MapView::NewContent();
}
