/******************************************************************************* * Copyright (c) 2007, 2008 Gregory Jordan * * This file is part of PhyloWidget. * * PhyloWidget 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. * * PhyloWidget 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 * PhyloWidget. If not, see <http://www.gnu.org/licenses/>. */ package org.andrewberman.ui.menu; import java.awt.Cursor; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.DecimalFormat; import org.andrewberman.ui.Point; import org.andrewberman.ui.UIUtils; import processing.core.PApplet; import processing.core.PFont; public class NumberScroller extends MenuItem { static RoundRectangle2D.Float buffRoundRect = new RoundRectangle2D.Float(0, 0, 0, 0, 0, 0); private DecimalFormat df; boolean customFormat = false; private Field field; private Method method; private Object fieldObj; public boolean allowPrecision = true; static float NaN = Float.NaN; protected float min = -Float.MAX_VALUE, max = Float.MAX_VALUE; boolean scrolling; float startY, startVal; // private int minDigits, maxDigits; private String stringValue; private float tWidth, nWidth, nOffset; private boolean useReflection; private boolean useMethod; private float value, defaultValue, increment, scrollSpeed; public NumberScroller() { super(); df = new DecimalFormat("#######0.0#"); df.setDecimalSeparatorAlwaysShown(false); value = 0; defaultValue = NaN; increment = 1; scrollSpeed = 1; setIncrement(increment); setValue(value); setScrollSpeed(scrollSpeed); stringValue = new String(); } public void setFormat(String f) { df = new DecimalFormat(f); df.setDecimalSeparatorAlwaysShown(false); int ind = f.indexOf("."); if (ind > -1) { int digCount = f.length() - ind - 1; df.setMinimumFractionDigits(digCount); df.setMaximumFractionDigits(digCount); } customFormat = true; updateString(); } protected void calcPreferredSize() { super.calcPreferredSize(); PFont font = menu.getStyle().getFont("font"); float fs = menu.getStyle().getF("f.fontSize"); float px = menu.getStyle().getF("f.padX"); float py = menu.getStyle().getF("f.padY"); /* * For the height, let's use the height of some capital letters. */ float tHeight = UIUtils.getTextHeight(menu.buff, font, fs, "XYZ", true); /* * Calculate the text rectangle size. */ if (getName().length() > 0) { tWidth = UIUtils.getTextWidth(menu.buff, font, fs, getName() + ":", true); tWidth += px; } String s = stringValue; /* * Store the beginning point for the number area. */ nWidth = 0; nWidth += UIUtils.getTextWidth(menu.buff, font, fs, s, true); nWidth += 2 * px; nOffset = getWidth() - px - nWidth; setWidth(px + tWidth + nWidth + px); setHeight(tHeight + 2 * py); } protected boolean containsPoint(Point p) { if (scrolling) return true; float ro = menu.getStyle().getF("f.roundOff"); buffRoundRect.setRoundRect(x, y, width, height, ro, ro); // buffRoundRect.setRoundRect(x + nOffset, y, nWidth, height, // menu.getStyle().roundOff, menu.getStyle().roundOff); return buffRoundRect.contains(p); } protected void drawMyself() { super.drawMyself(); getValue(); if (scrolling) { /* * Cause the menu to re-layout in case we've changed preferred size. */ menu.layout(); } float px = menu.getStyle().getF("f.padX"); float py = menu.getStyle().getF("f.padY"); float curX = x + px; MenuUtils.drawLeftText(this, getName() + ":", curX); curX += tWidth; curX = getX() + getWidth() - px - nWidth; if (shouldPerformFill()) MenuUtils.drawSingleGradientRect(this, curX, y, nWidth, height); /* * update the "value" object using Reflection. */ MenuUtils.drawText(this, stringValue, true, true, curX, y, nWidth, height); } protected void getRect(Rectangle2D.Float rect, Rectangle2D.Float buff) { buff.setFrame(x, y, width, height); Rectangle2D.union(rect, buff, rect); super.getRect(rect, buff); } public float getValue() { float oldValue = value; try { if (useReflection) { try { value = field.getFloat(fieldObj); } catch (Exception e) { try { value = parseValueFromString(field.get(fieldObj)); } catch (Exception e2) { e2.printStackTrace(); System.err.println(e.getMessage()); } } } } catch (Exception e) { useReflection = false; e.printStackTrace(); } if (value != oldValue) updateString(); return value; } protected float parseValueFromString(Object s) { if (s == null || s.equals("")) return 0; return Float.parseFloat(s.toString()); } // This is bad programming! Do as I say, not as I do!! // This is here so subclasses can directly get the current value, without // going through the reflection nonsense. Maybe I could get rid of it? protected float getValueDirectly() { return value; } public void performAction() { // super.performAction(); } public void setDefault(float def) { defaultValue = def; setValue(defaultValue); } public void setIncrement(float inc) { increment = inc; /* * Try and auto-detect number of decimal places from the increment. */ int numDecimals = (int) Math.ceil((float) -Math.log10(increment)); // System.out.println(Math.log10(increment)+ " "+getName() + " " + // increment + " " + numDecimals); if (!customFormat) { df.setMinimumFractionDigits(numDecimals); df.setMaximumFractionDigits(numDecimals); } setValue(getValue()); } public void setMax(float max) { this.max = max; } public void setMin(float min) { this.min = min; } public void setProperty(Object obj, String prop) { try { String setProp = "set" + MenuIO.upperFirst(prop); method = obj.getClass().getMethod(setProp, Float.TYPE); useMethod = true; } catch (Exception e) { useMethod = false; } try { field = obj.getClass().getField(prop); fieldObj = obj; useReflection = true; // System.out.println(field.getFloat(fieldObj)); } catch (Exception e) { // e.printStackTrace(); field = null; fieldObj = null; useReflection = false; throw new RuntimeException(); } if (Float.isNaN(defaultValue)) { setDefault(getValue()); } if (useReflection) { try { field.setFloat(fieldObj, getValue()); } catch (Exception e) { // Try setting the String value. String s = getStringValueForNumber(value); try { field.set(fieldObj, s); } catch (Exception e2) { e.printStackTrace(); e2.printStackTrace(); } } } else { setValue(defaultValue); } } public void setScrollSpeed(float changePerPixel) { scrollSpeed = changePerPixel; } public void setValue(float val) { float oldValue = value; value = PApplet.constrain(val, min, max); if (useReflection) { if (useMethod) { try { method.invoke(fieldObj, value); } catch (Exception e) { e.printStackTrace(); } } else { try { field.setFloat(fieldObj, value); } catch (Exception e) { // Try setting the String value. String s = getStringValueForNumber(value); try { field.set(fieldObj, s); } catch (Exception e2) { e.printStackTrace(); e2.printStackTrace(); } } } } updateString(); } void updateString() { stringValue = getStringValueForNumber(value); } protected String getStringValueForNumber(float value) { return df.format(value); } private boolean controlDown = false; protected void visibleMouseEvent(MouseEvent e, Point tempPt) { super.visibleMouseEvent(e, tempPt); if (!isEnabled()) return; float curInterval = increment; if (e.isControlDown() != controlDown) { controlDown = e.isControlDown(); startY = tempPt.y; startVal = getValue(); } if (e.isControlDown()) { curInterval /= 5f; } if (mouseInside) { menu.setCursor(Cursor.N_RESIZE_CURSOR); } switch (e.getID()) { case (MouseEvent.MOUSE_PRESSED): if (mouseInside) { if (e.getClickCount() > 1) { setValue(defaultValue); } startY = tempPt.y; startVal = getValue(); scrolling = true; nearestMenu.context.focus().setModalFocus(this.menu); } break; case (MouseEvent.MOUSE_DRAGGED): if (scrolling) { float dy = startY - tempPt.y; float dVal = dy * curInterval * scrollSpeed; value = startVal + dVal; setValue(value); e.consume(); } break; case (MouseEvent.MOUSE_RELEASED): if (scrolling) { e.consume(); scrolling = false; nearestMenu.context.focus().removeFromFocus(this.menu); } break; } } public boolean isAllowPrecision() { return allowPrecision; } public void setAllowPrecision(String allowPrecision) { if (allowPrecision.equalsIgnoreCase("true") || allowPrecision.toLowerCase().startsWith("y")) this.allowPrecision = true; else this.allowPrecision = false; } }