/* * Copyright (c) 1987, 1988, 1989 Stanford University * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided * that the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Stanford not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. Stanford makes no representations about * the suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * STANFORD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * $Header: /home/faculty1/gfisher/projects-library/CVS/interviews/implementation/idraw/selection.c,v 1.1.1.1 1999/01/24 23:55:45 gfisher Exp $ * implements classes Selection and NPtSelection. */ #include "ipaint.h" #include "istring.h" #include "listifont.h" #include "mapipaint.h" #include "selection.h" #include "state.h" #include #include #include #include /* * Static member allocations. */ char Selection::buf[BUFSIZE]; int Selection::versionnumber; /* * Define the start of data token. */ const char* startdata = "%I"; /* * Selection starts off with no handles. */ Selection::Selection (Graphic* gs) : Picture(gs) { handles = nil; } /* * Free storage allocated for the handles if any. */ Selection::~Selection () { DeleteHandles(); } /* * Copy returns a copy of the Selection. */ Graphic* Selection::Copy () { return new Selection(this); } /* * HasChildren returns false so Idraw won't ungroup this Picture. */ bool Selection::HasChildren () { return false; } /* * GetPaddedBox returns the Selection's smallest box with enough * padding added to include its handles. */ void Selection::GetPaddedBox (BoxObj& box) { Picture::GetBox(box); const int HDPAD = HDSIZE/2 + 1; /* how much to add to GetBox's size */ box.left -= HDPAD; box.bottom -= HDPAD; box.right += HDPAD; box.top += HDPAD; } /* * DrawHandles tells the handles to draw themselves unless they've * already drawn themselves. */ void Selection::DrawHandles (Painter* rasterxor, Canvas* canvas) { if (handles == nil) { CreateHandles(); } handles->SetPainter(rasterxor); handles->SetCanvas(canvas); handles->Draw(); } /* * EraseHandles tells the handles to erase themselves unless they've * already erased themselves. */ void Selection::EraseHandles (Painter* rasterxor, Canvas* canvas) { if (handles != nil) { handles->SetPainter(rasterxor); handles->SetCanvas(canvas); handles->Erase(); } } /* * RedrawHandles knows for sure that no unerased handles remain on the * screen, so it resets the handles to outline the Selection's * possibly different shape and location before drawing the handles. */ void Selection::RedrawHandles (Painter* rasterxor, Canvas* canvas) { DeleteHandles(); DrawHandles(rasterxor, canvas); } /* * RedrawUnclippedHandles knows that some unerased handles probably * remain on the screen so it can't reset the handles, but it can tell * the handles to draw themselves whether or not they've already drawn * themselves because the painter will clip the already drawn handles. */ void Selection::RedrawUnclippedHandles (Painter* rasterxor, Canvas* canvas) { if (handles == nil) { CreateHandles(); } handles->SetPainter(rasterxor); handles->SetCanvas(canvas); handles->Redraw(); } /* * ResetHandles deletes the handles since the Selection may have moved * out from under them. Redrawing the handles will recreate them. */ void Selection::ResetHandles () { DeleteHandles(); } /* * ShapedBy returns false since the Selection does not contain any * points which both determine its shape and fall within the given * distance of the given point. */ bool Selection::ShapedBy (Coord, Coord, float) { return false; } /* * CreateShape creates and returns a Rubberband representing the * Selection's shape for the user to reshape. */ Rubberband* Selection::CreateShape (Coord, Coord) { return nil; } /* * GetReshapedCopy creates and returns a copy of the Selection * incorporating the change made to its shape. */ Selection* Selection::GetReshapedCopy () { return nil; } /* * Skip skips over tokens in the input stream until it reads a start * of data token or reaches eof. */ void Selection::Skip (istream& from) { while (from >> buf && strcmp(buf, startdata) != 0) { /* skip Postscript code */ } } /* * ReadVersion reads the drawing's version number. Knowing the * drawing's version number allows us to invoke backward compatibility * code if necessary or detect an incompatibility ahead of time. */ void Selection::ReadVersion (istream& from) { Skip(from); from >> buf; if (strcmp(buf, "Idraw") == 0) { from >> versionnumber; } else { versionnumber = ORIGINALVERSION; } if (versionnumber > TEXTOFFSETVERSION) { fprintf(stderr, "warning: drawing version %d ", versionnumber); fprintf(stderr, "newer than idraw version %d\n", TEXTOFFSETVERSION); } } /* * ReadGridSpacing reads the grid spacing used by the drawing and * stores the new grid spacing value. It must correct the default * grid spacing it gives to old drawings for an implementation botch * in InterViews 2.4 that calculated point's value incorrectly using * 72.07/inch instead of inch/72.27 (it was a botch in TWO ways). */ void Selection::ReadGridSpacing (istream& from, State* state) { double g = state->GetGridSpacing(); if (versionnumber < GRIDSPACINGVERSION) { const int oldspacing = 8; const double oldpoints = 72.07/inches; g = oldpoints * round(oldspacing * oldpoints); } else { from >> buf; if (strcmp(buf, "Grid") == 0) { from >> g; } } state->SetGridSpacing(g); } /* * ReadGS reads data to initialize the graphic state for Selections * which don't contain any text. */ void Selection::ReadGS (istream& from, State* state) { ReadBrush(from, state); if (versionnumber >= FGANDBGCOLORVERSION) { ReadFgColor(from, state); ReadBgColor(from, state); SetFont(nil); } else if (versionnumber >= FGCOLORVERSION) { ReadFgColor(from, state); IColor* bg = state->GetMapIBgColor()->GetInitial(); SetColors(GetFgColor(), bg); SetFont(nil); } else { IColor* fg = state->GetMapIFgColor()->GetInitial(); IColor* bg = state->GetMapIBgColor()->GetInitial(); SetColors(fg, bg); ReadFont(from, state); } ReadPattern(from, state); ReadTransformer(from); } /* * ReadPictGS reads data to initialize the graphic state for * PictSelections which may contain some text. */ void Selection::ReadPictGS (istream& from, State* state) { ReadBrush(from, state); if (versionnumber >= FGANDBGCOLORVERSION) { ReadFgColor(from, state); ReadBgColor(from, state); } else if (versionnumber >= FGCOLORVERSION) { ReadFgColor(from, state); SetColors(GetFgColor(), nil); } else { SetColors(nil, nil); } ReadFont(from, state); ReadPattern(from, state); ReadTransformer(from); } /* * ReadTextGS reads data to initialize the graphic state for * TextSelections which don't need a brush or pattern. */ void Selection::ReadTextGS (istream& from, State* state) { if (versionnumber >= FGCOLORVERSION) { SetBrush(nil); ReadFgColor(from, state); SetColors(GetFgColor(), nil); } else { ReadBrush(from, state); SetColors(state->GetMapIFgColor()->GetInitial(), nil); } ReadFont(from, state); if (versionnumber < NONREDUNDANTVERSION) { ReadPattern(from, state); IPattern* pattern = (IPattern*) GetPattern(); float graylevel = pattern->GetGrayLevel(); const char* c = "Black"; int r = 0, g = 0, b = 0; if (graylevel != 0 && graylevel != -1) { if (graylevel == 1) { c = "White"; r = g = b = 65535; } else { c = "Gray"; r = g = b = 49152; } } SetColors(state->GetMapIFgColor()->FindOrAppend(c, r, g, b), nil); } else { SetPattern(nil); } ReadTransformer(from); if (versionnumber < TEXTOFFSETVERSION) { IFont* f = (IFont*) GetFont(); float x0, y0, x1, y1; transform(0.0, 0.0, x0, y0); transform(0.0, float(f->GetLineHt() - f->Height() - 1), x1, y1); float dx = x1 - x0; float dy = y1 - y0; Translate(dx, dy); } } /* * ReadBrush reads data to set the Selection's brush. */ void Selection::ReadBrush (istream& from, State* state) { Skip(from); from >> buf; if (buf[0] == 'b') { char lookahead = 'u'; bool undefined = false; bool none = false; int p = 0; int w = 0; int l = false; int r = false; from >> lookahead; from.putback(lookahead); switch (lookahead) { case 'u': undefined = true; break; case 'n': none = true; break; default: from >> p >> w >> l >> r; break; } if (undefined || !from.good()) { SetBrush(nil); } else { MapIBrush* mb = state->GetMapIBrush(); IBrush* brush = mb->FindOrAppend(none, p, w, l, r); SetBrush(brush); } } } /* * ReadFgColor reads data to set the Selection's foreground color. */ void Selection::ReadFgColor (istream& from, State* state) { Skip(from); from >> buf; if (buf[0] == 'c' && (buf[1] == 'f' || versionnumber < FGANDBGCOLORVERSION) ) { char lookahead = 'u'; bool undefined = false; char name[100]; float fr = 0, fg = 0, fb = 0; from >> lookahead; from.putback(lookahead); if (lookahead == 'u') { undefined = true; } else { from >> name; if (versionnumber >= FGANDBGCOLORVERSION) { from >> fr >> fg >> fb; } } if (undefined || !from.good()) { SetColors(nil, GetBgColor()); } else { int r = round(fr * 0xffff); int g = round(fg * 0xffff); int b = round(fb * 0xffff); MapIColor* mfg = state->GetMapIFgColor(); IColor* fgcolor = mfg->FindOrAppend(name, r, g, b); SetColors(fgcolor, GetBgColor()); } } } /* * ReadBgColor reads data to set the Selection's background color. */ void Selection::ReadBgColor (istream& from, State* state) { Skip(from); from >> buf; if (buf[0] == 'c' && buf[1] == 'b') { char lookahead = 'u'; bool undefined = false; char name[100]; float fr = 0, fg = 0, fb = 0; from >> lookahead; from.putback(lookahead); if (lookahead == 'u') { undefined = true; } else { from >> name >> fr >> fg >> fb; } if (undefined || !from.good()) { SetColors(GetFgColor(), nil); } else { int r = round(fr * 0xffff); int g = round(fg * 0xffff); int b = round(fb * 0xffff); MapIColor* mbg = state->GetMapIBgColor(); IColor* bgcolor = mbg->FindOrAppend(name, r, g, b); SetColors(GetFgColor(), bgcolor); } } } /* * ReadFont reads data to set the Selection's font. */ void Selection::ReadFont (istream& from, State* state) { Skip(from); from >> buf; if (buf[0] == 'f') { char lookahead = 'u'; bool undefined = false; char name[100]; char printfont[100]; char printsize[100]; from >> lookahead; from.putback(lookahead); if (lookahead == 'u') { undefined = true; } else { from >> name; from >> printfont; from >> printsize; } if (undefined || !from.good()) { SetFont(nil); } else { MapIFont* mf = state->GetMapIFont(); char* pf = (versionnumber >= NONREDUNDANTVERSION) ? &printfont[1] : printfont; IFont* font = mf->FindOrAppend(name, pf, printsize); SetFont(font); } } } /* * ReadPattern reads data to set the Selection's pattern. */ void Selection::ReadPattern (istream& from, State* state) { Skip(from); from >> buf; if (buf[0] == 'p') { char lookahead = 'u'; bool undefined = false; bool none = false; float graylevel = 0; int data[patternHeight]; int size = 0; from >> lookahead; switch (lookahead) { case 'u': undefined = true; break; case 'n': none = true; break; case '<': graylevel = -1; break; default: from.putback(lookahead); break; } if (!undefined && !none && graylevel != -1) { if (versionnumber >= FGANDBGCOLORVERSION) { from >> graylevel; } else { from >> data[0]; graylevel = CalcGrayLevel(data[0]); } } else if (graylevel == -1) { int i; for (i = 0; from >> buf && i < patternHeight; i++) { if (buf[0] == '>' || sscanf(buf, "%x", &data[i]) != 1) { break; } } if (buf[0] == '>') { size = i; } else { undefined = true; } } if (undefined || !from.good()) { SetPattern(nil); } else { MapIPattern* mp = state->GetMapIPattern(); IPattern* pattern = mp->FindOrAppend(none, graylevel, data, size); SetPattern(pattern); } } } /* * ReadTransformer reads data to set the Selection's transformation * matrix. */ void Selection::ReadTransformer (istream& from) { Skip(from); from >> buf; if (buf[0] == 't') { char uorbracket = 'u'; bool undefined = false; float a00, a01, a10, a11, a20, a21; from >> uorbracket; if (uorbracket == 'u') { undefined = true; } else { if (versionnumber < NONREDUNDANTVERSION) { from.putback(uorbracket); } from >> a00 >> a01 >> a10 >> a11 >> a20 >> a21; } if (from.good() && !undefined) { SetTransformer(new Transformer(a00, a01, a10, a11, a20, a21)); } } } /* * CalcGrayLevel calculates a 4x4 bitmap's gray level on the printer. * Since the gray level ranges from 0 = solid to 1 = clear, * CalcGrayLevel counts the number of 0 bits in the bitmap and divides * the sum by the total number of bits in the bitmap. */ float Selection::CalcGrayLevel (int seed) { const int numbits = 16; int numzeros = 0; for (int i = 0; i < numbits; i++) { numzeros += !((seed >> i) & 0x1); } return float(numzeros) / numbits; } /* * WriteData writes everything needed to draw or reconstruct the * Selection. */ void Selection::WriteData (ostream& to) { /* define it in your subclass */ } /* * WriteVersion writes the drawing's version number. Storing a * version number with the drawing makes backward compatibility easier * to support when the drawing format changes in the future. */ void Selection::WriteVersion (ostream& to) { to << startdata << " Idraw " << TEXTOFFSETVERSION << " "; } /* * WriteGridSpacing writes the drawing's grid spacing. Storing the * grid spacing with the drawing ensures graphics will remain aligned * to the grid they were aligned to before. */ void Selection::WriteGridSpacing (ostream& to, State* state) { to << "Grid " << state->GetGridSpacing() << " "; } /* * WriteGS writes the graphic state for Selections that don't contain * any text. */ void Selection::WriteGS (ostream& to) { WriteBrush(to); WriteFgColor(to); WriteBgColor(to); WritePattern(to); WriteTransformer(to); } /* * WritePictGS writes the graphic state for PictSelections which may * contain some text. */ void Selection::WritePictGS (ostream& to) { WriteBrush(to); WriteFgColor(to); WriteBgColor(to); WriteFont(to); WritePattern(to); WriteTransformer(to); } /* * WriteTextGS writes the graphic state for TextSelections which * don't need a brush or pattern but do need a font. */ void Selection::WriteTextGS (ostream& to) { WriteFgColor(to); WriteFont(to); WriteTransformer(to); } /* * WriteBrush writes the Selection's brush. */ void Selection::WriteBrush (ostream& to) { IBrush* brush = (IBrush*) GetBrush(); if (brush == nil) { to << startdata << " b u\n"; } else if (brush->None()) { to << "none SetB " << startdata << " b n\n"; } else { int p = brush->GetLinePattern(); to << startdata << " b " << p << "\n"; int w = brush->Width(); bool l = brush->LeftArrow(); bool r = brush->RightArrow(); to << w << " " << l << " " << r << " "; const int* dashpat = brush->GetDashPattern(); int dashpatsize = brush->GetDashPatternSize(); int dashoffset = brush->GetDashOffset(); if (dashpatsize <= 0) { to << "[] " << dashoffset << " "; } else { int i; to << "["; for (i = 0; i < dashpatsize - 1; i++) { to << dashpat[i] << " "; } to << dashpat[i] << "] " << dashoffset << " "; } to << "SetB\n"; } } /* * WriteFgColor writes the Selection's foreground color. */ void Selection::WriteFgColor (ostream& to) { IColor* fgcolor = (IColor*) GetFgColor(); if (fgcolor == nil) { to << startdata << " cfg u\n"; } else { const char* name = fgcolor->GetName(); to << startdata << " cfg " << name << "\n"; if (strcmp(name, "white") == 0 || strcmp(name, "White") == 0) { to << "1 1 1 SetCFg\n"; } else { int r, g, b; fgcolor->Intensities(r, g, b); float fr = float(r) / 0xffff; float fg = float(g) / 0xffff; float fb = float(b) / 0xffff; to << fr << " " << fg << " " << fb << " SetCFg\n"; } } } /* * WriteBgColor writes the Selection's background color. */ void Selection::WriteBgColor (ostream& to) { IColor* bgcolor = (IColor*) GetBgColor(); if (bgcolor == nil) { to << startdata << " cbg u\n"; } else { const char* name = bgcolor->GetName(); to << startdata << " cbg " << name << "\n"; if (strcmp(name, "white") == 0 || strcmp(name, "White") == 0) { to << "1 1 1 SetCBg\n"; } else { int r, g, b; bgcolor->Intensities(r, g, b); float fr = float(r) / 0xffff; float fg = float(g) / 0xffff; float fb = float(b) / 0xffff; to << fr << " " << fg << " " << fb << " SetCBg\n"; } } } /* * WriteFont writes the Selection's font. */ void Selection::WriteFont (ostream& to) { IFont* font = (IFont*) GetFont(); if (font == nil) { to << startdata << " f u\n"; } else { const char* name = font->GetName(); const char* pf = font->GetPrintFont(); const char* ps = font->GetPrintSize(); to << startdata << " f " << name << "\n"; to << "/" << pf << " " << ps << " SetF\n"; } } /* * WritePattern writes the Selection's pattern. */ void Selection::WritePattern (ostream& to) { IPattern* pattern = (IPattern*) GetPattern(); if (pattern == nil) { to << startdata << " p u\n"; } else if (pattern->None()) { to << "none SetP " << startdata << " p n\n"; } else if (pattern->GetSize() > 0) { const int* data = pattern->GetData(); int size = pattern->GetSize(); to << startdata << " p\n"; to << "< "; if (size <= 8) { for (int i = 0; i < 8; i++) { sprintf(buf, "%02x", data[i] & 0xff); to << buf << " "; } } else { for (int i = 0; i < patternHeight; i++) { sprintf(buf, "%0*x", patternWidth/4, data[i]); if (i != patternHeight - 2) { to << buf << " "; } else { to << buf << "\n "; } } } to << "> -1 SetP\n"; } else { float graylevel = pattern->GetGrayLevel(); to << startdata << " p\n"; to << graylevel << " SetP\n"; } } /* * WriteTransformer writes the Selection's transformation matrix. */ void Selection::WriteTransformer (ostream& to) { Transformer* t = GetTransformer(); if (t == nil || *t == *identity) { to << startdata << " t u\n"; } else { float mat[6]; t->GetEntries(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); to << startdata << " t\n[ "; for (int i = 0; i < 6; i++) { to << (fabs(mat[i]) < 0.0001 ? 0.0 : mat[i]) << " "; } to << "] concat\n"; } } /* * CreateHandles creates handles outlining the Selection's shape. */ void Selection::CreateHandles () { const int N = 8; const int RUBPT = 0; Coord l, b, r, t, hx, hy, x[N], y[N]; Picture::GetBox(l, b, r, t); hx = (r + l)/2; hy = (t + b)/2; x[0] = l; y[0] = b; x[1] = hx; y[1] = b; x[2] = r; y[2] = b; x[3] = r; y[3] = hy; x[4] = r; y[4] = t; x[5] = hx; y[5] = t; x[6] = l; y[6] = t; x[7] = l; y[7] = hy; handles = new RubberHandles(nil, nil, x, y, N, RUBPT, HDSIZE); } /* * DeleteHandles zeroes the handles to record it has none now. */ void Selection::DeleteHandles () { if (handles != nil) { delete handles; handles = nil; } } /* * NPtSelection passes its argument to Selection. */ NPtSelection::NPtSelection (Graphic* gs) : Selection(gs) { myname = "YouForgotToDefineMyName"; rubbervertex = nil; } /* * GetOriginal returns the points that were passed to the * the NPtSelection subclass's constructor. */ int NPtSelection::GetOriginal (const Coord*&, const Coord*&) { return 0; } /* * ShapedBy returns true if any of the NPtSelection's points falls * within the given distance of the given point. */ bool NPtSelection::ShapedBy (Coord px, Coord py, float maxdist) { const Coord* ux; const Coord* uy; int n = GetOriginal(ux, uy); Coord* x = new Coord[n]; Coord* y = new Coord[n]; CopyArray(ux, uy, n, x, y); TotalTransform(x, y, n); int closestpt = ClosestPoint(x, y, n, px, py); bool shapedby = Distance(x[closestpt], y[closestpt], px, py) <= maxdist; delete x; delete y; return shapedby; } /* * CreateShape creates, stores, and returns a rubberband representing * the NPtSelection's shape for the user to reshape. */ Rubberband* NPtSelection::CreateShape (Coord px, Coord py) { const Coord* ux; const Coord* uy; int n = GetOriginal(ux, uy); Coord* x = new Coord[n]; Coord* y = new Coord[n]; CopyArray(ux, uy, n, x, y); TotalTransform(x, y, n); int rubpt = ClosestPoint(x, y, n, px, py); rubbervertex = CreateRubberVertex(x, y, n, rubpt); delete x; delete y; return rubbervertex; } /* * GetReshapedCopy creates and returns a copy of the NPtSelection * incorporating the change made to its shape. */ Selection* NPtSelection::GetReshapedCopy () { Coord* x; Coord* y; int n; int rubpt; rubbervertex->GetCurrent(x, y, n, rubpt); delete rubbervertex; InvTotalTransform(x, y, n); Selection* reshaped = CreateReshapedCopy(x, y, n); delete x; delete y; return reshaped; } /* * ReadPoints reads a set of points as efficiently as possible by * using dynamic static buffers instead of mallocing on every call. */ void NPtSelection::ReadPoints (istream& from, const Coord*& x, const Coord*& y, int& n) { const int INITIALSIZE = 15; static int sizepoints = 0; static Coord* xcoords = nil; static Coord* ycoords = nil; Skip(from); from >> n; if (n > sizepoints) { delete xcoords; delete ycoords; sizepoints = max(n, INITIALSIZE); xcoords = new Coord[sizepoints]; ycoords = new Coord[sizepoints]; } for (int i = 0; i < n; i++) { if (versionnumber < NONREDUNDANTVERSION) { Skip(from); } from >> xcoords[i] >> ycoords[i]; } x = xcoords; y = ycoords; } /* * WriteData writes the NPtSelection's data and Postscript code to * draw it. */ void NPtSelection::WriteData (ostream& to) { const Coord* x; const Coord* y; int n = GetOriginal(x, y); to << "Begin " << startdata << " " << myname << "\n"; WriteGS(to); to << startdata << " " << n << "\n"; for (int i = 0; i < n; i++) { to << x[i] << " " << y[i] << "\n"; } to << n << " " << myname << "\n"; to << "End\n\n"; } /* * CreateRubberVertex creates and returns the right kind of * RubberVertex to represent the NPtSelection's shape. */ RubberVertex* NPtSelection::CreateRubberVertex (Coord*, Coord*, int, int) { /* implement it in your subclass */ return nil; } /* * CreateReshapedCopy creates and returns a reshaped copy of itself * using the passed points and its graphic state. */ Selection* NPtSelection::CreateReshapedCopy (Coord*, Coord*, int) { /* implement it in your subclass */ return nil; } /* * CreateHandles creates handles highlighting the NPtSelection's * points. */ void NPtSelection::CreateHandles () { const int RUBPT = 0; const Coord* ux; const Coord* uy; int n = GetOriginal(ux, uy); Coord* x = new Coord[n]; Coord* y = new Coord[n]; CopyArray(ux, uy, n, x, y); TotalTransform(x, y, n); handles = new RubberHandles(nil, nil, x, y, n, RUBPT, HDSIZE); delete x; delete y; } /* * TotalTransform transforms the given points from the Selection's * coordinate system to the screen's coordinate system. */ void NPtSelection::TotalTransform (Coord* x, Coord* y, int n) { Transformer total; TotalTransformation(total); for (int i = 0; i < n; i++) { total.Transform(x[i], y[i]); } } /* * InvTotalTransform transforms the given points from the screen's * coordinate system to the NPtSelection's coordinate system. */ void NPtSelection::InvTotalTransform (Coord* x, Coord* y, int n) { Transformer total; TotalTransformation(total); for (int i = 0; i < n; i++) { total.InvTransform(x[i], y[i]); } } /* * ClosestPoint returns the index within the arrays of the closest * point to the given coordinates. */ int NPtSelection::ClosestPoint (Coord* x, Coord* y, int n, Coord px, Coord py) { int closestpt = 0; float mindist = Distance(x[0], y[0], px, py); for (int i = 1; i < n; i++) { float dist = Distance(x[i], y[i], px, py); if (dist < mindist) { mindist = dist; closestpt = i; } } return closestpt; } /* * LeftAcont checks whether the left arrowhead contains the point. */ bool NPtSelection::LeftAcont (Coord x0, Coord y0, Coord x1, Coord y1, PointObj& po, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->LeftArrow()) { return ArrowHeadcont(x0, y0, x1, y1, po, gs); } return false; } /* * RightAcont checks whether the right arrowhead contains the point. */ bool NPtSelection::RightAcont (Coord x0, Coord y0, Coord x1, Coord y1, PointObj& po, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->RightArrow()) { return ArrowHeadcont(x0, y0, x1, y1, po, gs); } return false; } /* * LeftAints checks whether the left arrowhead intersects the area. */ bool NPtSelection::LeftAints (Coord x0, Coord y0, Coord x1, Coord y1, BoxObj& userb, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->LeftArrow()) { return ArrowHeadints(x0, y0, x1, y1, userb, gs); } return false; } /* * RightAints checks whether the right arrowhead intersects the area. */ bool NPtSelection::RightAints (Coord x0, Coord y0, Coord x1, Coord y1, BoxObj& userb, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->RightArrow()) { return ArrowHeadints(x0, y0, x1, y1, userb, gs); } return false; } /* * drawLeftA draws the left arrowhead if it should be drawn. */ void NPtSelection::drawLeftA (Coord x0, Coord y0, Coord x1, Coord y1, Canvas* c, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->LeftArrow()) { drawArrowHead(x0, y0, x1, y1, c, gs); } } /* * drawRightA draws the right arrowhead if it should be drawn. */ void NPtSelection::drawRightA (Coord x0, Coord y0, Coord x1, Coord y1, Canvas* c, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->RightArrow()) { drawArrowHead(x0, y0, x1, y1, c, gs); } } /* * Define an arrow head with its tip at the origin and its tail on the * negative x axis so we can rotate the tail about the origin to the * right angle and translate the tip to the right point. */ static const int ARROWN = 3; static Coord arrowx[ARROWN] = {0, -ARROWHEIGHT, -ARROWHEIGHT}; static Coord arrowy[ARROWN] = {0, ARROWWIDTH/2, -ARROWWIDTH/2}; static Coord arrowconvx[ARROWN + 1]; static Coord arrowconvy[ARROWN + 1]; /* * MergeArrowHeadTol returns a tolerance to use around the graphic's * extent that includes the arrowhead's size. */ float NPtSelection::MergeArrowHeadTol (float tol, Graphic* gs) { IBrush* brush = (IBrush*) gs->GetBrush(); if (brush->LeftArrow() || brush->RightArrow()) { float magnif = 1; Transformer* view = getRoot()->GetTransformer(); if (view != nil && !view->Rotated()) { /* rot breaks magnif calc */ float fpx0, fpy0, fpx1, fpy1; view->Transform(0.0, 0.0, fpx0, fpy0); view->Transform(1.0, 1.0, fpx1, fpy1); magnif = fpy1 - fpy0; } float arrowtol = 0.5 * ARROWWIDTH * points * magnif; tol = max(arrowtol, tol); } return tol; } /* * ArrowHeadcont returns true if the arrowhead contains the point. * The arrowhead may be filled or unfilled depending on the pattern. */ bool NPtSelection::ArrowHeadcont (Coord x0, Coord y0, Coord x1, Coord y1, PointObj& po, Graphic* gs) { bool contains = false; IPattern* pattern = (IPattern*) gs->GetPattern(); SetCTM(x0, y0, x1, y1, gs, !pattern->None()); PointObj pt(&po); invTransform(pt.x, pt.y, gs); if (pattern->None()) { MultiLineObj ml(arrowx, arrowy, ARROWN); LineObj l(arrowx[ARROWN-1], arrowy[ARROWN-1], arrowx[0], arrowy[0]); contains = ml.Contains(pt) || l.Contains(pt); } else { FillPolygonObj fp(arrowx, arrowy, ARROWN); contains = fp.Contains(pt); } RestoreCTM(gs); return contains; } /* * ArrowHeadints returns true if the arrowhead intersects the area. * The arrowhead may be filled or unfilled depending on the pattern. */ bool NPtSelection::ArrowHeadints (Coord x0, Coord y0, Coord x1, Coord y1, BoxObj& userb, Graphic* gs) { bool intersects = false; IPattern* pattern = (IPattern*) gs->GetPattern(); SetCTM(x0, y0, x1, y1, gs, !pattern->None()); transformList(arrowx, arrowy, ARROWN, arrowconvx, arrowconvy, gs); if (pattern->None()) { arrowconvx[ARROWN] = arrowconvx[0]; arrowconvy[ARROWN] = arrowconvy[0]; MultiLineObj ml(arrowconvx, arrowconvy, ARROWN + 1); intersects = ml.Intersects(userb); } else { FillPolygonObj fp(arrowconvx, arrowconvy, ARROWN); intersects = fp.Intersects(userb); } RestoreCTM(gs); return intersects; } /* * drawArrowHead draws the arrowhead. */ void NPtSelection::drawArrowHead (Coord x0, Coord y0, Coord x1, Coord y1, Canvas* c, Graphic* gs) { IPattern* pattern = (IPattern*) gs->GetPattern(); if (!pattern->None()) { SetCTM(x0, y0, x1, y1, gs, true); update(gs); pFillPolygon(c, arrowx, arrowy, ARROWN); RestoreCTM(gs); } IBrush* brush = (IBrush*) gs->GetBrush(); if (!brush->None()) { SetCTM(x0, y0, x1, y1, gs, false); update(gs); pPolygon(c, arrowx, arrowy, ARROWN); RestoreCTM(gs); } } /* * SetCTM stores gs's former transformation matrix and overwrites it * with a new one defined to scale the arrowhead to its proper size * and align it with the line. The matrix includes the graphic's * topmost parent's scaling but no other parents' scaling so the * arrowhead will change size when we zoom the view but stay the same * size when we scale the line. New argument patternfill special- * cases filling arrowhead to include the outermost edge of the * outline drawn by the brush so dashed brushes won't expose white * space where there's no pattern fill. */ static Transformer* origCTM; static Transformer* arrowCTM; void NPtSelection::SetCTM (Coord x0, Coord y0, Coord x1, Coord y1, Graphic* gs, bool patternfill) { if (arrowCTM == nil) { arrowCTM = new Transformer; arrowCTM->Reference(); } *arrowCTM = *identity; if (patternfill) { IBrush* brush = (IBrush*) gs->GetBrush(); float bw = brush->Width(); float padtip = sqrt(ARROWHEIGHT*ARROWHEIGHT + 0.25*ARROWWIDTH*ARROWWIDTH) * bw / ARROWWIDTH; float padtail = bw / 2; float patternscale = (ARROWHEIGHT + padtip + padtail) / ARROWHEIGHT; arrowCTM->Scale(patternscale, patternscale); arrowCTM->Translate(padtip, 0.); } arrowCTM->Scale(point, point); Transformer* view = getRoot()->GetTransformer(); if (view != nil && !view->Rotated()) { /* rot breaks magnif calc */ float fpx0, fpy0, fpx1, fpy1; view->Transform(0.0, 0.0, fpx0, fpy0); view->Transform(1.0, 1.0, fpx1, fpy1); float arrowxmag = fpx1 - fpx0; float arrowymag = fpy1 - fpy0; if (arrowxmag == arrowymag) { /* won't scale arrows in BrushView */ arrowCTM->Scale(arrowxmag, arrowymag); } } float tipx, tipy, tailx, taily; transform(float(x0), float(y0), tipx, tipy, gs); transform(float(x1), float(y1), tailx, taily, gs); float angle = Slope(tipx - tailx, tipy - taily); arrowCTM->Rotate(angle); arrowCTM->Translate(tipx, tipy); origCTM = gs->GetTransformer(); if (origCTM != nil) { origCTM->Reference(); } gs->SetTransformer(arrowCTM); } /* * RestoreCTM restores gs's original transformation matrix. */ void NPtSelection::RestoreCTM (Graphic* gs) { gs->SetTransformer(origCTM); Unref(origCTM); } /* * sign returns 1 if the number's nonnegative, -1 if it's negative. */ inline float sign (float num) { return (num >= 0.) ? 1. : -1.; } /* * Slope returns the number of degrees in the given slope. */ float NPtSelection::Slope (float dx, float dy) { float angle = 0.; if (dx == 0.) { angle = sign(dy) * 90.; } else { angle = degrees(atan(dy/dx)); if (dx < 0.) { angle += sign(dy) * 180.; } } return angle; }