/*
 * Copyright (c) 1991,1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * @(#) $Header: /cvsroot/nsnam/nam-1/view.cc,v 1.31 2011/11/02 16:08:04 tom_henderson Exp $ (LBL)
 */

#include <stdlib.h>
#ifdef WIN32
#include <windows.h>
#endif

#include <ctype.h>
#include <math.h>

#include <tclcl.h>
#include "view.h"
#include "bbox.h"
#include "netview.h"
#include "netmodel.h"
#include "tclcl.h"
#include "paint.h"
#include "packet.h"

// Create list of font names and sizes
static char * fontName[NFONT] = {
  "-adobe-times-medium-r-normal-*-8-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-10-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-12-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-14-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-18-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-20-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-24-*-*-*-*-*-*-*",
  "-adobe-times-medium-r-normal-*-34-*-*-*-*-*-*-*"
};


// Convert a string to world coordinates
int View::getCoord(char *strx, char *stry, float &dx, float &dy) {
	Tcl& tcl = Tcl::instance();

	double tx, ty;
	if ((Tk_GetScreenMM(tcl.interp(), tk_, strx, &tx) != TCL_OK) ||
	    (Tk_GetScreenMM(tcl.interp(), tk_, stry, &ty) != TCL_OK)) {
		return TCL_ERROR;
	}
	
	tx *= pixelsPerMM_;
	ty *= pixelsPerMM_;
	dx = tx, dy = ty;
	matrix_.imap(dx, dy);
	return TCL_OK;
}

void View::zoom(float mag)
{
        magnification_ *= mag;
        resize(width_, height_);
}

//---------------------------------------------------------------------
// void
// View::resize(int width, int height)
//   - resize the display canvas and setup the mapping between 
//     world and screen coordinate systems
//---------------------------------------------------------------------
void View::resize(int width, int height) {
  width_ = width;
  height_ = height;

  matrix_.clear();

  BBox bb;

  // a model can choose to use these values, or can set its own
  // NetModel sets it to bb.clear(), while GraphView use this
  bb.xmin = 0;
  bb.ymin = 0;
  bb.xmax = width;
  bb.ymax = height;

  // WorldBox refers to the boundaries for the network model world 
  // Basically NetView and EditView used to return the bounding box
  // for NetModel not for themselves so now BoundingBox returns
  // values based on the canvas size and getWorldBox returns the 
  // NetModel values.
  getWorldBox(bb);
 // BoundingBox(bb);

  double x = (0.0 - panx_) * width;
  double y = (0.0 - pany_) * height;
  double w = width;
  double h = height;
  /*
   * Set up a transform that maps bb -> canvas.  I.e,
   * bb -> unit square -> allocation, but which retains
   * the aspect ratio.  Also, add a margin.
   */
  double world_width = bb.xmax - bb.xmin;
  double world_height = bb.ymax - bb.ymin;

  
  /* 
   * Grow a margin if we asked for square aspect ratio.
   */
  double bbw;
  double bbh;

  if (aspect_ == SQUARE) {
    bbw = 1.1 * world_width;
    bbh = 1.1 * world_height;

   // bbw = world_width;
   // bbh = world_width*height/width;

 //   if (bbh < world_height) {
      // Need to scale in the other direction
  //    bbw = world_height*width/height;
   //   bbh = world_height;
   // }
  } else {
    bbw = world_width;
    bbh = world_height;
  }

  // Calculate the width and height for translating x and y axis
  double tx = bb.xmin - 0.5 * (bbw - world_width);
  double ty = bb.ymin - 0.5 * (bbh - world_height);
  
  // move base coordinate system to origin
  matrix_.translate(-tx, -ty);
  
  // flip vertical axis because Y is backwards.
  matrix_.scale(1.0, -1.0);
  matrix_.translate(0., bbh);
  
  double ws = w / bbw;
  double hs = h / bbh;
  
  // The matrix_.translate(x expression, y expression) takes care of
  // scrolling when clicking on the scroll bars
  if (aspect_ == SQUARE) {
    if (ws <= hs) {
      matrix_.scale(ws, ws);
      matrix_.translate(x, y + 0.5 * (h - ws * bbh));
    } else {
      matrix_.scale(hs, hs);
      matrix_.translate(x + 0.5 * (w - hs * bbw), y);
    }
  } else {
    matrix_.scale(ws, hs);
    matrix_.translate(x, y);
  }

  if (offscreen_ != 0)
    Tk_FreePixmap(Tk_Display(tk_), offscreen_);

  if ((width_<=0) || (height_<=0))
    abort();

  // Create a new offscreen canvas for double buffered drawing
  offscreen_ = Tk_GetPixmap(Tk_Display(tk_), Tk_WindowId(tk_),
                            width_, height_, Tk_Depth(tk_));


  // Scale for any magnification
  matrix_.scale(magnification_, magnification_);

  if (xscroll_ != NULL) {
    Tcl& tcl = Tcl::instance();
    tcl.evalf("%s set %f %f", xscroll_, panx_, 
                              panx_+(1.0/magnification_));
  }

  if (yscroll_ != NULL) {
    Tcl& tcl = Tcl::instance();
    tcl.evalf("%s set %f %f", yscroll_, pany_, 
                              pany_+(1.0/magnification_));
  }

  pixelsPerMM_ = WidthOfScreen(Tk_Screen(tk_)) / 
                 WidthMMOfScreen(Tk_Screen(tk_));

  if (!bClip_) {
    clip_.xmin = 0;
    clip_.ymin = 0;
    clip_.xmax = width_;
    clip_.ymax = height_;
  }
}

