/* File: DasDevicePosition.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden * Jessica Swanner * Edward E. West * * This file is part of the das2 library. * * das2 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.graph; import org.das2.DasApplication; import org.das2.graph.event.DasUpdateEvent; import org.das2.graph.event.DasUpdateListener; import java.beans.PropertyChangeEvent; import javax.swing.event.EventListenerList; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.text.ParseException; import java.util.Locale; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.components.propertyeditor.Editable; import org.das2.system.MutatorLock; import org.das2.util.DebugPropertyChangeSupport; import org.das2.util.LoggerManager; /** * DasRows and DasColumns are both DasDevidePositions that lay out the * canvas. Any object on the DasCanvas have a row and column object to indicate * the position of the object. * @author jbf */ public abstract class DasDevicePosition implements Editable, java.io.Serializable { private final static Logger logger= LoggerManager.getLogger("das2.graphics"); public static final String PROP_DMAXIMUM = "dMaximum"; public static final String PROP_DMINIMUM = "dMinimum"; public static final String PROP_EMMAXIMUM = "emMaximum"; public static final String PROP_EMMINIMUM = "emMinimum"; public static final String PROP_MAXIMUM = "maximum"; public static final String PROP_MINIMUM = "minimum"; public static final String PROP_PTMAXIMUM = "ptMaximum"; public static final String PROP_PTMINIMUM = "ptMinimum"; protected transient DasCanvas canvas; protected transient DasDevicePosition parent; private double minimum; private double maximum; private boolean isWidth; private String dasName; private transient PropertyChangeSupport propertyChangeDelegate; protected EventListenerList listenerList = new EventListenerList(); private final PropertyChangeListener canvasFontListener= new PropertyChangeListener() { @Override public void propertyChange( PropertyChangeEvent ev ) { if ( DasDevicePosition.this.emMinimum!=0 || DasDevicePosition.this.emMaximum!=0 ) { revalidate(); } } }; private final PropertyChangeListener canvasListener= new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { revalidate(); } }; private final ComponentAdapter componentAdapter= new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { revalidate(); } }; /** * create the DasDevicePosition. Typically the DasRow or DasColumn * constructor is used. * @param canvas the canvas to which the position refers. * @param isWidth true if the DasDevicePosition is a column not a row. * @param parent null or the parent to which this position is relative. * @param minimum normal position with respect to the canvas or parent if non-null. * @param maximum normal position with respect to the canvas or parent if non-null. * @param emMinimum em offset from the minimum position, in canvas font heights. * @param emMaximum em offset from the maximum position, in canvas font heights. * @param ptMinimum point offset from the minimum position, note points are the same as pixels. * @param ptMaximum point offset from the maximum position, note points are the same as pixels. */ protected DasDevicePosition( DasCanvas canvas, boolean isWidth, DasDevicePosition parent, double minimum, double maximum, double emMinimum, double emMaximum, int ptMinimum, int ptMaximum ) { if ( minimum > maximum ) { throw new IllegalArgumentException( "minimum>maximum" ); } // isNull indicates this is the NULL row or column. boolean isNull= ( canvas==null ) && ( parent==null ); if ( parent!=null ) { canvas= parent.getCanvas(); isWidth= parent.isWidth; } if ( canvas==null && ( ! isNull ) ) { throw new IllegalArgumentException("parent cannot be null"); } if ( isNull ) { logger.finest( "null canvas and null parent is allowed if you know what you are doing."); } this.canvas = canvas; this.parent= parent; this.minimum = minimum; this.maximum = maximum; this.emMinimum= emMinimum; this.emMaximum= emMaximum; this.ptMinimum= ptMinimum; this.ptMaximum= ptMaximum; this.isWidth = isWidth; this.dasName = DasApplication.getDefaultApplication().suggestNameFor(this); logger.log(Level.FINER, "ADD {0} {1} {2}", new Object[]{dasName, canvas, parent}); this.propertyChangeDelegate = new DebugPropertyChangeSupport(this); if ( parent!=null ) { parent.addPropertyChangeListener( canvasListener ); } else { if (canvas != null) { canvas.addComponentListener( componentAdapter ); canvas.addPropertyChangeListener( "font", canvasFontListener ); canvas.addDevicePosition(this); //TODO: it's interesting that this only happens for the parent devicePosition, not the kids. } } if ( !isNull ) { revalidate(); } } /** * remove the listeners so that the DasRow or DasColumn can be garbage collected. */ public void removeListeners() { logger.log(Level.FINER, "RM {0}", dasName); if ( parent!=null ) { parent.removePropertyChangeListener( canvasListener ); } else { if ( canvas!=null ) { canvas.removeComponentListener( componentAdapter ); canvas.removePropertyChangeListener( "font", canvasFontListener ); } } } /** * parse the format string into a pixel count. Convenience method. * parseFormatStr(s) will throw a parse exception and should be used to * verify strings. * @param s The string, like "5em+3pt" * @param em the em height of the font, * @param widthHeight the width or height of the dimension. * @param fail the value to return if the parsing fails. * @return the length in pixels (or points). */ public static double parseLayoutStr( String s, double em, int widthHeight, double fail ) { try { double [] r= parseLayoutStr(s); return widthHeight * r[0] + em * r[1] + r[2]; } catch ( ParseException ex ) { return fail; } } /** * Calls parseLayoutStr * @param s * @return * @deprecated use parseLayoutStr. * @throws ParseException */ public static double[] parseFormatStr( String s ) throws ParseException { return parseLayoutStr(s); } /** * parse position strings like "100%-5em+4pt" into [ npos, emoffset, pt_offset ]. * Note px is acceptable, but pt is proper. * Ems are rounded to the nearest hundredth. * Percents are returned as normal (0-1) and rounded to the nearest thousandth. * @param s string containing the position string. * @return three-element array of parsed [ npos, emoffset, pt_offset ] * @throws java.text.ParseException * @see formatFormatStr */ public static double[] parseLayoutStr( String s ) throws ParseException { double[] result= new double[] { 0, 0, 0 }; StringTokenizer tok= new StringTokenizer( s, "%emptx", true ); int pos=0; while ( tok.hasMoreTokens() ) { String ds= tok.nextToken(); pos+=ds.length(); double d= Double.parseDouble(ds); String u=null; try { u = tok.nextToken(); } catch (NoSuchElementException e) { if ( s.trim().equals("0") ) { return new double[] { 0,0,0 }; } else { throw new ParseException("missing units in format string: "+s,0); } } pos+=u.length(); if ( u.charAt(0)=='%' ) { result[0]= d/100.; } else if ( u.equals("e") ) { String s2= tok.nextToken(); if ( !s2.equals("m") ) throw new ParseException( "expected m following e",pos); pos+= s2.length(); result[1]= d; } else if ( u.equals("p") ) { String s2= tok.nextToken(); if ( !( s2.equals("t") || s2.equals("x") ) ) throw new ParseException( "expected t following p",pos); pos+= s2.length(); result[2]= d; } } result[0]= Math.round(result[0]*1000)/1000.; result[1]= Math.round(result[1]*10)/10.; return result; } /** * formats the three position specifiers efficiently. * @param arr three-element array [ npos, emoffset, pt_offset ]. * @return String like "100%-5em+4pt" * @see #parseFormatStr(java.lang.String) * @see #formatLayoutStr(org.das2.graph.DasDevicePosition, boolean) which contains repeated code. * @deprecated see formatLayoutStr( double[] arr ) */ public static String formatFormatStr( double[] arr ) { return formatLayoutStr(arr); } /** * formats the three position specifiers efficiently. * @param arr three-element array [ npos (0.0-1.0), emoffset, pt_offset ]. * @return String like "100%-5em+4pt" * @see #parseFormatStr(java.lang.String) * @see #formatLayoutStr(org.das2.graph.DasDevicePosition, boolean) which contains repeated code. */ public static String formatLayoutStr( double[] arr ) { StringBuilder buf= new StringBuilder(); if ( arr[0]!=0 ) buf.append( String.format( Locale.US, "%.2f%%", arr[0]*100 ) ); if ( buf.toString().endsWith(".00%") ) buf= buf.replace( buf.length()-4, buf.length(), "%" ); if ( arr[1]!=0 ) buf.append( String.format(Locale.US, "%+.1fem", arr[1] ) ); if ( buf.toString().endsWith(".0em") ) buf= buf.replace( buf.length()-4, buf.length(), "em" ); if ( arr[2]!=0 ) buf.append( String.format(Locale.US, "%+dpt", (int)arr[2] ) ); if ( buf.length()==0 ) buf.append("0%"); return buf.toString(); } /** * parses the layout string, which contains both the minimum and maximum * positions, and configures the row or column. Note that for rows, * 0% refers to the top of the canvas or parent row. * @param pos row or column to assign values. * @param spec string like "0%+2em,100%-5em+4pt" * @throws ParseException when the string cannot be parsed. */ public static void parseLayoutStr( DasDevicePosition pos, String spec ) throws ParseException { String[] ss= spec.split(","); double[] pmin= parseLayoutStr( ss[0] ); double[] pmax= parseLayoutStr( ss[1] ); MutatorLock lock= pos.mutatorLock(); lock.lock(); //try { pos.setMinimum(pmin[0]); pos.setEmMinimum(pmin[1]); pos.setPtMinimum((int)pmin[2]); pos.setMaximum(pmax[0]); pos.setEmMaximum(pmax[1]); pos.setPtMaximum((int)pmax[2]); //} finally { lock.unlock(); //} } /** * formats the row or column position into a string like 100%+1em,100%+2em. * @param pos the row or column * @return String like "100%+1em,100%+2em" * @see #formatFormatStr(double[]) which contains repeated code. */ public static String formatLayoutStr( DasDevicePosition pos ) { StringBuilder buf= new StringBuilder(); buf.append( formatLayoutStr( pos, true ) ); buf.append(","); buf.append( formatLayoutStr( pos, false ) ); return buf.toString(); } /** * formats the row or column position into a string like 100%-5em+4pt. * @param pos the row or column * @param min true if the minimum boundary is to be formatted, false if the maximum boundary is to be formatted. * @return String like "100%-5em+4pt" * @see #formatFormatStr(double[]) which contains repeated code. */ public static String formatLayoutStr( DasDevicePosition pos, boolean min ) { StringBuilder buf= new StringBuilder(); if ( min ) { if ( pos.getMinimum()!=0 ) buf.append( String.format( Locale.US, "%.2f%%", pos.getMinimum()*100 ) ); if ( pos.getEmMinimum()!=0 ) buf.append( String.format(Locale.US, "%+.1fem", pos.getEmMinimum() ) ); if ( pos.getPtMinimum()!=0 ) buf.append( String.format(Locale.US, "%+dpt", pos.getPtMinimum() ) ); } else { if ( pos.getMaximum()!=0 ) buf.append( String.format(Locale.US, "%.2f%%", pos.getMaximum()*100 ) ); if ( pos.getEmMaximum()!=0 ) buf.append( String.format(Locale.US, "%+.1fem", pos.getEmMaximum() ) ); if ( pos.getPtMaximum()!=0 ) buf.append( String.format(Locale.US, "%+dpt", pos.getPtMaximum() ) ); } if ( buf.length()==0 ) return "0%"; return buf.toString(); } public DasDevicePosition(DasCanvas parent, double minimum, double maximum, boolean width) { this( parent, width, null, minimum, maximum, 0., 0., 0, 0 ); } protected DasCanvas getCanvas() { return this.canvas; } /** * set the name associated with this object. * @param name the name associated with this object * @throws org.das2.DasNameException */ public void setDasName(String name) throws org.das2.DasNameException { if (name.equals(dasName)) { return; } String oldName = dasName; dasName = name; DasApplication app = canvas.getDasApplication(); if (app != null) { app.getNameContext().put(name, this); if (oldName != null) { app.getNameContext().remove(oldName); } } this.firePropertyChange("name", oldName, name); } /** * get the name associated with this object. * @return the name associated with this object. */ public String getDasName() { return dasName; } /** * returns the em size for the canvas. We define the em size as the * height of the font. * @return the em height in points. */ public int getEmSize() { return canvas.getFont().getSize(); } private int getParentMin() { if ( parent==null ) { return 0; } else { return parent.getDMinimum(); } } private int getParentMax() { if ( parent==null ) { return isWidth ? canvas.getWidth() : canvas.getHeight(); } else { return parent.getDMaximum(); } } /** * dMinimum and dMaximum are the position in pixel space. */ private int dMinimum, dMaximum; /** * recalculates dMinimum and dMaximum based on the new values, and * checks for correctness. Note if dMaximum<=dMinimum, we define * dMaximum= dMinimum+1. */ protected final void revalidate() { if ( parent!=null ) { parent.revalidate(); } minLayout= getMinLayout(); maxLayout= getMaxLayout(); int oldmin= dMinimum; int oldmax= dMaximum; dMinimum= (int)( getParentMin() + minimum*getDeviceSize() + getEmSize() * emMinimum + ptMinimum ); dMaximum= (int)( getParentMin() + maximum*getDeviceSize() + getEmSize() * emMaximum + ptMaximum ); if ( dMaximum<=dMinimum ) dMaximum= dMinimum+1; if ( dMinimum!=oldmin ) { firePropertyChange( PROP_DMINIMUM, oldmin ,dMinimum); firePropertyChange( PROP_MINLAYOUT, null, minLayout ); } if ( dMaximum!=oldmax ) { firePropertyChange( PROP_DMAXIMUM, oldmax ,dMaximum); firePropertyChange( PROP_MAXLAYOUT, null, maxLayout ); } if ( dMinimum!=oldmin || dMaximum!=oldmax ) fireUpdate(); canvas.repaint(); } /** * returns the pixel position of the minimum of the Row/Column. This is * the left side of a column and the top of a row. * @return the pixel position (pixel=point for now) */ public int getDMinimum() { if ( canvas==null && parent==null ) { String type= isWidth ? "column" : "row"; throw new RuntimeException( type+" was not set before layout, having no canvas or parent "+type ); } return dMinimum; } /** * returns the pixel position of the maximum of the Row/Column. This is * the right side of a column and the bottom of a row. * @return the pixel position (pixel=point for now) */ public int getDMaximum() { if ( canvas==null && parent==null ) { String type= isWidth ? "column" : "row"; throw new RuntimeException( type+" was not set before layout, having no canvas or parent "+type); } return dMaximum; } /** * return the normal position control of the top/left. * @return the normal position control of the top/left. */ public double getMinimum() { return minimum; } /** * return the normal position control of the bottom/right. * @return the normal position control of the bottom/right. */ public double getMaximum() { return maximum; } /** * set the new normal location of both the min and max in one operation. * @param minimum the top or left * @param maximum the bottom or right */ private void setPosition(double minimum, double maximum) { double oldMin = this.minimum; double oldMax = this.maximum; this.minimum = Math.min(minimum, maximum); this.maximum = Math.max(minimum, maximum); revalidate(); if (oldMin != this.minimum) { firePropertyChange( PROP_MINIMUM, oldMin, this.minimum); } if (oldMax != this.maximum) { firePropertyChange( PROP_MAXIMUM, oldMax, this.maximum); } } /** * set the new pixel location of both the min and max in one operation. * @param minimum the top or left * @param maximum the bottom or right */ public void setDPosition( int minimum, int maximum) { int pmin= getParentMin(); int pmax= getParentMax(); int em= getEmSize(); int length= pmax - pmin; double nmin= ( minimum - emMinimum * em - ptMinimum - pmin ) / length; double nmax= ( maximum - emMaximum * em - ptMaximum - pmin ) / length; setPosition( nmin, nmax ); } /** * set the normal position of the minimum of the row or column. * For a row, this is the bottom. For a column, this is the right side. * @param maximum normal (0-1) position */ public void setMaximum(double maximum) { if (maximum == this.maximum) { return; } if (maximum < this.minimum) { setPosition(maximum, this.minimum); } else { double oldValue = this.maximum; this.maximum = maximum; firePropertyChange( PROP_MAXIMUM, oldValue, maximum); revalidate(); } } /** * set the new pixel position of the bottom/right boundary. * em and pt offsets are not modified, and the normal position * is recalculated. * @param maximum new pixel maximum */ public void setDMaximum( int maximum) { int pmin= getParentMin(); int pmax= getParentMax(); int em= getEmSize(); int length= pmax - pmin; double n= ( maximum - emMaximum * em - ptMaximum ) / length; setMaximum( n ); } /** * set the normal position of the minimum of the row or column. * For a row, this is the top. For a column, this is the left side. * @param minimum normal (0-1) position */ public void setMinimum( double minimum) { if (minimum == this.minimum) { return; } if (minimum > this.maximum) { setPosition(this.maximum, minimum); } else { double oldValue = this.minimum; this.minimum = minimum; firePropertyChange( PROP_MINIMUM, oldValue, minimum); revalidate(); } } /** * set the new pixel position of the top/left boundary. em and pt offsets * are not modified, and the normal position is recalculated. * @param minimum new pixel minimum */ public void setDMinimum( int minimum) { int pmin= getParentMin(); int pmax= getParentMax(); int em= getEmSize(); int length= pmax - pmin; double n= ( minimum - emMinimum * em - ptMinimum ) / length; setMinimum( n ); } /** * return the parent canvas. * @return the parent canvas. */ public DasCanvas getParent() { return this.canvas; } /** * set the parent canvas. * @param parent canvas. */ public void setParent(DasCanvas parent) { this.canvas= parent; fireUpdate(); } private boolean valueIsAdjusting= false; /** * get a lock for this object, used to mutate a number of properties * as one atomic operation. * @return a lock */ protected synchronized MutatorLock mutatorLock() { return new MutatorLock() { @Override public void lock() { if ( isValueIsAdjusting() ) { System.err.println("lock is already set!"); } valueIsAdjusting= true; } @Override public void unlock() { valueIsAdjusting= false; propertyChangeDelegate.firePropertyChange( "mutatorLock", "locked", "unlocked"); } }; } /** * add an update listener * @param l update listener */ public void addUpdateListener(DasUpdateListener l) { listenerList.add(DasUpdateListener.class, l); if ( listenerList.getListenerCount()>100 ) { logger.log(Level.WARNING, "I think I found a leak in {0}", this.getDasName()); } } /** * remove an update listener * @param l update listener */ public void removeUpdateListener(DasUpdateListener l) { int n0= listenerList.getListenerCount(); listenerList.remove(DasUpdateListener.class, l); if ( n0>0 && listenerList.getListenerCount()==n0 ) { logger.fine("nothing was removed..."); } } /** * fire an update to all listeners. */ protected void fireUpdate() { DasUpdateEvent e = new DasUpdateEvent(this); Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==DasUpdateListener.class) { ((DasUpdateListener)listeners[i+1]).update(e); } } } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeDelegate.addPropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeDelegate.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeDelegate.removePropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener( PropertyChangeListener listener) { propertyChangeDelegate.removePropertyChangeListener(listener); } protected void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { firePropertyChange(propertyName, (oldValue ? Boolean.TRUE : Boolean.FALSE), (newValue ? Boolean.TRUE : Boolean.FALSE)); } protected void firePropertyChange(String propertyName, int oldValue, int newValue) { firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue) ); } protected void firePropertyChange(String propertyName, long oldValue, long newValue) { firePropertyChange(propertyName, Long.valueOf(oldValue), Long.valueOf(newValue) ); } protected void firePropertyChange(String propertyName, float oldValue, float newValue) { firePropertyChange(propertyName, new Float(oldValue), new Float(newValue)); } protected void firePropertyChange(String propertyName, double oldValue, double newValue) { firePropertyChange(propertyName, new Double(oldValue), new Double(newValue)); } protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { propertyChangeDelegate.firePropertyChange(propertyName, oldValue, newValue); } /** * return the size in pixels (or points) * @return the size in pixels (or points) */ protected int getDeviceSize() { return getParentMax() - getParentMin(); } /** * convenience method for creating a rectangle from a row and column. * The rectangle will be in canvas pixel coordinates. * @param row row describing the top and bottom of the box. * @param column column describing the left and right sides of the box. * @return rectangle in canvas pixel coordinates. */ public static java.awt.Rectangle toRectangle(DasRow row, DasColumn column) { int xmin=column.getDMinimum(); int ymin=row.getDMinimum(); return new java.awt.Rectangle(xmin,ymin, column.getDMaximum()-xmin, row.getDMaximum()-ymin); } /** * return a human-readable string representing the object for debugging. * @return a human-readable string representing the object for debugging. */ @Override public String toString() { //String format="%.1f%%%+.1fem%+dpt"; //String smin= String.format(format, minimum*100, emMinimum, ptMinimum ); //String smax= String.format(format, maximum*100, emMaximum, ptMaximum ); String t= getClass().getSimpleName(); if ( canvas==null && parent==null ) { return t + " " + getDasName() + " unattached"; } else { return t + " " + getDasName() + " " + formatLayoutStr(this, true) + "," +formatLayoutStr(this, false) + " [dpos=" + getDMinimum() + "," + getDMaximum() + "]"; } } /** * returns true if ( getDMinimum() <= x ) && ( x <= getDMaximum() ); * @param x the pixel position * @return true if ( getDMinimum() <= x ) && ( x <= getDMaximum() ); */ public boolean contains( int x ) { return ( getDMinimum() <= x ) && ( x <= getDMaximum() ); } /** * returns pixel position (device position) of the the middle of the row or column * @return pixel position (device position) of the the middle of the row or column */ public int getDMiddle() { return (getDMinimum()+getDMaximum())/2; } /** * property emMinimum, the em (font height * 2/3) offset from the minimum */ private double emMinimum; /** * return the em offset that controls the position of the top/left boundary. * @return the em offset that controls the position of the top/left boundary. */ public double getEmMinimum() { return this.emMinimum; } /** * set the em offset that controls the position of the top/left boundary. * @param emMinimum the em offset. */ public void setEmMinimum(double emMinimum) { double oldValue= this.emMinimum; this.emMinimum = emMinimum; firePropertyChange( PROP_EMMINIMUM, oldValue, emMinimum); if ( oldValue!=emMinimum ) { firePropertyChange( PROP_MINLAYOUT, minLayout, getMinLayout() ); } revalidate(); } /** * property emMaximum, the em (font height) offset from the maximum */ private double emMaximum; /** * return the em offset that controls the position of the bottom/right boundary. * @return the em offset that controls the position of the bottom/right boundary. */ public double getEmMaximum() { return this.emMaximum; } /** * set the em offset that controls the position of the bottom/right boundary. * @param emMaximum the em offset. */ public void setEmMaximum(double emMaximum) { double oldValue= this.emMaximum; this.emMaximum = emMaximum; firePropertyChange( PROP_EMMAXIMUM, oldValue, emMaximum); if ( oldValue!=emMaximum ) { firePropertyChange( PROP_MAXLAYOUT, maxLayout, getMaxLayout() ); } revalidate(); } private int ptMinimum; /** * return the points offset that controls the position of the top/left boundary. * @return the points offset */ public int getPtMinimum() { return this.ptMinimum; } /** * set the points offset that controls the position of the top/left boundary. * @param ptMinimum the points offset */ public void setPtMinimum(int ptMinimum) { int oldValue= this.ptMinimum; this.ptMinimum = ptMinimum; firePropertyChange( PROP_PTMINIMUM, oldValue, ptMinimum); if ( oldValue!=ptMinimum ) { firePropertyChange( PROP_MINLAYOUT, minLayout, getMinLayout() ); } revalidate(); } /** * property ptMaximum, the pixel offset from the maximum */ private int ptMaximum=0; /** * return the points offset that controls the position of the bottom/right boundary. * @return the points offset that controls the position of the bottom/right boundary. */ public int getPtMaximum() { return this.ptMaximum; } /** * set the pt offset that controls the position of the bottom/right boundary. * @param ptMaximum the em offset. */ public void setPtMaximum(int ptMaximum) { int oldValue= this.ptMaximum; this.ptMaximum = ptMaximum; firePropertyChange( PROP_PTMAXIMUM, oldValue, ptMaximum); if ( oldValue!=ptMaximum ) { firePropertyChange( PROP_MAXLAYOUT, maxLayout, getMaxLayout() ); } revalidate(); } /** * set all three as one atomic operation * @param norm normal position from 0 to 1. * @param em em offset from the normal position. * @param pt points offset from the normal position. */ public void setMin( double norm, double em, int pt ) { double[] old= new double[ ] { this.minimum, this.emMinimum, this.ptMinimum }; this.minimum= norm; this.emMinimum= em; this.ptMinimum= pt; firePropertyChange(PROP_PTMINIMUM, old[2], pt ); firePropertyChange(PROP_EMMINIMUM, old[1], em ); firePropertyChange(PROP_MINIMUM, old[0], norm ); firePropertyChange(PROP_MINLAYOUT, minLayout, getMinLayout() ); revalidate(); } /** * set all three as one atomic operation * @param norm normal position from 0 to 1. * @param em em offset from the normal position. * @param pt points offset from the normal position. */ public void setMax( double norm, double em, int pt ) { double[] old= new double[ ] { this.maximum, this.emMaximum, this.ptMaximum }; this.maximum= norm; this.emMaximum= em; this.ptMaximum= pt; firePropertyChange(PROP_PTMAXIMUM, old[2], pt ); firePropertyChange(PROP_EMMAXIMUM, old[1], em ); firePropertyChange(PROP_MAXIMUM, old[0], norm ); firePropertyChange(PROP_MAXLAYOUT, maxLayout, getMaxLayout() ); revalidate(); } private String maxLayout=""; public static final String PROP_MAXLAYOUT = "maxLayout"; public String getMaxLayout() { String layout= formatLayoutStr(this,false); return layout; } public void setMaxLayout(String maxLayout) { String oldMinLayout = getMaxLayout(); try { double[] dd= parseLayoutStr(maxLayout); setMax( dd[0], dd[1], (int)dd[2] ); this.maxLayout= getMaxLayout(); } catch (ParseException ex) { return; } propertyChangeDelegate.firePropertyChange(PROP_MAXLAYOUT, oldMinLayout, maxLayout); } private String minLayout=""; public static final String PROP_MINLAYOUT = "minLayout"; public String getMinLayout() { String layout= formatLayoutStr(this,true); return layout; } public void setMinLayout(String minLayout) { String oldMinLayout = getMinLayout(); try { double[] dd= parseLayoutStr(minLayout); setMin( dd[0], dd[1], (int)dd[2] ); this.minLayout= getMinLayout(); } catch (ParseException ex) { return; } propertyChangeDelegate.firePropertyChange(PROP_MINLAYOUT, oldMinLayout, minLayout); } /** * return the parent, or null. If parent is non-null, then position is * relative to the parent. * @return the parent, or null. */ public DasDevicePosition getParentDevicePosition() { return this.parent; } /** * return true if the value is currently adjusting because a * mutator lock is out. * @return true if the value is currently adjusting. */ public boolean isValueIsAdjusting() { return valueIsAdjusting; } }