/* * ParamField.java * (FScape) * * Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.fscape.gui; import de.sciss.app.BasicEvent; import de.sciss.app.EventManager; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.gui.Jog; import de.sciss.gui.NumberEvent; import de.sciss.gui.NumberField; import de.sciss.gui.NumberListener; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * GUI element for numeric input * including a mouse controlled dial * wheel and optional unit selector * with automatic value conversion. */ public class ParamField extends JPanel implements EventManager.Processor { // -------- public variables -------- // -------- private variables -------- private static final String dimTxt[] = { "", "", "ms", "Hz", "\u00B0" }; private static final String specialTxt[] = { "", "beats", "semi", "dB" }; private static final String percentTxt = "%"; private static final String relTxt = "\u00B1"; private GridBagLayout lay; private GridBagConstraints con; private NumberField ggNumber; private final Jog ggJog; private JComboBox ggUnits; private JLabel lbUnits; private final EventManager elm = new EventManager( this ); // private Param para; private Param reference = null; // static reference value private ParamField refField = null; // dynamic reference field protected ParamSpace spaces[]; // e.g. Constants.spaces[ ... ] private int currentSpaceIdx; // ArrayIndex in spaces[] private ParamSpace currentSpace; // -------- public methods -------- public ParamField() { super(); final Color c1 = getForeground (); final Color c2 = getBackground (); spaces = new ParamSpace[ 1 ]; spaces[ 0 ] = Constants.spaces[ Constants.emptySpace ]; currentSpaceIdx = 0; currentSpace = spaces[ currentSpaceIdx ]; lay = new GridBagLayout(); con = new GridBagConstraints(); ggNumber = new NumberField(); ggNumber.setSpace( currentSpace ); ggJog = new Jog(); ggUnits = new JComboBox(); lbUnits = new JLabel(); ggJog.addListener( new NumberListener() { public void numberChanged( NumberEvent e ) { if( currentSpace != null ) { final double inc = e.getNumber().doubleValue() * currentSpace.inc; final Number num = ggNumber.getNumber(); final Number newNum; boolean changed; if( currentSpace.isInteger() ) { newNum = new Long( (long) currentSpace.fitValue( num.longValue() + inc )); } else { newNum = new Double( currentSpace.fitValue( num.doubleValue() + inc )); } changed = !newNum.equals( num ); if( changed ) { ggNumber.setNumber( newNum ); } if( changed || !e.isAdjusting() ) { fireValueChanged( e.isAdjusting() ); } } } }); setLayout( lay ); con.anchor = GridBagConstraints.WEST; con.fill = GridBagConstraints.HORIZONTAL; con.gridwidth = 1; con.gridheight = 2; con.gridx = 0; con.gridy = 0; con.weighty = 1.0; lay.setConstraints( ggJog, con ); ggJog.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 2 )); add( ggJog ); con.gridheight = 1; con.gridx += 1; con.gridy = 1; con.weightx = 1.0; con.weighty = 0.0; lay.setConstraints( ggNumber, con ); ggNumber.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { dispatchChange(); } }); // High-Level Events: forward Return-Hit add( ggNumber ); con.gridx += 1; con.weightx = 0.0; con.gridwidth = GridBagConstraints.REMAINDER; lay.setConstraints( ggUnits, con ); lay.setConstraints( lbUnits, con ); add(ggUnits); ggUnits.setVisible(false); ggUnits.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final int newSpace = ggUnits.getSelectedIndex(); if (newSpace != currentSpaceIdx) { // transform final Param tempPara, newParam; final Param oldParam = getParam(); tempPara = Param.transform(oldParam, spaces[newSpace].unit, (refField == null) ? reference : refField.makeReference(), null); // apply new values newParam = new Param(spaces[newSpace].fitValue( (tempPara == null) ? oldParam.value : tempPara.value), spaces[newSpace].unit); currentSpaceIdx = newSpace; currentSpace = spaces[currentSpaceIdx]; ggNumber.setSpace(currentSpace); ggNumber.setNumber(paraToNumber(newParam)); // inform Listener dispatchChange(); } } }); add(lbUnits); } /** * Single Unit version */ public ParamField(ParamSpace space) { this(); ParamSpace spaces[] = new ParamSpace[1]; spaces[0] = space; setSpaces(spaces); } /** * Multi Unit version */ public ParamField(ParamSpace spaces[]) { this(); setSpaces(spaces); } protected Number paraToNumber(Param newParam) { final Number newNum; if (currentSpace.isInteger()) { newNum = new Long((long) newParam.value); } else { newNum = new Double(newParam.value); } return newNum; } /** * Changes the available units */ public void setSpaces(ParamSpace spaces[]) { int oldNum = this.spaces.length; // previous number of units this.spaces = spaces; currentSpaceIdx = Math.min( spaces.length - 1, currentSpaceIdx ); currentSpace = spaces[ currentSpaceIdx ]; ggUnits.removeAllItems(); if (spaces.length > 1) { for (int i = 0; i < spaces.length; i++) { ggUnits.addItem(getUnitText(spaces[i].unit)); } if (oldNum == 1) { // was JLabel? than change JLabel for JComboBox lbUnits.setVisible(false); ggUnits.setVisible(true); } } else { // ...als JLabel lbUnits.setText(getUnitText(currentSpace.unit)); if (oldNum != 1) { // was JComboBox? then change JComboBox for JLabel ggUnits.setVisible(false); lbUnits.setVisible(true); } } ggNumber.setSpace(currentSpace); } /** * Retrieves the current unit */ public ParamSpace getSpace() { return currentSpace; } /** * Sets the parameter to a new value (which might be converted in the process) */ public void setParam(Param newParam) { final Number oldNum = ggNumber.getNumber(); final Number newNum; int newSpace = 0; int pri; final boolean spaceChange; for (int i = 0, bestPri = -99; i < spaces.length; i++) { pri = Param.getPriority(newParam.unit, spaces[i].unit); if (pri > bestPri) { bestPri = pri; newSpace = i; } } spaceChange = currentSpaceIdx != newSpace; if (spaceChange) { currentSpaceIdx = newSpace; currentSpace = spaces[currentSpaceIdx]; ggNumber.setSpace(currentSpace); } if (currentSpace.isInteger()) { newNum = new Long((long) newParam.value); } else { newNum = new Double(newParam.value); } if (spaces.length > 1) { ggUnits.setSelectedIndex(currentSpaceIdx); // choose JComboBox } if (spaceChange || !newNum.equals(oldNum)) ggNumber.setNumber(newNum); } /** * Sets a reference parameter which the * ParamField can use as orientation if the user * chooses a different unit. * (cf. Param.transform()) */ public void setReference( Param reference ) { this.reference = reference; } /** * Selects a second ParamField as reference. * This ParamField will use the reference when * the user chooses a different unit. * (cf. Param.transform()) * * This field has higher priority than a value * specified through <code>setReference(Param)</code>! */ public void setReference( ParamField refField ) { this.refField = refField; } /** * Creates a reference value with <code>ABS_UNIT</code> which * can be used by another <code>Param(Field)</code> als a <code>Param.transform()</code> reference. * * @return possibly <code>null</code>! */ public Param makeReference() { int destUnit = (currentSpace.unit & ~Param.FORM_MASK) | Param.ABS_UNIT; Param tempPara; final Param oldParam = getParam(); tempPara = Param.transform(oldParam, destUnit, (refField == null) ? reference : refField.makeReference(), null); if (tempPara == oldParam) { return (Param) tempPara.clone(); // since this.para is non-"static" } else { return tempPara; } } public Param getParam() { return new Param(ggNumber.getNumber().doubleValue(), currentSpace == null ? Param.NONE : currentSpace.unit); } public void addParamListener( ParamListener li ) { elm.addListener( li ); } public void removeParamListener( ParamListener li ) { elm.removeListener( li ); } protected void fireValueChanged(boolean adjusting) { dispatchChange(); } public void processEvent( BasicEvent e ) { ParamListener listener; for( int i = 0; i < elm.countListeners(); i++ ) { listener = (ParamListener) elm.getListener( i ); switch( e.getID() ) { case ParamEvent.CHANGED: listener.paramChanged( (ParamEvent) e ); break; default: assert false : e.getID(); } } // for( i = 0; i < elm.countListeners(); i++ ) } protected void dispatchChange() { elm.dispatchEvent(new ParamEvent(this, ParamEvent.CHANGED, System.currentTimeMillis(), getParam(), getSpace())); } public void setEnabled(boolean state) { if (!state) { ggJog.requestFocus(); // tricky ggNumber.looseFocus() ;) } ggNumber.setEnabled(state); ggJog.setEnabled(state); if (ggUnits.isVisible()) { ggUnits.setEnabled(state); } if (state) { ggNumber.requestFocus(); } } /* * Determines string representation of the unit */ private String getUnitText(int unit) { String unitTxt = specialTxt[ (unit & Param.SPECIAL_MASK) >> 8 ]; switch( unit & Param.FORM_MASK ) { case Param.ABS_UNIT: if( unitTxt.length() == 0 ) { unitTxt = dimTxt[ unit & Param.DIM_MASK ]; } break; case Param.ABS_PERCENT: unitTxt = ((unitTxt.length() == 0) ? percentTxt : unitTxt); break; case Param.REL_UNIT: unitTxt = relTxt + ' ' + ((unitTxt.length() == 0) ? dimTxt[ unit & Param.DIM_MASK ] : unitTxt); break; case Param.REL_PERCENT: unitTxt = relTxt + ' ' + ((unitTxt.length() == 0) ? percentTxt : unitTxt); break; default: break; } return unitTxt; } }