//---------------------------------------------------------------------
// void
// View::draw() 
//   - Does double buffered drawing
//     All objects are drawn to an offscreen area and then the whole
//     offscreen area is copied to the current view --mehringe@isi.edu
//   - render is in a sub class of View
//---------------------------------------------------------------------
void
View::draw() {
	if (offscreen_ == 0) {
		return;
	}
	
	// Clear offscreen bitmap
	XFillRectangle(Tk_Display(tk_), offscreen_, background_,
	               0, 0, width_, height_);

	// Draw objects onto offscreen bitmap
	render();
	
	// Copy over offscreen bitmap to current view
	XCopyArea(Tk_Display(tk_), offscreen_, Tk_WindowId(tk_), background_,
	          0, 0, width_, height_, 0, 0);
}

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
View::View(const char* name, int aspect, int width, int height) :
	next_(NULL),
	magnification_(1.0),
	panx_(0.0),
	pany_(0.0), 
	aspect_(aspect),
	xscroll_(NULL),
	yscroll_(NULL),
	bClip_(0) {

	Tcl& tcl = Tcl::instance();
	tk_ = Tk_CreateWindowFromPath(tcl.interp(), tcl.tkmain(),
	                              (char *) name, 0);

	if (tk_) {
		Tk_SetClass(tk_, "NetView");
		/*XXX*/
		/* Specify preferred window size. */
		Tk_GeometryRequest(tk_, width, height);

		Tk_CreateEventHandler(tk_, ExposureMask|StructureNotifyMask,
		                      handle, (ClientData)this);

		Tk_MakeWindowExist(tk_);

		pixelsPerMM_ = WidthOfScreen(Tk_Screen(tk_)) / 
		               WidthMMOfScreen(Tk_Screen(tk_));
	} else {
		// Just an arbitrary initialization
		pixelsPerMM_ = 1;
	}

	width_ = height_ = 0;
	background_ = Paint::instance()->background_gc();
	offscreen_ = 0;
	load_fonts();
}



View::~View()
{
	/* 
	 * Do not do Tk_DestroyWindow() stuff. Should *never* use
	 * delete <view> to destroy a view
	 */ 
	if (tk_ != 0) {
		if (offscreen_ != 0)
			Tk_FreePixmap(Tk_Display(tk_), offscreen_);
		free_fonts();
	}
	tk_ = 0;
}

//----------------------------------------------------------------------
// void
// View::BoundingBox(BBox& destination)
//  
//----------------------------------------------------------------------
void
View::BoundingBox(BBox& destination) {
  fprintf(stderr,"View::BoundingBox\n");
}

void View::setClipRect(BBox &b) 
{
	XRectangle rect;
	Paint* paint = Paint::instance();
	clip_ = b;
	bClip_ = 1;
	clip_.xmin = clip_.ymin = 0;
	clip_.xmax = (float)width_;
	clip_.ymax = (float)height_;
	b.xrect(rect);
#ifndef WIN32
	for (int i = 0; i < paint->num_gc(); i++) {
		XSetClipRectangles(Tk_Display(tk_), paint->paint_to_gc(i),
				   0, 0, &rect, 1, Unsorted);
	}
#endif
}

