/* * 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.30 2003/10/11 22:56:51 xuanc Exp $ (LBL) */ #include #ifdef WIN32 #include #endif #include #include #include #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 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 x_start, y_start; float world_x_start, world_y_start; nv->matrix_.imap(atof(argv[3]), atof(argv[4]), world_x_start, world_y_start); x_start = atof(argv[3]); y_start = atof(argv[3]); } 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, height; float x_padding, y_padding; height = 0; 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); }