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

//$TextShapeSketcher,TextShapeStretcher,TextShape$
#include "Class.h"
#ifdef ET25
#include "Math.h"
#endif
#include "TextShape.h"
#include "BoxShape.h"
#include "DrawView.h"
#include "StyledText.h"
#include "VObjectText.h"
#include "VObjectTView.h"
#include "ObjInt.h"
#include "Alert.h"

const int cMinParagraph = 30;       // minimum size of a p

//---- TextShape ---------------------------------------------------------------

static short TextImage[]= {
#   include  "images/TextShape.im"
};

MetaImpl(TextShape, (TP(to), TP(attachedTo), TB(captured),
0));

Font * TextShape::defaultFont;

TextShape::TextShape()
{
    attachedTo= 0;
    to= 0;
    if (defaultFont == 0)
	defaultFont= new_Font(eFontHelvetica, 14);
}

TextShape::~TextShape()
{
    if (attachedTo)
	attachedTo->RemoveObserver(this);
    SafeDelete(to);
}

short *TextShape::GetImage()
{
    return TextImage;
}

bool TextShape::IsGarbage()
{   
    if (((DrawView*)GetView())->GetActiveText() == this)
	return FALSE;
    return Shape::IsGarbage() || to->Empty();
}

GrCursor TextShape::SketchCursor()
{
    return eCrsIBeam;
}

void TextShape::Init(Point p1, Point p2)
{
    Shape::Init(p1, p2);
    ink= 0;
    captured= bbox.Width() <= cMinParagraph;
    isEnabled= (bool) !captured;
    int width= captured ? cFit : bbox.extent.x;
    to= new VObjectTextView(0, Rectangle(bbox.origin, Point(width,cFit)),
	    new VObjectText(16, new_Font(eFontHelvetica, 14)),
#ifdef ET25
	    TRUE, eTextViewDefault, gPoint0);
#else
	    eLeft, eOne, TRUE, eTextViewDefault, gPoint0);
#endif
    bbox.extent.y= to->Height();
    to->AddObserver(this);      // register myself as dependent of the TextView
}

void TextShape::Draw(Rectangle r)
{
  if (to) {
    GrFillRect(bbox);
    GrSetTextInk(InkPalette[penink]);
    if (((DrawView*)GetView())->GetActiveText() != this && to->AnySelection())
#ifdef ET25
	to->ClearSelection();
#else
	to->SetNoSelection();
#endif
    to->Draw(r);
  }
}

void TextShape::Outline(Point p1, Point p2)
{
    int bh;
    Shape::Outline(p1, p2);
    Rectangle r= NormRect(p1, p2);
    GrLine(r.NW(), r.SW());
    GrLine(r.NE(), r.SE());
    if (to)
	bh= to->BaseHeight(1);
    else
	bh= defaultFont->Ascender(); 
    GrLine(r.NW()+Point(1,bh), r.NE()+Point(-1,bh));
}

void TextShape::SetFont(RunArray *newfont)
{
#ifdef ET25
#else
    StyledText *st= (StyledText*) to->GetText();
    st->ReplaceStyles(newfont, 0, st->Size());
    to->Reformat();
    Changed();
#endif
}

#ifdef ET25
#else
void TextShape::ApplyFont(StChangeStyle st, StyleSpec sp)
{
    StyledText *text= (StyledText*) to->GetText();
    text->SetStyle(st, 0, text->Size(), sp);    
    to->Reformat();
    Changed();
}
#endif

int TextShape::GetSimpleProperty(ShapeProperties what)
{
    switch (what) {
#ifdef ET25
#else
    case eShapeAdjust:
	return int(to->GetJust());
    case eShapeSpacing:
	return int(to->GetSpacing());
#endif
    default:
	return Shape::GetSimpleProperty(what);
    }
}