void View::clearClipRect()
{
	Paint* paint = Paint::instance();
#ifndef WIN32
	for (int i = 0; i < paint->num_gc(); i++) {
		// XXX Or should I set it to the whole window???
		XSetClipRectangles(Tk_Display(tk_), paint->paint_to_gc(i),
				   0, 0, None, 1, Unsorted);
	}
#endif
	bClip_ = 0;
}

void View::setFunction(int func)
{
	Paint* paint = Paint::instance();
	for (int i = 0; i < paint->num_gc(); i++) 
		XSetFunction(Tk_Display(tk_), paint->paint_to_gc(i), func);
}

/* Handler for the Expose, DestroyNotify and ConfigureNotify events. */
void View::handle(ClientData cd, XEvent* ep)
{
	View* nv = (View*)cd;
	
	switch (ep->type) {
	case Expose:
		if (ep->xexpose.count == 0)
			/*XXX*/
			nv->draw();
		break;
		
	case DestroyNotify:
		/*XXX kill ourself */
		/*XXX: this should kill the viewer! */
		/*XXX how about use delete this? */
		if (nv->tk_ != 0) {
			if (nv->offscreen_ != 0)
				Tk_FreePixmap(Tk_Display(nv->tk_), 
					      nv->offscreen_);
			nv->free_fonts();
		}
		nv->tk_ = 0;
		delete nv;
		break;
		
	case ConfigureNotify:
		if (nv->width_ != ep->xconfigure.width ||
		    nv->height_ != ep->xconfigure.height) {
		  //if it gets smaller, there will be no expose event,
		  //so we have to draw it outselves - mjh
		  int smaller=0;
		  if ((nv->width_ >= ep->xconfigure.width) &&
		      (nv->height_ >= ep->xconfigure.height))
		    smaller=1;
		  nv->resize(ep->xconfigure.width,
			     ep->xconfigure.height);
		  if (smaller)
		    nv->draw();
		}
		break;
	}
}

int View::command(ClientData cd, Tcl_Interp* tcl, int argc, CONST84 char **argv)
{
  NetView *nv = (NetView *)cd;
  if (argc < 2) {
    Tcl_AppendResult(tcl, "\"", argv[0], "\": arg mismatch", 0);
    return (TCL_ERROR);
  }
  if (strcmp(argv[1], "xscroll") == 0) {
    if (argc == 3) {
      nv->xscroll_=new char[strlen(argv[2])+1];
      strcpy(nv->xscroll_, argv[2]);
      return TCL_OK;
    } else {
      Tcl_AppendResult(tcl, "\"", argv[0],
                       "\": arg mismatch", 0);
      return TCL_ERROR;
    }
  }
  if (strcmp(argv[1], "yscroll") == 0) {
    if (argc == 3) {
      nv->yscroll_=new char[strlen(argv[2])+1];
      strcpy(nv->yscroll_, argv[2]);
      return TCL_OK;
    } else {
      Tcl_AppendResult(tcl, "\"", argv[0],
                       "\": arg mismatch", 0);
      return TCL_ERROR;
    }
  }
  if (strcmp(argv[1], "zoom") == 0) {
    if (argc == 3) {
      float mag=atof(argv[2]);
      if (mag>1.0) {
	nv->panx_+=(1.0-1.0/mag)/(2.0*nv->magnification_);
	nv->pany_+=(1.0-1.0/mag)/(2.0*nv->magnification_);
      } else {
	nv->panx_-=(1.0-mag)/(2.0*nv->magnification_*mag);
	nv->pany_-=(1.0-mag)/(2.0*nv->magnification_*mag);
      }
      nv->zoom(mag);
      nv->draw();
      //nv->pan(panx, pany);
      return TCL_OK;
    } else {
      Tcl_AppendResult(tcl, "\"", argv[0],
		       "\": arg mismatch", 0);
      return TCL_ERROR;
    }
  }
  if ((strcmp(argv[1], "xview") == 0) ||
      (strcmp(argv[1], "yview") == 0)) {
    if ((argc==4) && (strcmp(argv[2], "moveto")==0)) {
      if (strcmp(argv[1], "xview") == 0)
	nv->panx_=atof(argv[3]);
      else
	nv->pany_=atof(argv[3]);
      nv->resize(nv->width_, nv->height_);
      nv->draw();

    } else if ((argc==5) && (strcmp(argv[2], "scroll")==0)) {
      float step = atof(argv[3]);

      if (strcmp(argv[4], "units")==0) {
	step *= 0.05/nv->magnification_;
      } else if (strcmp(argv[4], "pages")==0) {
	step *= 0.8/nv->magnification_;
      } else if (strcmp(argv[4], "world")==0) {
        // Used for canvas grab style dragging
       // float x0, y0, x_step, y_step;
       // nv->matrix_.map(0.0, 0.0, x0, y0);
      //  if (strcmp(argv[1], "xview") == 0) {
      //    nv->matrix_.map(step, 0.0, x_step, y_step);
      //    step = x0 - x_step;
      //  } else {
      //    nv->matrix_.map(step, 0.0, x_step, y_step);
      //    step = y0 - y_step;
      //  }
        step /= nv->magnification_;
      }  

      if (strcmp(argv[1], "xview") == 0)
	nv->panx_ += step;
      else
	nv->pany_ += step;

    //  fprintf(stderr, "View::command pan %f,%f\n",nv->panx_,nv->pany_);

      nv->resize(nv->width_, nv->height_);
      nv->draw();

    } else if ((argc == 7) && (strcmp(argv[2], "grab") == 0)) {
      float world_x_start, world_y_start;

      nv->matrix_.imap(atof(argv[3]), atof(argv[4]),
                       world_x_start, world_y_start); 

    } else {
      Tcl_AppendResult(tcl, "\"", argv[0],
		       "\": arg mismatch", 0);
      return TCL_ERROR;
    }
    return TCL_OK;
  }
  Tcl_AppendResult(tcl, "\"", argv[0], "\": unknown arg: ", argv[1], 0);
  return (TCL_ERROR);
}


