/* Part of the GUI library for Processing http://www.lagers.org.uk/g4p/index.html http://sourceforge.net/projects/g4p/files/?source=navbar Copyright (c) 2008-12 Peter Lager This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package automenta.vivisect.gui; import automenta.vivisect.Widget; import automenta.vivisect.swing.PCanvas; import java.awt.Color; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import processing.core.PApplet; import processing.core.PConstants; import processing.core.PGraphics; import processing.core.PGraphicsJava2D; import processing.event.KeyEvent; import processing.event.MouseEvent; /** * Abstract base class for all GUI controls. * * @author Peter Lager * */ public abstract class GControl extends Widget implements PConstants, GConstants, GConstantsInternal { /* * INTERNAL USE ONLY * This holds a reference to the GComponent that currently has the * focus. * A component loses focus when another component takes focus with the * takeFocus() method. The takeFocus method should use focusIsWith.loseFocus() * before setting its value to the new component. */ static GControl focusIsWith = null; /* * INTERNAL USE ONLY * Use by the tab manager to move focus between controls * */ static GControl controlToTakeFocus = null; /* * INTERNAL USE ONLY * Keeps track of the component the mouse is over so the mouse * cursor can be changed if we wish. */ static GControl cursorIsOver; // Increment to be used if on a GPanel final static int Z_PANEL = 1024; // Components that don't release focus automatically // i.e. GTextField final static int Z_STICKY = 0; // Components that automatically releases focus when appropriate // e.g. GButton final static int Z_SLIPPY = 24; // Components that expand e.g. GDropList final static int Z_SLIPPY_EXPANDS = 48; // Reference to the PApplet object that owns this control protected PCanvas winApp; protected float sx = 1f, sy = 1f; /* Used to when components overlap */ protected int z = Z_STICKY; // Set to true when mouse is dragging : set false on button released protected boolean dragging = false; protected static float epsilon = 0.001f; /** * Link to the parent panel (if null then it is on main window) */ protected GControl parent = null; /* * A list of child GComponents added to this component * Created and used by GPanel and GDropList classes */ protected List<GControl> children = null; protected int localColorScheme = GUI.globalColorScheme; protected int[] palette = null; protected Color[] jpalette = null; protected int alphaLevel = GUI.globalAlpha; /** * Top left position of component in pixels (relative to parent or absolute * if parent is null) (changed form int data type in V3 */ protected float x, y; /** * Width and height of component in pixels for drawing background (changed * form int data type in V3 */ protected float width, height; /** * Half sizes reduces programming complexity later */ protected float halfWidth, halfHeight; /** * The centre of the control */ protected float cx, cy; /** * The angle to control is rotated (radians) */ protected float rotAngle; /** * Introduced V3 to speed up AffineTransform operations */ protected double[] temp = new double[2]; // New to V3 components have an image buffer which is only redrawn if // it has been invalidated protected PGraphicsJava2D buffer = null; protected boolean bufferInvalid = true; /** * Whether to show background or not */ protected boolean opaque = false; // The cursor image when over a control // This should be set in the controls constructor protected int cursorOver = HAND; /* * Position over control corrected for any transformation. <br> * [0,0] is top left corner of the control. * This is used to determine the mouse position over any * particular control or part of a control. */ protected float ox, oy; /* Simple tag that can be used by the user */ public String tag; /* Allows user to specify a number for this component */ public int tagNo; /* Is the component visible or not */ boolean visible = true; /* Is the component enabled to generate mouse and keyboard events */ boolean enabled = true; /* * Is the component available for mouse and keyboard events. * This is only used internally to prevent user input being * processed during animation. * It will preserve enabled and visible flags */ boolean available = true; /* The object to handle the event */ protected Object eventHandlerObject = null; /* The method in eventHandlerObject to execute */ protected Method eventHandlerMethod = null; /* the name of the method to handle the event */ protected String eventHandlerMethodName; int registeredMethods = 0; /* * Specify the PImage that contains the image{s} to be used for the button's state. <br> * This image may be a composite of 1 to 3 images tiled horizontally. * @param img * @param nbrImages in the range 1 - 3 */ // static PImage[] loadImages(PImage img, int nbrImages){ // if(img == null || nbrImages <= 0 || nbrImages > 3) // return null; // PImage[] bimage = new PImage[3]; // int iw = img.width / nbrImages; // for(int i = 0; i < nbrImages; i++){ // bimage[i] = new PImage(iw, img.height, ARGB); // bimage[i].copy(img, // i * iw, 0, iw, img.height, // 0, 0, iw, img.height); // } // // If less than 3 images reuse last image in set // for(int i = nbrImages; i < 3; i++) // bimage[i] = bimage[nbrImages - 1]; // return bimage; // } // public static String getFocusName(){ // if(focusIsWith == null) // return "null"; // else // return focusIsWith.toString(); // } /** * Base constructor for ALL control ctors that do not have a visible UI but * require access to a PApplet object. <br> * As of V3.5 the only class using this constructor is GGroup * * @param theApplet */ public GControl(PApplet theApplet) { super(); // If this is the first control to be created then theAapplet must be the sketchApplet if (GUI.applet == null) { GUI.applet = theApplet; } winApp = (PCanvas) theApplet; tag = this.getClass().getSimpleName(); } /** * Base constructor for ALL control ctors that have a visible UI but whose * width and height are determined elsewhere e.g. the size of an image. It * will set the position of the control based on controlMode. <br> * */ public GControl(PApplet theApplet, float p0, float p1) { this(theApplet); switch (GUI.control_mode) { case CORNER: // (x,y,w,h) case CORNERS: // (x0,y0,x1,y1) x = p0; y = p1; break; case CENTER: // (cx,cy,w,h) cx = p0; cy = p1; break; } GCScheme.makeColorSchemes(winApp); rotAngle = 0; z = 0; palette = GCScheme.getColor(localColorScheme); jpalette = GCScheme.getJavaColor(localColorScheme); } /** * Base constructor for ALL control ctors that have a visible UI. It will * set the position and size of the control based on controlMode. <br> * */ public GControl(PApplet theApplet, float p0, float p1, float p2, float p3) { this(theApplet); setPositionAndSize(p0, p1, p2, p3); // Create the buffer (only created with this ctor) buffer = (PGraphicsJava2D) winApp.createGraphics((int) width, (int) height, PApplet.JAVA2D); buffer.rectMode(PApplet.CORNER); GCScheme.makeColorSchemes(winApp); rotAngle = 0; z = 0; palette = GCScheme.getColor(localColorScheme); jpalette = GCScheme.getJavaColor(localColorScheme); } /** * Calculate all the variables that determine the position and size of the * control. This depends on * <pre>control_mode</pre> * */ private void setPositionAndSize(float n0, float n1, float n2, float n3) { switch (GUI.control_mode) { case CORNER: // (x,y,w,h) x = n0; y = n1; width = n2; height = n3; halfWidth = width / 2; halfHeight = height / 2; cx = x + halfWidth; cy = y + halfHeight; break; case CORNERS: // (x0,y0,x1,y1) x = n0; y = n1; width = n2 - n0; height = n3 - n1; halfWidth = width / 2; halfHeight = height / 2; cx = x + halfWidth; cy = y + halfHeight; break; case CENTER: // (cx,cy,w,h) cx = n0; cy = n1; width = n2; height = n3; halfWidth = width / 2; halfHeight = height / 2; x = cx - halfWidth; y = cy - halfHeight; break; } } /** * Used internally to enforce minimum size constraints * * @param w the new width * @param h the new height */ protected void resize(int w, int h) { width = w; height = h; halfWidth = width / 2; halfHeight = height / 2; switch (GUI.control_mode) { case CORNER: // (x,y,w,h) case CORNERS: // (x0,y0,x1,y1) cx = x + halfWidth; cy = y + halfHeight; break; case CENTER: // (cx,cy,w,h) x = cx - halfWidth; y = cy - halfHeight; break; } buffer = (PGraphicsJava2D) winApp.createGraphics(w, h, PApplet.JAVA2D); buffer.rectMode(PApplet.CORNER); } /** * If the component responds to key or mouse input or has a visual * representation this it can be part of a group controller. * * @param control the GUI control we are interested in * @return true if it can be added to a group controller */ protected boolean isSuitableForGroupControl(GControl control) { return (GROUP_CONTROL_METHOD & registeredMethods) != 0; } /* * These are empty methods to enable polymorphism */ public void draw() { } @Override public boolean draw(PGraphics g) { draw(); return true; } public void mouseEvent(MouseEvent event) { } public void keyEvent(KeyEvent e) { } public void pre() { } public void post() { } protected void applyTransform() { winApp.translate(cx, cy); winApp.rotate(rotAngle); winApp.scale(sx, sy); } /** * This will remove all references to this control in the library. <br> * The user is responsible for nullifying all references to this control in * their sketch code. <br> * Once this method is called the control cannot be reused but resources * used by the control remain until all references to the control are set to * null. * */ public void dispose() { GUI.removeControl(this); } /** * <b>This is for emergency use only!!!! </b><br> * In this version of the library a visual controls is drawn to off-screen * buffer and then drawn to the screen by copying the buffer. This means * that the computationally expensive routines needed to draw the control * (especially text controls) are only done when a change has been noted. * This means that single changes need not trigger a full redraw to buffer. * <br> * It does mean that an error in the library code could result in the buffer * not being updated after changes. If this happens then in draw() call this * method on the affected control, and report it as an issue * <a href = 'http://code.google.com/p/gui4processing/issues/list'> * here</a><br> * Thanks */ public void forceBufferUpdate() { bufferInvalid = true; } protected HotSpot[] hotspots = null; protected int currSpot = -1; /** * Stop when we are over a hotspot. <br> * Hotspots should be listed in order of importance. * * @param px * @param py * @return the index for the first hotspot containing px,py else return -1 */ protected int whichHotSpot(float px, float py) { if (hotspots == null) { return -1; } for (int i = 0; i < hotspots.length; i++) { if (hotspots[i].contains(px, py)) { return hotspots[i].id; } } return -1; } protected int getCurrHotSpot() { return currSpot; } /** * Determines if a particular pixel position is over the panel. * * @return true if the position is over. */ public boolean isOver(float x, float y) { calcTransformedOrigin(winApp.getCursorX(), winApp.getCursorY()); currSpot = whichHotSpot(ox, oy); return (currSpot >= 0); } /** * Set the local colour scheme for this control. Children are ignored. * * @param cs the colour scheme to use */ public void setLocalColorScheme(int cs) { cs = Math.abs(cs) % 16; // Force into valid range if (localColorScheme != cs || palette == null) { localColorScheme = cs; palette = GCScheme.getColor(localColorScheme); jpalette = GCScheme.getJavaColor(localColorScheme); bufferInvalid = true; } } /** * Set the local colour scheme for this control. Children are ignored. If * required include the children and their children. * * @param cs the colour scheme to use * @param includeChildren if do do the same for all descendants */ public void setLocalColorScheme(int cs, boolean includeChildren) { cs = Math.abs(cs) % 16; // Force into valid range if (localColorScheme != cs || palette == null) { localColorScheme = cs; palette = GCScheme.getColor(localColorScheme); jpalette = GCScheme.getJavaColor(localColorScheme); bufferInvalid = true; if (includeChildren && children != null) { for (GControl c : children) { c.setLocalColorScheme(cs, true); } } } } /** * Get the local color scheme ID number. * */ public int getLocalColorScheme() { return localColorScheme; } /** * Set the transparency of the component and make it unavailable to mouse * and keyboard events if below the threshold. Child controls are ignored? * * @param alpha value in the range 0 (transparent) to 255 (opaque) */ public void setAlpha(int alpha) { alpha = Math.abs(alpha) % 256; if (alphaLevel != alpha) { alphaLevel = alpha; available = (alphaLevel >= ALPHA_BLOCK); bufferInvalid = true; } } /** * Set the transparency of the component and make it unavailable to mouse * and keyboard events if below the threshold. Child controls are ignored? * <br> * If required include the children and their children. * * @param alpha value in the range 0 (transparent) to 255 (opaque) * @param includeChildren if do do the same for all descendants */ public void setAlpha(int alpha, boolean includeChildren) { setAlpha(alpha); if (includeChildren && children != null) { for (GControl c : children) { c.setAlpha(alpha, true); } } } /** * Get the parent control. If null then this is a top-level component */ public GControl getParent() { return parent; } /** * Get the PApplet that manages this component */ public PApplet getPApplet() { return winApp; } // Used by composite control i.e. ones that have scrollbars, buttons etc. but not // GWindow and GPanel protected PGraphics getBuffer() { return buffer; } /** * Support UTF8 encoding * * @param ascii UTF8 code * @return true if the character can be displayed */ protected boolean isDisplayable(int ascii) { return !(ascii < 32 || ascii == 127); } /** * This method should be used sparingly since it is heavy on resources. * * @return a PGraphics object showing current state of the control (ignoring * rotation) */ public PGraphics getSnapshot() { if (buffer != null) { updateBuffer(); PGraphicsJava2D snap = (PGraphicsJava2D) winApp.createGraphics(buffer.width, buffer.height, PApplet.JAVA2D); snap.beginDraw(); snap.image(buffer, 0, 0); return snap; } return null; } /* * Empty method at the moment make abstract * in final version */ protected void updateBuffer() { } /** * Attempt to create the default event handler for the component class. The * default event handler is a method that returns void and has a single * parameter of the same type as the component class generating the event * and a method name specific for that class. * * @param handlerObj the object to handle the event * @param methodName the method to execute in the object handler class * @param param_classes the parameter classes. * @param param_names that names of the parameters (used for error messages * only) */ @SuppressWarnings("rawtypes") protected void createEventHandler(Object handlerObj, String methodName, Class[] param_classes, String[] param_names) { try { eventHandlerMethod = handlerObj.getClass().getMethod(methodName, param_classes); eventHandlerObject = handlerObj; eventHandlerMethodName = methodName; } catch (Exception e) { GMessenger.message(MISSING, new Object[]{this, methodName, param_classes, param_names}); eventHandlerObject = null; } } /** * Attempt to create the default event handler for the component class. The * default event handler is a method that returns void and has a single * parameter of the same type as the component class generating the event * and a method name specific for that class. * * @param obj the object to handle the event * @param methodName the method to execute in the object handler class */ public void addEventHandler(Object obj, String methodName) { try { eventHandlerObject = obj; eventHandlerMethodName = methodName; eventHandlerMethod = obj.getClass().getMethod(methodName, new Class<?>[]{this.getClass(), GEvent.class}); } catch (Exception e) { GMessenger.message(NONEXISTANT, new Object[]{this, methodName, new Class<?>[]{this.getClass(), GEvent.class}}); eventHandlerObject = null; eventHandlerMethodName = ""; } } /** * Attempt to fire an event for this component. * * The method called must have a single parameter which is the object firing * the event. If the method to be called is to have different parameters * then it should be overridden in the child class The method */ protected void fireEvent(Object... objects) { if (eventHandlerMethod != null) { try { eventHandlerMethod.invoke(eventHandlerObject, objects); } catch (Exception e) { GMessenger.message(EXCP_IN_HANDLER, new Object[]{eventHandlerObject, eventHandlerMethodName, e}); } } } /** * Set the rotation to apply when displaying this control. The center of * rotation is determined by the control_mode attribute. * * @param angle clockwise angle in radians */ public void setRotation(float angle) { setRotation(angle, GUI.control_mode); } /** * Set the rotation to apply when displaying this control. The center of * rotation is determined by the mode parameter parameter. * * @param angle clockwise angle in radians * @param mode PApplet.CORNER / CORNERS / CENTER */ public void setRotation(float angle, GControlMode mode) { rotAngle = angle; AffineTransform aff = new AffineTransform(); aff.setToRotation(angle); switch (mode) { case CORNER: case CORNERS: // Rotate about top corner temp[0] = halfWidth; temp[1] = halfHeight; aff.transform(temp, 0, temp, 0, 1); cx = (float) temp[0] + x;// - halfWidth; cy = (float) temp[1] + y;// - halfHeight; break; case CENTER: default: // Rotate about centre temp[0] = -halfWidth; temp[1] = -halfHeight; aff.transform(temp, 0, temp, 0, 1); x = cx + (float) temp[0]; y = cy + (float) temp[1]; // should this be minus?? I don't think so break; } } /** * Move the control to the given position based on the mode. <br> * * The position is not constrained to the screen area. <br> * * The current control mode determines whether we move the corner or the * center of the control to px,py <br> * * @param px the horizontal position to move to * @param py the vertical position to move to */ public void moveTo(float px, float py) { moveTo(px, py, GUI.control_mode); } /** * Move the control to the given position based on the mode. <br> * * Unlike when dragged the position is not constrained to the screen area. * <br> * * The mode determines whether we move the corner or the center of the * control to px,py <br> * * @param px the horizontal position to move to * @param py the vertical position to move to * @param mode the control mode */ public void moveTo(float px, float py, GControlMode mode) { GControl p = parent; if (p != null) { px -= p.width / 2.0f; py -= p.height / 2.0f; } switch (mode) { case CORNER: case CORNERS: cx += (px - x); cy += (py - y); x = cx - width / 2.0f; y = cy - height / 2.0f; break; case CENTER: cx = px; cy = py; x = cx - width / 2.0f; y = cy - height / 2.0f; break; } } /** * Get the left position of the control. <br> * If the control is on a panel then the value returned is relative to the * top-left corner of the panel otherwise it is relative to the sketch * window display. <br> * */ public float getX() { if (parent != null) { return x + parent.width / 2.0f; } else { return x; } } /** * Get the top position of the control. <br> * If the control is on a panel then the value returned is relative to the * top-left corner of the panel otherwise it is relative to the sketch * window display. <br> * */ public float getY() { if (parent != null) { return y + parent.height / 2.0f; } else { return y; } } /** * Get the centre x position of the control. <br> * If the control is on a panel then the value returned is relative to the * top-left corner of the panel otherwise it is relative to the sketch * window display. <br> */ public float getCX() { if (parent != null) { return x + (parent.width + width) / 2.0f; } else { return cx; } } /** * Get the centre y position of the control. <br> * If the control is on a panel then the value returned is relative to the * top-left corner of the panel otherwise it is relative to the sketch * window display. <br> * */ public float getCY() { if (parent != null) { return x + (parent.width + width) / 2.0f; } else { return cy; } } /** * @return the width */ public float getWidth() { return width; } /** * @return the height */ public float getHeight() { return height; } /** * * @param visible the visibility to set */ public void setVisible(boolean visible) { // If we are making it invisible and it has focus give up the focus if (!visible && focusIsWith == this) { loseFocus(null); } this.visible = visible; // Only available if alpha level is high enough if (visible) { available = (alphaLevel > ALPHA_BLOCK); } else { available = false; } // If this control has children than make them available if this control // is visible and unavailable if invisible if (children != null) { for (GControl c : children) { c.setAvailable(this.visible); } } } /** * @return the component's visibility */ public boolean isVisible() { return visible; } /** * The availability flag is used by the library code to determine whether a * control should be considered for drawing and mouse/key input. <br> * It perits an internal control that does not affect the visible and * enabled state of the control, which are set by the programmer. * * If a control and its children are made unavailable it will still be drawn * but it not respond to user input. * * @param avail */ protected void setAvailable(boolean avail) { available = avail; if (children != null) { for (GControl c : children) { c.setAvailable(avail); } } } /** * Is this control available? */ protected boolean isAvailable() { return available; } /** * Determines whether to show the back colour or not. Only applies to some * components * * @param opaque */ public void setOpaque(boolean opaque) { // Ensure that we dont't go from true >> false otherwise // it will validate an invalid buffer bufferInvalid |= (opaque != this.opaque); this.opaque = opaque; } /** * Find out if the component is opaque * * @return true if the background is visible */ public boolean isOpaque() { return opaque; } public boolean isDragging() { return dragging; } /** * Enable or disable the ability of the component to generate mouse * events.<br> * GTextField - it also controls key press events <br> * GPanel - controls whether the panel can be moved/collapsed/expanded <br> * * @param enable true to enable else false */ public void setEnabled(boolean enable) { enabled = enable; if (children != null) { for (GControl c : children) { c.setEnabled(enable); } } } /** * Is this component enabled * * @return true if the component is enabled */ public boolean isEnabled() { return enabled; } /** * Give the focus to this component but only after allowing the current * component with focus to release it gracefully. <br> * Always cancel the keyFocusIsWith irrespective of the component type. If * the component needs to retain keyFocus then override this method in that * class e.g. GCombo */ protected void takeFocus() { if (focusIsWith != null && focusIsWith != this) { focusIsWith.loseFocus(this); } focusIsWith = this; } /** * For most components there is nothing to do when they loose focus. * Override this method in classes that need to do something when they loose * focus eg TextField */ protected void loseFocus(GControl grabber) { if (cursorIsOver == this) { cursorIsOver = null; } focusIsWith = grabber; } /** * Determines whether this component is to have focus or not * * @param focus */ public void setFocus(boolean focus) { if (focus) { takeFocus(); } else { loseFocus(null); } } /** * Does this component have focus * * @return true if this component has focus else false */ public boolean hasFocus() { return (this == focusIsWith); } /** * Get the Z order value for the object with focus. */ protected static int focusObjectZ() { return (focusIsWith == null) ? -1 : focusIsWith.z; } /** * This will set the rotation of the control to angle overwriting any * previous rotation set. Then it calculates the centre position so that the * original top left corner of the control will be the position indicated by * x,y with respect to the top left corner of parent. <br> * * The added control will have its position calculated relative to the * centre of the parent control. <br> * * All overloaded methods call this one. <br> * * @param c the control to add. * @param x the leftmost or centre position depending on controlMode * @param y the topmost or centre position depending on controlMode * @param angle the rotation angle (replaces any the angle specified in * control) */ public void add(GControl c, float x, float y, float angle) { // Ignore if children are not allowed. if (children == null) { return; } c.rotAngle = angle; // In child control reset the control so it centred about the origin AffineTransform aff = new AffineTransform(); aff.setToRotation(angle); /* * The following code should result in the x,y and cx,cy coordinates of * the added control (c) added being measured relative to the centre of * this control. */ switch (GUI.control_mode) { case CORNER: case CORNERS: // Rotate about top corner c.x = x; c.y = y; c.temp[0] = c.halfWidth; c.temp[1] = c.halfHeight; aff.transform(c.temp, 0, c.temp, 0, 1); c.cx = (float) c.temp[0] + x - halfWidth; c.cy = (float) c.temp[1] + y - halfHeight; c.x = c.cx - c.halfWidth; c.y = c.cy - c.halfHeight; break; case CENTER: // Rotate about centre c.cx = x; c.cy = y; c.temp[0] = -c.halfWidth; c.temp[1] = -c.halfHeight; aff.transform(c.temp, 0, c.temp, 0, 1); c.x = c.cx + (float) c.temp[0] - halfWidth; c.y = c.cy - (float) c.temp[1] - halfHeight; c.cx -= halfWidth; c.cy -= halfHeight; break; } c.rotAngle = angle; // Add to parent c.parent = this; c.setZ(z); // Parent will now be responsible for drawing c.registeredMethods &= (ALL_METHOD - DRAW_METHOD); if (children == null) { children = new ArrayList<GControl>(); } children.add(c); Collections.sort(children, new Z_Order()); // Does the control being added have to do anything extra c.addToParent(this); } /** * Add a control at the given position with zero rotation angle. * * @param c the control to add. * @param x the leftmost or centre position depending on controlMode * @param y the topmost or centre position depending on controlMode */ public void add(GControl c, float x, float y) { if (children == null) { return; } GControl.this.add(c, x, y, 0); } /** * Add a control at the position and rotation specified in the control. * * @param c the control to add */ public void add(GControl c) { if (children == null) { return; } switch (GUI.control_mode) { case CORNER: case CORNERS: GControl.this.add(c, c.x, c.y, c.rotAngle); break; case CENTER: GControl.this.add(c, c.cx, c.cy, c.rotAngle); break; } } /** * Add several control at the position and rotation specified in each * control. * * @param controls comma separated list of controls */ public void add(GControl... controls) { if (children == null) { return; } for (GControl c : controls) { switch (GUI.control_mode) { case CORNER: case CORNERS: GControl.this.add(c, c.x, c.y, c.rotAngle); break; case CENTER: GControl.this.add(c, c.cx, c.cy, c.rotAngle); break; } } } /** * Changes that need to be made to child when added * * @param p the parent */ protected void addToParent(GControl p) { } /** * Get the shape type when the cursor is over a control * * @return shape type */ public int getCursorOver() { return cursorOver; } /** * Set the shape type to use when the cursor is over a control * * @param cursorOver the shape type to use */ public void setCursorOver(int cursorOver) { this.cursorOver = cursorOver; } /** * Get an affine transformation that is the compound of all transformations * including parents * * @param aff */ protected AffineTransform getTransform(AffineTransform aff) { if (parent != null) { aff = parent.getTransform(aff); } aff.translate(cx, cy); aff.rotate(rotAngle); aff.scale(sx, sy); return aff; } /** * This method takes a position px, py and calculates the equivalent * position [ox,oy] as if no transformations have taken place and the origin * is the top-left corner of the control. * * @param px * @param py */ protected void calcTransformedOrigin(float px, float py) { AffineTransform aff = new AffineTransform(); aff = getTransform(aff); temp[0] = px; temp[1] = py; try { aff.inverseTransform(temp, 0, temp, 0, 1); ox = (float) temp[0] + halfWidth; oy = (float) temp[1] + halfHeight; } catch (NoninvertibleTransformException e) { } } /** * Recursive function to set the priority of a component. This is used to * determine who gets focus when components overlap on the screen e.g. when * a combobo expands it might cover a button. <br> * It is used where components have childen e.g. GCombo and GPaneln It is * used when a child component is added. * * @param component * @param parentZ */ protected void setZ(int parentZ) { z += parentZ; if (children != null) { for (GControl c : children) { c.setZ(parentZ); } } } /** * If the control is permanently no longer required then call this method to * remove it and free up resources. <br> * The variable identifier used to create this control should be set to * null. <br> * For example if you want to dispose of a button called * <pre>btnDoThis</pre> then to remove the button use the statements <br> * <pre> * btnDoThis.dispose(); <br> * btnDoThis = null; <br></pre> */ public void markForDisposal() { GUI.removeControl(this); } public String toString() { if (tag == null) { return this.getClass().getSimpleName(); } else { return tag; } } /** * Comparator used for controlling the order components are drawn * * @author Peter Lager */ public static class Z_Order implements Comparator<GControl> { public int compare(GControl c1, GControl c2) { if (c1.z != c2.z) { return new Integer(c1.z).compareTo(new Integer(c2.z)); } else { return new Integer((int) -c1.y).compareTo(new Integer((int) -c2.y)); } } } // end of comparator class public void scale(float sx, float sy) { this.sx = sx; this.sy = sy; } }