Object *TextShape::GetProperty(ShapeProperties what)
{
    StyledText *st= (StyledText*) to->GetText();
    RunArray *ra;
    
    switch (what) {
#ifdef ET25
#else
    case eShapeTextFont:
    case eShapeTextSize:
    case eShapeTextFace:
    case eShapePenPattern:
	ra= new RunArray;
	st->CopyStyles(ra, 0, st->Size());
	return ra;
#endif
    default:
	return Shape::GetProperty(what);
    }
}

void TextShape::SetProperty(ShapeProperties what, Object *op)
{
    Ink *ip;
    int p;
    switch (what) {
#ifdef ET25
#else
    case eShapeTextFont:
	p= Guard(op, ObjInt)->GetValue();
	ApplyFont(eStFont, StyleSpec((GrFont) p, eFacePlain, 0, ePatNone));
	break;
    case eShapeTextSize:
	p= Guard(op, ObjInt)->GetValue();
	ApplyFont(eStSize, StyleSpec(eFontTimes, eFacePlain, p, ePatNone));
	break;
    case eShapeTextFace:
	p= Guard(op, ObjInt)->GetValue();
	ApplyFont(eStFace, StyleSpec(eFontTimes, (GrFace)p, eFacePlain, ePatNone));
	break;
    case eShapePenPattern:
	ip= Guard(op->Clone(), Ink);
	ApplyFont(eStInk, StyleSpec(eFontTimes, eFacePlain, 0, ip));
	break;
#endif
    default:
	Shape::SetProperty(what, op);
	return;
    }
    Changed();
}

void TextShape::SetSimpleProperty(ShapeProperties what, int p)
{
    switch (what) {
#ifdef ET25
#else
    case eShapeAdjust:
	to->SetJust(eTextJust(p));
	break;
    case eShapeSpacing:
	to->SetSpacing(eSpacing(p));
	break;
#endif
    default:
	Shape::SetSimpleProperty(what, p);
	return;
    }
    Changed();
}

void TextShape::ResetProperty(ShapeProperties what, Object *op)
{
    switch (what) {
    case eShapeTextFont:
    case eShapeTextSize:
    case eShapePenPattern:
    case eShapeTextFace:
	SetFont((RunArray*)op);
	break;
    default:
	Shape::ResetProperty(what, op);
    }
}


Rectangle TextShape::InvalRect()
{
    return Shape::InvalRect().Expand(2);
}


void TextShape::AreaChanged()
{
    if (attachedTo) {
      attachedTo->AreaChanged();
      Rectangle r= attachedTo->GetTextRect();
      SetSpan(Rectangle(r.NW(), r.SE()));
    } else {
      Point oldext= bbox.extent;
      Shape::AreaChanged();
      bbox.extent= oldext;
      SetGeoPos();
      to->SetOrigin(bbox.origin);
    }
}

void TextShape::Moveby(Point delta)
{
    if (attachedTo) {
      ShowAlert(eAlertNote, "This text is linked to a Shape\nand therefore cannot be moved.");
      return;
    }
    Shape::Moveby(delta);
    to->SetOrigin(bbox.origin);
}

void TextShape::InvalDiff(Rectangle r2)
{
    if (GetContainer()) {
	Rectangle r[4];
	int n= Difference(r, r2, bbox);
	
	for (int i= 0; i < n; i++)
	    GetView()->InvalidateRect(r[i]);
    }
}

void TextShape::Highlight(HighlightState h)
{
    if (GetView()) {
	// if there is no active Text highlight the object as a shape
	if (((DrawView*)GetView())->GetActiveText() != this)
	    Shape::Highlight(h);
	else if (to && !to->InTextSelector()) // ???
	    to->ForceRedraw();
    }
}

void TextShape::SetSpan(Rectangle r)
{
   // Shape::SetSpan(r); // Needed to adjust geopos
   if (! captured) {
	bbox= NormRect(r.origin, r.extent);
	SetGeoPos();
	to->SetExtent(Point(bbox.extent.x, cFit));
	to->SetOrigin(bbox.origin);
	Changed();
    }
}

void TextShape::MakeDependentOn(Shape *s)
{
    s->AddObserver(this);
    attachedTo= s;    
}