void 
View::line(float x0, float y0, float x1, float y1, int paint)
{
	int ax, ay;
	matrix_.map(x0, y0, ax, ay);
	int bx, by;
	matrix_.map(x1, y1, bx, by);
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawLine(Tk_Display(tk_), offscreen_, gc, ax, ay, bx, by);
}

void View::rect(float x0, float y0, float x1, float y1, int paint)
{
	int x, y;
	matrix_.map(x0, y0, x, y);
	int xx, yy;
	matrix_.map(x1, y1, xx, yy);
	
	int w = xx - x;
	if (w < 0) {
		x = xx;
		w = -w;
	}
	int h = yy - y;
	if (h < 0) {
		h = -h;
		y = yy;
	}
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawRectangle(Tk_Display(tk_), offscreen_, gc, x, y, w, h);
}

void View::polygon(const float* x, const float* y, int n, int paint)
{
	/*XXX*/
	XPoint pts[10];
	
	for (int i = 0; i < n; ++i) {
		float tx, ty;
		matrix_.map(x[i], y[i], tx, ty);
		pts[i].x = int(tx);
		pts[i].y = int(ty);
	}
	pts[n] = pts[0];
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawLines(Tk_Display(tk_), offscreen_, gc, pts, n + 1,
		   CoordModeOrigin);
}

