/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Bert Lisser - Bert.Lisser@cwi.nl (CWI) * * Paul Klint - Paul.Klint@cwi.nl - CWI * * Arnold Lankamp - Arnold.Lankamp@cwi.nl *******************************************************************************/ package org.rascalmpl.eclipse.library.vis.figure; import static org.rascalmpl.eclipse.library.vis.properties.Properties.ASPECT_RATIO; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FILL_COLOR; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FONT; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FONT_BOLD; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FONT_COLOR; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FONT_ITALIC; import static org.rascalmpl.eclipse.library.vis.properties.Properties.FONT_SIZE; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HALIGN; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HGROW; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HRESIZABLE; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HSHADOWPOS; import static org.rascalmpl.eclipse.library.vis.properties.Properties.LINE_COLOR; import static org.rascalmpl.eclipse.library.vis.properties.Properties.LINE_STYLE; import static org.rascalmpl.eclipse.library.vis.properties.Properties.LINE_WIDTH; import static org.rascalmpl.eclipse.library.vis.properties.Properties.SHADOW; import static org.rascalmpl.eclipse.library.vis.properties.Properties.SHADOW_COLOR; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VALIGN; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VGROW; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VRESIZABLE; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VSHADOWPOS; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.ALIGN; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.MIRROR; import static org.rascalmpl.eclipse.library.vis.properties.TwoDProperties.SIZE; import static org.rascalmpl.eclipse.library.vis.util.vector.Dimension.HOR_VER; import static org.rascalmpl.eclipse.library.vis.util.vector.Dimension.X; import static org.rascalmpl.eclipse.library.vis.util.vector.Dimension.Y; import java.util.List; import org.rascalmpl.eclipse.library.vis.figure.interaction.MouseOver; import org.rascalmpl.eclipse.library.vis.graphics.FontStyle; import org.rascalmpl.eclipse.library.vis.graphics.GraphicsContext; import org.rascalmpl.eclipse.library.vis.properties.Properties; import org.rascalmpl.eclipse.library.vis.properties.PropertyManager; import org.rascalmpl.eclipse.library.vis.swt.ICallbackEnv; import org.rascalmpl.eclipse.library.vis.swt.IFigureConstructionEnv; import org.rascalmpl.eclipse.library.vis.swt.SWTFontsAndColors; import org.rascalmpl.eclipse.library.vis.swt.applet.IHasSWTElement; import org.rascalmpl.eclipse.library.vis.util.NameResolver; import org.rascalmpl.eclipse.library.vis.util.vector.BoundingBox; import org.rascalmpl.eclipse.library.vis.util.vector.Coordinate; import org.rascalmpl.eclipse.library.vis.util.vector.Dimension; import org.rascalmpl.eclipse.library.vis.util.vector.Rectangle; import org.rascalmpl.eclipse.library.vis.util.vector.TransformMatrix; import org.rascalmpl.eclipse.library.vis.util.vector.TwoDimensional; import io.usethesource.vallang.IBool; import io.usethesource.vallang.IMap; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import org.rascalmpl.values.ValueFactoryFactory; /** * Figures are the foundation of Rascal visualization. They are based on a * bounding box. The bounding box defines the maximal dimensions * of the element. * * @author paulk */ public abstract class Figure implements Comparable<Figure> { public static final IValueFactory VF = ValueFactoryFactory.getValueFactory(); public static enum ResizeMode{ RESIZE, ZOOM; } public static enum RecomputeStatus{ DID_NOT_CHANGE, CHANGED; } public static final Figure[] childless = new Figure[0]; public static final Type[] noTypes = new Type[0]; public static final IValue[] noArgs = new IValue[0]; public static int sequencer = 0; // to impose arbitrary ordering on figures @SuppressWarnings("unused") private static final boolean debug = false; public int sequenceNr; public RecomputeStatus status; public Figure[] children; public boolean mouseOver; // set externally, only set when relevant (see MouseAndKeyboardHandler) public PropertyManager prop; public BoundingBox minSize; public BoundingBox size; public Coordinate localLocation; // the location of the left, top corner of this figure relative to the parent location public Coordinate globalLocation; // public TwoDimensional<Boolean> resizable; public Figure(PropertyManager properties) { this.prop = properties; minSize = new BoundingBox(); size = new BoundingBox(); localLocation = new Coordinate(); globalLocation = new Coordinate(); resizable = new TwoDimensional<Boolean>(true, true); sequenceNr = sequencer; sequencer++; } public final void registerIds(IFigureConstructionEnv env,NameResolver resolver){ resolver.register(this); registerMore(env,resolver); setChildren(env,resolver); for(Figure child : children){ child.registerIds(env,resolver); } } void registerMore(IFigureConstructionEnv env, NameResolver resolver) {} public void setChildren(IFigureConstructionEnv env, NameResolver resolver) {} public final void registerConverts(NameResolver resolver){ prop.registerMeasures(resolver); for(Figure child : children){ child.registerConverts(resolver); } } /* * There are three major phases in the processing of Figures: * - init: compute minimal size * - resize: compute actual size * - draw: draw it. */ /* PHASE: init * * Down: * - initialize (computefigure, register controls and other stuff) (*) * - registerNames (defined by id property). * Up: * - register measures * - compute min size (*) * - finalize (*) * * (*) : extension point for subclasses. */ /** * * @param env the callback environment for computing stuff when initializing * @param resolver the name resolver * @param swtSeen * @param visible * @param overlaps the current set of overlaps (figure not abiding to strict inclusive layout), add overlapping figures here * @param zparent the parent in the zorder tree, this is a partial view of the figure tree describing only the swt elements and hence their z order * @return */ public final boolean init(IFigureConstructionEnv env,NameResolver resolver, MouseOver mparent, boolean swtSeen, boolean visible){ resizable.set(prop.getBool(HRESIZABLE), prop.getBool(VRESIZABLE)); initElem(env, mparent, swtSeen, visible, resolver); swtSeen = initChildren(env, resolver, mparent, swtSeen, visible); swtSeen = swtSeen || containsSWTElement(); computeMinSize(); adjustMinSize(); finalize(true); return swtSeen; } public boolean containsSWTElement() { return false; } public boolean initChildren(IFigureConstructionEnv env, NameResolver resolver, MouseOver mparent, boolean swtSeen, boolean visible) { boolean swtSeenResult = false; for(int i = 0; i < children.length ; i++){ boolean here = children[i].init(env, resolver,mparent, swtSeen, visible); swtSeenResult = swtSeenResult || here; } return swtSeenResult; } public void initElem(IFigureConstructionEnv env, MouseOver mparent, boolean swtSeen, boolean visible, NameResolver resolver){} public abstract void computeMinSize() ; public void finalize(boolean needsRecompute){} /* * PHASE: resize * * Down: * - resize * * - Size has been set by parent. * - Set size of all children and their locallocations (globalocations are set automatically in the step below). * - register the zorder elements (SWT related) (*) * - set the global location of children (transform locallocation to globallocation) * Up: * - onResizeUp (do some stuff if the clients wants to) (*) * * (*) : extension point for subclasses. */ public final void resize(Rectangle view,TransformMatrix transform){ adjustSizeAndLocation(); resizeElement(view); resizeChildren(view, transform); onResizeUp(); } public void resizeChildren(Rectangle view, TransformMatrix transform) { for(Figure child : children){ for(Dimension d : HOR_VER){ if(prop.get2DBool(d, MIRROR)){ child.localLocation.set(d,size.get(d) - (child.localLocation.get(d) + child.size.get(d))); } } child.globalLocation.set(globalLocation); child.resize(view,transform); } } public abstract void resizeElement(Rectangle view) ; public void applyTransformation(TransformMatrix transform) {} public void reverseTransformation(TransformMatrix transform) {} public void onResizeUp() { } public void updateGlobalLocation(){ for(Figure child : children){ child.globalLocation.set(globalLocation); child.globalLocation.add(child.localLocation); child.updateGlobalLocation(); } } /* * PHASE: draw * * Down: * - beforeDraw (optional preprocessing) (*) * - applyProperties * - drawElement (draw the current figure) * - drawChildren (draw its children). * Up: there is no up phase. * * (*) : extension point for subclasses. * */ public final void draw(Coordinate zoom, GraphicsContext gc,Rectangle part, List<IHasSWTElement> visibleSWTElements) { // TODO: iets met rotate en transformaties // Coordinate offset = new Coordinate(); // double oldZoomX, oldZoomY; // oldZoomX = zoom.getX(); // oldZoomY = zoom.getY(); // for(Dimension d : HOR_VER){ // if(!prop.get2DBool(d, ZOOMABLE)){ // offset.set(d, size.get(d) * (zoom.get(d) - 1.0) * prop.get2DReal(d, ALIGN) ); // zoom.set(d, 1.0); // } // } // if(zoom.getX() != zoom.getY() && prop.isSet(ASPECT_RATIO)){ // double minZoom = Math.min(zoom.getX(),zoom.getY()); // zoom.set(minZoom,minZoom); // } // gc.translate(offset.getX(), offset.getY()); beforeDraw(zoom); applyProperties(gc); drawElement(gc, visibleSWTElements); drawChildren(zoom, gc, part, visibleSWTElements); // gc.translate(-offset.getX(), -offset.getY()); //zoom.set(oldZoomX, oldZoomY); } public void drawChildren(Coordinate zoom, GraphicsContext gc, Rectangle part, List<IHasSWTElement> visibleSWTElements) { for(Figure f : children){ //if(f.overlapsWith(part)){ Rectangle npart = f.isContainedIn(part) ? null : part; f.draw(zoom, gc, npart, visibleSWTElements); //} } } public void beforeDraw(Coordinate zoom) {} public void drawElement(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){} public boolean widthDependsOnHeight(){ return false; } public Dimension getMajorDimension(){ return X; } /** * Give a figure the opportunity to remove allocated components, etc. */ public void destroy(IFigureConstructionEnv env) { for(Figure child : children){ child.destroy(env); } destroyElement(env); } public void destroyElement(IFigureConstructionEnv env) { } public void hide(IFigureConstructionEnv env) { for(Figure child : children){ child.hide(env); } hideElement(env); } public void hideElement(IFigureConstructionEnv env) { destroyElement(env); } public void getFiguresUnderMouse(Coordinate c,List<Figure> result){ if(!mouseInside(c)){ return; } for(Figure child : children){ child.getFiguresUnderMouse(c, result); // TODO: overlap } if(handlesInput()){ result.add(this); } } public boolean handlesInput(){ return prop.hasHandlerProperties(); } private void adjustMinSize() { if(prop.isSet(ASPECT_RATIO)){ double ar = prop.getReal(ASPECT_RATIO); minSize.setMax(X, minSize.getY() * ar); minSize.setMax(Y, minSize.getX() / ar); } for(Dimension d : HOR_VER){ minSize.setMax(d,prop.get2DReal(d, SIZE)); } } public void adjustSizeAndLocation(){ for(Dimension d : HOR_VER){ if(!resizable.get(d)){ localLocation.add(d, (size.get(d) - minSize.get(d)) * prop.get2DReal(d, ALIGN) ); size.set(d,minSize.get(d)); } } if(prop.isSet(ASPECT_RATIO)){ double ar = prop.getReal(Properties.ASPECT_RATIO); double car = size.getX() / size.getY(); if(car > ar) { // too wide double newWidth = size.getY() * ar; localLocation.add(X, (size.getX() - newWidth) * prop.getReal(HALIGN)); size.setX(newWidth); } else if( car < ar){ // too tall double newHeight = size.getX() / ar; localLocation.add(Y, (size.getY() - newHeight) * prop.getReal(VALIGN)); size.setY(newHeight); } } globalLocation.add(localLocation); } /* * Compare two Figures using an arbitrary ordering * * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Figure o) { return sequenceNr - o.sequenceNr; } public BoundingBox getMinViewingSize() { BoundingBox minViewSize = new BoundingBox(); minViewSize.set(minSize.getX() / prop.getReal(HGROW), minSize.getY() /prop.getReal(VGROW) ); return minViewSize; } public boolean overlapsWith(Rectangle r){ return r == null || r.overlapsWith(globalLocation, size); } public boolean isContainedIn(Rectangle r){ return r == null || r.contains(globalLocation,size); } public Rectangle getRectangle(){ return new Rectangle(globalLocation, size); } public Rectangle getRectangleIncludingOuterLines(){ double hlw = 0.5 * prop.getReal(LINE_WIDTH); return new Rectangle(globalLocation.getX() - hlw, globalLocation.getY() -hlw , size.getX() + 2*hlw, size.getY() + 2*hlw); } public boolean executeKeyHandlers(ICallbackEnv env,IValue keySym, boolean keyDown, IMap modifiers){ Type[] types = {keySym.getType(),modifiers.getType()}; IValue[] args = {keySym,modifiers}; if(keyDown) return executeHandlerProperty(env,Properties.ON_KEY_DOWN,types,args); else return executeHandlerProperty(env,Properties.ON_KEY_UP,types,args); } public void executeMouseMoveHandlers(ICallbackEnv env, boolean enter) { Type[] types = {}; IValue[] args = {}; if(enter) executeHandlerProperty(env,Properties.ON_MOUSE_OVER,types,args); else executeHandlerProperty(env,Properties.ON_MOUSE_OFF,types,args); } public boolean mouseInside(Coordinate c) { return c.getX() >= globalLocation.getX() && c.getX() <= globalLocation.getX() + size.getX() && c.getY() >= globalLocation.getY() && c.getY() <= globalLocation.getY()+ size.getY(); } public boolean executeOnClick(ICallbackEnv env, int button, IMap modifiers, boolean down) { Type[] types = {TypeFactory.getInstance().integerType(),modifiers.getType()}; IValue[] args = {VF.integer(button),modifiers}; if(down) return executeHandlerProperty(env, Properties.ON_MOUSE_DOWN, types, args); else return executeHandlerProperty(env, Properties.ON_MOUSE_UP, types, args); } // returns if the event is captured (i.e. not propagated further) public boolean executeHandlerProperty(ICallbackEnv env, Properties property, Type[] types, IValue[] args){ if(prop.isSet(property)){ IValue v = prop.executeHandler(env, property, types, args); if(v instanceof IBool){ return ((IBool)v).getValue(); } else { return false; } } else { return false; // event not consumed, so not captured } } public void applyProperties(GraphicsContext gc) { gc.fill(prop.getColor(FILL_COLOR)); gc.stroke(prop.getColor(LINE_COLOR)); gc.strokeWeight(prop.getReal(LINE_WIDTH)); gc.strokeStyle(prop.getStr(LINE_STYLE)); boolean shadow = prop.getBool(SHADOW); gc.setShadow(shadow); if (shadow) { gc.setShadowColor(prop.getColor(SHADOW_COLOR)); gc.setShadowLeft(prop.getReal(HSHADOWPOS)); gc.setShadowTop(prop.getReal(VSHADOWPOS)); } gc.setFont(prop.getStr(FONT), prop.getInt(FONT_SIZE), FontStyle.getFontStyles(prop.getBool(FONT_BOLD), prop.getBool(FONT_ITALIC))); gc.font(prop.getColor(FONT_COLOR)); } public double getTextAscent(){ return SWTFontsAndColors.textAscent( prop.getStr(FONT), prop.getInt(FONT_SIZE),FontStyle.getFontStyles(prop.getBool(FONT_BOLD), prop.getBool(FONT_ITALIC))); } public double getTextDescent(){ return SWTFontsAndColors.textDescent( prop.getStr(FONT), prop.getInt(FONT_SIZE),FontStyle.getFontStyles(prop.getBool(FONT_BOLD), prop.getBool(FONT_ITALIC))); } public double getTextHeight(){ return getTextAscent() + getTextDescent(); } public double getTextWidth(String s){ return SWTFontsAndColors.textWidth(s, prop.getStr(FONT), prop.getInt(FONT_SIZE),FontStyle.getFontStyles(prop.getBool(FONT_BOLD), prop.getBool(FONT_ITALIC))); } /** * Draw an arrow from an external position (fromX, fromY) directed to the * center (X,Y) of the current figure. The arrow is placed at At the * intersection with the border of the current figure and it is * appropriately rotated. * @param X * X of center of current figure * @param Y * Y of center of current figure * @param fromX * X of center of figure from which connection is to be drawn * @param fromY * Y of center of figure from which connection is to be drawn * @param toArrow * the figure to be used as arrow (the upperside will act as arrow head) */ public void connectArrowFrom(double X, double Y, double fromX, double fromY, Figure toArrow, GraphicsContext gc, List<IHasSWTElement> visibleSWTElements ) { System.out.printf("Niet gespecialiseerd %s!!\n",this); for(Dimension d : HOR_VER){ toArrow.minSize.set(d,toArrow.prop.get2DReal(d, SIZE)); } toArrow.size.set(toArrow.minSize); toArrow.globalLocation.set(0,0); toArrow.localLocation.set(0,0); toArrow.resize(null, new TransformMatrix()); if (fromX == X) fromX += 0.00001; double s = (fromY - Y) / (fromX -X); double rotd = Math.toDegrees(Math.atan(s)); double IX; double IY; double h2 = minSize.getY() / 2; double w2 = minSize.getX() / 2; if ((-h2 <= s * w2) && (s * w2 <= h2)) { if (fromX > X) { // right IX = X + w2; IY = Y + s * w2; rotd += (fromY < Y ? -90 : -90); } else { // left IX = X - w2; IY = Y - s * w2; rotd += 90; } } else { if (fromY > Y) { // bottom IX = X + h2 / s; IY = Y + h2; rotd += (fromX < X ? 90 : -90); } else { // top IX = X - h2 / s; IY = Y - h2; rotd += (fromX < X ? 90 : -90); } } gc.pushMatrix(); gc.translate(IX , IY); gc.rotate(rotd); gc.translate(-toArrow.size.getX()/2.0,0); toArrow.applyProperties(gc); toArrow.drawElement(gc,visibleSWTElements); gc.popMatrix(); } public Coordinate getGlobalCenter(){ return new Coordinate(globalLocation.getX() + size.getX()/2, globalLocation.getY() + size.getY()/2); } public Coordinate getLocalCenter(){ return new Coordinate(localLocation.getX() + size.getX()/2, localLocation.getY() + size.getY()/2); } }