void TextShape::DoObserve(int, int part, void*, Object *op)
{
    if (to == 0)
	return;
    if (op == to) { // text obj changed 
	switch (part) {
	case cPartExtentChanged:
	    if (ink != 0)
		InvalDiff(Rectangle(bbox.origin, to->GetExtent()));
	    bbox.extent= to->GetExtent();
	    SetGeoPos();
	    break;
	    
	case cPartOriginChanged:
	    if (ink != 0)
		InvalDiff(Rectangle(bbox.origin, to->GetOrigin()));
	    bbox.origin= to->GetOrigin();
	    SetGeoPos();
	    break;

	case cPartSenderDied:
	    to= 0;
	    break;
	}
    } else if (op == attachedTo) {   
	if (part == cPartSenderDied)
	    attachedTo= 0;
	else {
	    Rectangle r= Guard(op,Shape)->GetTextRect();
	    SetSpan(Rectangle(r.NW(), r.SE()));
	}
    }
}

void TextShape::SetContainer(VObject *vop)
{
    Shape::SetContainer(vop);
    if (to)
	to->SetContainer(vop);
}

#ifdef ET25
OStream& TextShape::PrintOn(OStream& s)
#else
ostream& TextShape::PrintOn(ostream& s)
#endif
{
    Shape::PrintOn(s);
    s << attachedTo SP << to SP << captured SP;
    return s;
}

#ifdef ET25
IStream& TextShape::ReadFrom(IStream& s)
#else
istream& TextShape::ReadFrom(istream& s)
#endif
{
    Shape::ReadFrom(s);
    s >> attachedTo >> to >> Bool(captured);
    to->SetOrigin(bbox.origin);
#ifdef ET25
    to->ClearSelection(FALSE);
#else
    to->SetNoSelection(FALSE);
#endif
    return s;
}

ShapeSketcher *TextShape::NewSketcher(class DrawView *dv, SketchModes m)
{
    return new TextShapeSketcher(dv, this, m); 
}

ShapeStretcher *TextShape::NewStretcher(class DrawView *dv, int handle)
{
    if (captured)
	return (ShapeStretcher*) gNoChanges;
    return new TextShapeStretcher(dv, this, handle);
}

Font * TextShapeDefaultFont()
{
    return TextShape::defaultFont;
}

//---- TextShapeSketcher -------------------------------------------------------

TextShapeSketcher::TextShapeSketcher(DrawView *dv, Shape *pro, SketchModes m)
								  : (dv, pro, m) 
{
    Font *f= TextShapeDefaultFont();
    lineHeight= f->Spacing();
    baseHeight= f->Ascender();
}

Command *TextShapeSketcher::TrackMouse(TrackPhase tp, Point ap, Point pp, Point np)
{
    ShapeSketcher::TrackMouse(tp, ap, pp, np);
    if (tp == eTrackRelease) {
	newshape= (Shape*)proto->Clone();
	newshape->SetContainer(view);
	newshape->Init(ap, np);
	view->Insert(newshape);
	view->SetActiveText((TextShape*)newshape);
	return gNoChanges;
    }
    return this;
}

void TextShapeSketcher::TrackConstrain(Point ap, Point pp, Point *np)
{
    if (ap == pp) {     // constrain left/top corner
	ShapeSketcher::TrackConstrain(ap, pp, np);
	np->y-= baseHeight;
    } else              // constrain right/bottom corner
	np->y= ap.y + lineHeight;
}

//---- TextShapeStretcher ------------------------------------------------------

TextShapeStretcher::TextShapeStretcher(DrawView *dv, Shape *p, int h) : (dv, p, h) 
{
}

void TextShapeStretcher::TrackConstrain(Point, Point, Point *np)
{
#ifdef ET25
    np->x= Math::Max(sp->bbox.origin.x + cMinParagraph, np->x);
#else
    np->x= max(sp->bbox.origin.x + cMinParagraph, np->x);
#endif
}