void View::fill(const float* x, const float* y, int n, int paint)
{
	/*XXX*/
	XPoint pts[10];
	
	for (int i = 0; i < n; ++i) {
		float tx, ty;
		matrix_.map(x[i], y[i], tx, ty);
		pts[i].x = int(tx);
		pts[i].y = int(ty);
	}
	pts[n] = pts[0];
	GC gc = Paint::instance()->paint_to_gc(paint);
	XFillPolygon(Tk_Display(tk_), offscreen_, gc, pts, n + 1,
		     Convex, CoordModeOrigin);
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
void View::circle(float x, float y, float r, int paint) {
  int tx, ty;     // Translated x and y
  int tr, dummy;  // Translated radius
  
  // Map world coordinates to current view coordinates
  matrix_.map(x, y, tx, ty);
  matrix_.map(x + r, y, tr, dummy);

//  fprintf(stderr, "View::circle x %f y %f tx %d ty %d\n", x,y,tx,ty);

  // XDrawArc starts from the corner so we have to 
  // move the tx and ty coordinates from the center
  // of the circle to the corner
  tr -= tx;
  tx -= tr;
  ty -= tr;
  
  // We want tr to be the diameter of the arc
  tr *= 2;
  GC gc = Paint::instance()->paint_to_gc(paint);
  XDrawArc(Tk_Display(tk_), offscreen_, gc, tx, ty, tr, tr, 0, 64 * 360);
}

//----------------------------------------------------------------------
// void 
// View::load_fonts()
//   - Set the font structures using values in 'fontName'(defined above)
//----------------------------------------------------------------------
void 
View::load_fonts() {
	Tcl_Interp* tcl;
	nfont_ = 0;
	
	if (tk_) {
		tcl = Tcl::instance().interp();
		// Load each font described in fontName[]
		for (int i = 0; i < NFONT; ++i) {
			fonts_[nfont_] = Tk_GetFont(tcl, tk_, fontName[i]);
			if (fonts_[nfont_] == 0) {
				fprintf(stderr, "Unable to load font: %s\n",fontName[i]);
				continue;
			}
			font_gc_[nfont_] = Paint::instance()->text_gc(Tk_FontId(fonts_[nfont_]));
			++nfont_;
		}
		if (nfont_ == 0) {
			fprintf(stderr, "nam: warning no fonts found\n");
		} else {
			default_font_ = TIMES_14POINT;
		}
	}
}

void View::free_fonts()
{
	/*XXX Tk_FreeFontStruct*/
	for (int i = 0; i < NFONT; i++) {
		Tk_FreeFont(fonts_[i]);
	}
}

//----------------------------------------------------------------------
// int
// View::lookup_font(int d)
//   - returns the index into the fonts_ array that is smaller in size
//     than d
//   - returns -1 if nfont_ is 0 which indicates that no fonts have been
//     loaded
//----------------------------------------------------------------------
int
View::lookup_font(int d) {
	int i = nfont_;
	while (--i > 0) {
		Tk_Font f = fonts_[i];
		Tk_FontMetrics p;
		Tk_GetFontMetrics(f, &p);
		if (d >= p.ascent + p.descent) {
			return (i);
    } 
	}
	if (nfont_) {
		return 0;
	}

	return -1;
}

//----------------------------------------------------------------------
// int
// View::getStringScreenWidth(char * text, double screen_height)
//   - Calculate the width of a string in screen coordinates based upon
//     the font size selected by the height of the string
//----------------------------------------------------------------------
int
View::getStringScreenWidth(const char * text, int screen_height) {
	int font_id;
	font_id = lookup_font(screen_height);
	if (font_id != -1) {
		return Tk_TextWidth(fonts_[lookup_font(screen_height)],
												text, strlen(text));
	} else { 
		return 0;
	}
		
}


//----------------------------------------------------------------------
// double
// View::getStringWidth(char * text, double world_height)
//   - calculates a strings width and returns it in world size
//----------------------------------------------------------------------
double
View::getStringWidth(const char * text, double world_height) {
  float x_min, x_max, y_min, y_max, discarded;
  double world_width;
  int screen_height, screen_width;
  
  matrix_.map(0.0, 0.0, discarded, y_min);
  matrix_.map(0.0, world_height, discarded, y_max);
  screen_height = (int) ceil(y_min - y_max);

  screen_width = getStringScreenWidth(text, screen_height);
  matrix_.imap(0.0, 0.0, x_min, discarded);
  matrix_.imap((float) screen_width, 0.0, x_max, discarded);
  world_width = x_max - x_min;

  return world_width;
}

//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int
View::getStringHeight(char * text) {
  return 0; 
}


//-----------------------------------------------------------------------
// void
// View::string(float fx, float fy, float dim, const char* s, 
//              int anchor, const char* color)
//     - This draws a string on the view
//     - Whoever wrote this code should be shot.  For anyone who is
//       working on nam in the future. Read this:
//         PUT SOME COMMENTS IN YOUR CODE!!!
//         and I don't mean comments like this is a hack or this
//         code is bad.  
//         Also, USE DESCRIPTIVE VARIBALE NAMES!!! Don't use just i or
//         f or d, dlow, dhigh or just p.  What the does that stand for?
//     - This code is driving me insane because I have to rewrite and 
//       comment everything just to understand what the hell the person 
//       before me was trying to do.    
//----------------------------------------------------------------------
void
View::string(float fx, float fy, float dim, const char* s, 
                  int anchor, const char* color) {
	if (nfont_ <= 0)
		return;
	
	int dummy;
	int dlow, dhigh;
	//Tcl& tcl = Tcl::instance();
	
	/*XXX this could be cached*/
	matrix_.map(0., 0., dummy, dlow);
	matrix_.map(0., 0.9 * dim, dummy, dhigh);
	int d = dhigh - dlow;
	if (d < 0)
		d = -d;
	int font = lookup_font(d);
	Tk_Font f = fonts_[font];
	Tk_FontMetrics p;
	Tk_GetFontMetrics(f, &p);

	int h = p.ascent;
	int len = strlen(s);
	int w = Tk_TextWidth(f, s, len);
	
	int x, y;
	matrix_.map(fx, fy, x, y);
	
	/* XXX still need to adjust for mismatch between d and actual height*/
	
	switch (anchor) {
		
	case ANCHOR_CENTER:
		x -= w / 2;
		y += h / 2;
		break;
		
	case ANCHOR_NORTH:
		x -= w / 2;
		y += d;
		break;
		
	case ANCHOR_SOUTH:
		x -= w / 2;
		y -= h/2;
		break;
		
	case ANCHOR_WEST:
		y += h / 2;
		break;
		
	case ANCHOR_EAST:
		x -= w;
		y += h / 2;
		break;
	}

	// XXX Very very very bad hack. Should go through Paint::text_gc()!!
	//Colormap cmap;
	//Tk_Uid colorname;
	GC fgc = font_gc_[font]; // Font gc
	if (color != NULL) {
		fgc = Paint::instance()->text_gc(Tk_FontId(fonts_[font]), color);
	}

	Tk_DrawChars(Tk_Display(tk_), offscreen_, fgc, f, s, len, x, y);
	if (color != NULL) {
		Tk_FreeGC(Tk_Display(tk_), fgc);
	}
	// XXX We don't free the color allocated above. Hope it won't 
	// be a problem!
}


//----------------------------------------------------------------------
//
//----------------------------------------------------------------------
int
View::string(const char * text, double world_x, double world_y,
             double size, const char * color) {
  int screen_x, screen_y, font_index;
  Tk_Font font;
  Tk_FontMetrics font_metrics;
  int width;
  GC font_gc;
  int dummy, y_min, y_max;

  width = 0;
  if (nfont_ > 0) {
    matrix_.map(world_x, world_y, screen_x, screen_y);

    // Find screen height of box
    matrix_.map(0., 0., dummy, y_min);
    matrix_.map(0., 0.9 * size, dummy, y_max);
    int height = y_max - y_min ;
    if (height < 0) {
      height = -1*height;
    }
    font_index = lookup_font(height);

    //font_index = TIMES_14POINT;
    font = fonts_[font_index];
    Tk_GetFontMetrics(font, &font_metrics);

    font_gc = font_gc_[font_index]; // Font gc
    if (color) { 
      font_gc = Paint::instance()->text_gc(Tk_FontId(font), color);
    }

	  width = Tk_TextWidth(font, text, strlen(text));

    Tk_DrawChars(Tk_Display(tk_), offscreen_, font_gc, font, text, 
                 strlen(text), screen_x, screen_y);

    if (color) {
      Tk_FreeGC(Tk_Display(tk_), font_gc);
    }
  }
  return width;
}


//----------------------------------------------------------------------
//----------------------------------------------------------------------
void
View::boxedString(const char * text, double world_x, double world_y,
                  double vertical_size, int paint, 
                  const char * color) {
  float box_width, text_width;
  float x_padding, y_padding;

  box_width = getStringWidth(text, vertical_size);
  text_width = getStringWidth(text, 0.9*vertical_size);

  x_padding = (box_width - text_width)/2.0;
  y_padding = 0.05*vertical_size;
  string(text, world_x + x_padding, world_y + y_padding, 0.9*vertical_size, color);

  rect(world_x, world_y, world_x + box_width, world_y + vertical_size, paint);  
}
  
  

