001    /*
002     * $Id: MemoryOption.java,v 1.9 2012/02/19 17:35:49 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.startupmanager.options;
032    
033    import java.awt.Color;
034    import java.awt.event.ActionEvent;
035    import java.awt.event.ActionListener;
036    import java.awt.event.KeyAdapter;
037    import java.awt.event.KeyEvent;
038    import java.util.regex.Matcher;
039    import java.util.regex.Pattern;
040    
041    import javax.swing.ButtonGroup;
042    import javax.swing.JComboBox;
043    import javax.swing.JComponent;
044    import javax.swing.JLabel;
045    import javax.swing.JPanel;
046    import javax.swing.JRadioButton;
047    import javax.swing.JSlider;
048    import javax.swing.JTextField;
049    import javax.swing.event.ChangeEvent;
050    import javax.swing.event.ChangeListener;
051    
052    import ucar.unidata.util.GuiUtils;
053    
054    import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
055    import edu.wisc.ssec.mcidasv.util.McVTextField;
056    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
057    import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.OptionPlatform;
058    import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Type;
059    import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Visibility;
060    
061    public class MemoryOption extends AbstractOption implements ActionListener {
062        public enum Prefix {
063            MEGA("M", "megabytes"),
064            GIGA("G", "gigabytes"),
065            TERA("T", "terabytes"),
066            PERCENT("P", "percent");
067    
068            private final String javaChar;
069            private final String name;
070    
071            private Prefix(final String javaChar, final String name) {
072                this.javaChar = javaChar;
073                this.name = name;
074            }
075    
076            public String getJavaChar() {
077                return javaChar.toUpperCase();
078            }
079    
080            public String getName() {
081                return name;
082            }
083    
084            public String getJavaFormat(final String value) {
085                long longVal = Long.parseLong(value);
086                return longVal + javaChar;
087            }
088    
089            @Override public String toString() {
090                return name;
091            }
092        }
093    
094        private enum State { 
095            VALID(Color.BLACK, Color.WHITE),
096            WARN(Color.BLACK, new Color(255, 255, 204)),
097            ERROR(Color.WHITE, Color.PINK);
098    
099            private final Color foreground;
100            private final Color background;
101    
102            private State(final Color foreground, final Color background) {
103                this.foreground = foreground;
104                this.background = background;
105            }
106            
107            public Color getForeground() { return foreground; }
108            public Color getBackground() { return background; }
109        }
110    
111        private final static Prefix[] PREFIXES = { Prefix.MEGA, Prefix.GIGA, Prefix.TERA };
112        private Prefix currentPrefix = Prefix.MEGA;
113    
114        private static final Pattern MEMSTRING = 
115            Pattern.compile("^(\\d+)([M|G|T|P]?)$", Pattern.CASE_INSENSITIVE);
116    
117        private final String defaultPrefValue;
118        private String failsafeValue = "512M";
119        private String value = failsafeValue; // bootstrap
120    
121        private JRadioButton jrbSlider = new JRadioButton();
122        private JRadioButton jrbNumber = new JRadioButton();
123        private ButtonGroup jtbBg = GuiUtils.buttonGroup(jrbSlider, jrbNumber);
124    
125        private JPanel sliderPanel = new JPanel();
126        private JLabel sliderLabel = new JLabel();
127        private JSlider slider = new JSlider();
128    
129        private JPanel textPanel = new JPanel();
130        private McVTextField text = new McVTextField();
131        private JComboBox memVals = new JComboBox(PREFIXES);
132        private String initTextValue = value;
133        private Prefix initPrefixValue = currentPrefix;
134    
135        private int minSliderValue = 10;
136        private int maxSliderValue = 80;
137        private int initSliderValue = minSliderValue;
138    
139        private int maxmem = StartupManager.getMaximumHeapSize();
140    
141        private State currentState = State.VALID;
142    
143        private boolean doneInit = false;
144    
145        public MemoryOption(final String id, final String label, 
146            final String defaultValue, final OptionPlatform optionPlatform,
147            final Visibility optionVisibility) 
148        {
149            super(id, label, Type.MEMORY, optionPlatform, optionVisibility);
150            if (maxmem==0) defaultPrefValue=failsafeValue;
151            else defaultPrefValue = defaultValue;
152            try {
153                setValue(defaultPrefValue);
154            } catch (IllegalArgumentException e) {
155                setValue(value);
156            }
157            text.setAllow(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
158            jrbSlider.setActionCommand("slider");
159            jrbSlider.addActionListener(this);
160            jrbNumber.setActionCommand("number");
161            jrbNumber.addActionListener(this);
162            sliderPanel.setEnabled(false);
163            textPanel.setEnabled(false);
164        }
165    
166        private String[] getNames(final Prefix[] arr) {
167            assert arr != null;
168            String[] newArr = new String[arr.length];
169            for (int i = 0; i < arr.length; i++)
170                newArr[i] = arr[i].getName();
171            return newArr;
172        }
173    
174        private void setState(final State newState) {
175            assert newState != null : newState;
176            currentState = newState;
177            text.setForeground(currentState.getForeground());
178            text.setBackground(currentState.getBackground());
179        }
180    
181        private boolean isValid() {
182            return currentState == State.VALID;
183        }
184    
185        private boolean isSlider() {
186            return currentPrefix.equals(Prefix.PERCENT);
187        }
188    
189        public void actionPerformed(ActionEvent e) {
190            if (e.getActionCommand().equals("slider")) {
191                GuiUtils.enableTree(sliderPanel, true);
192                GuiUtils.enableTree(textPanel, false);
193                // Trigger the listener
194                int sliderValue = slider.getValue();
195                if (sliderValue==minSliderValue)
196                    slider.setValue(maxSliderValue);
197                else
198                    slider.setValue(minSliderValue);
199                slider.setValue(sliderValue);
200            }
201            else {
202                GuiUtils.enableTree(sliderPanel, false);
203                GuiUtils.enableTree(textPanel, true);
204                // Trigger the listener
205                handleNewValue(text, memVals);
206            }
207        }
208    
209        public ChangeListener percentListener = new ChangeListener() {
210            public void stateChanged(ChangeEvent evt) {
211                if (!sliderPanel.isEnabled()) return;
212                int sliderValue = ((JSlider)evt.getSource()).getValue();
213                setValue(sliderValue + Prefix.PERCENT.getJavaChar());
214                text.setText("" + Math.round(sliderValue / 100.0 * maxmem));
215            }
216        };
217        
218        private void handleNewValue(final JTextField field, final JComboBox box) {
219            if (!textPanel.isEnabled()) return;
220            assert field != null;
221            assert box != null;
222    
223            try {
224                String newValue = field.getText();
225                String huh = ((Prefix)box.getSelectedItem()).getJavaFormat(newValue);
226    
227                if (!isValid())
228                    setState(State.VALID);
229    
230                setValue(huh);
231            } catch (IllegalArgumentException e) {
232                setState(State.ERROR);
233                text.setToolTipText("This value must be an integer greater than zero.");
234            }
235        }
236    
237        public JComponent getComponent() {
238            JPanel topPanel = GuiUtils.hbox(jrbSlider, getSliderComponent());
239            JPanel bottomPanel = GuiUtils.hbox(jrbNumber, getTextComponent());
240            if (isSlider()) {
241                GuiUtils.enableTree(sliderPanel, true);
242                GuiUtils.enableTree(textPanel, false);
243            }
244            else {
245                GuiUtils.enableTree(sliderPanel, false);
246                GuiUtils.enableTree(textPanel, true);
247            }
248            if (maxmem==0) {
249                    jrbSlider.setEnabled(false);
250    //              jrbNumber.setEnabled(false);
251            }
252            doneInit = true;
253            return McVGuiUtils.topBottom(topPanel, bottomPanel, null);
254        }
255        
256        public JComponent getSliderComponent() {
257            sliderLabel = new JLabel("Use "+initSliderValue+"% ");
258            String memoryString = maxmem + "mb";
259            if (maxmem==0) memoryString="Unknown";
260            JLabel postLabel = new JLabel(" of available memory (" + memoryString + ")");
261            JComponent[] sliderComps = GuiUtils.makeSliderPopup(minSliderValue, maxSliderValue+1, initSliderValue, percentListener);
262            slider = (JSlider) sliderComps[1];
263            slider.setMinorTickSpacing(5);
264            slider.setMajorTickSpacing(10);
265            slider.setSnapToTicks(true);
266            slider.setExtent(1);
267            slider.setPaintTicks(true);
268            slider.setPaintLabels(true);
269            sliderComps[0].setToolTipText("Set maximum memory by percent");
270            sliderPanel = GuiUtils.hbox(sliderLabel, sliderComps[0], postLabel);
271            return sliderPanel;
272        }
273    
274        public JComponent getTextComponent() {
275            text.setText(initTextValue);
276            text.addKeyListener(new KeyAdapter() {
277                public void keyReleased(final KeyEvent e) {
278                    handleNewValue(text, memVals);
279                }
280            });
281            memVals.setSelectedItem(initPrefixValue);
282            memVals.addActionListener(new ActionListener() {
283                public void actionPerformed(final ActionEvent e) {
284                    handleNewValue(text, memVals);
285                }
286            });
287            McVGuiUtils.setComponentWidth(text, McVGuiUtils.Width.ONEHALF);
288            McVGuiUtils.setComponentWidth(memVals, McVGuiUtils.Width.ONEHALF);
289            textPanel = GuiUtils.hbox(text, memVals);
290            return textPanel;
291        }
292    
293        public String toString() {
294            return String.format(
295                "[MemoryOption@%x: value=%s, currentPrefix=%s, isSlider=%s]", 
296                hashCode(), value, currentPrefix, isSlider());
297        }
298    
299        public String getValue() {
300            if (!isValid())
301                return defaultPrefValue;
302            return currentPrefix.getJavaFormat(value);
303        }
304    
305        // overridden so that any illegal vals coming *out of* a runMcV.prefs
306        // can be replaced with a legal val.
307        @Override public void fromPrefsFormat(final String prefText) {
308            try {
309                    super.fromPrefsFormat(prefText);
310            } catch (IllegalArgumentException e) {
311                setValue(failsafeValue);
312            }
313        }
314    
315        public void setValue(final String newValue) {       
316            Matcher m = MEMSTRING.matcher(newValue);
317            if (!m.matches())
318                throw new IllegalArgumentException("Badly formatted memory string: "+newValue);
319    
320            String quantity = m.group(1);
321            String prefix = m.group(2);
322            
323            // Fall back on failsafe value if user wants a percentage of an unknown maxmem
324            if (maxmem==0 && prefix.toUpperCase().equals(Prefix.PERCENT.getJavaChar())) {
325                m = MEMSTRING.matcher(failsafeValue);
326                if (!m.matches())
327                    throw new IllegalArgumentException("Badly formatted memory string: "+failsafeValue);
328                quantity = m.group(1);
329                prefix = m.group(2);
330            }
331    
332            int intVal = Integer.parseInt(quantity);
333            if (intVal <= 0)
334                throw new IllegalArgumentException("Memory cannot be less than or equal to zero: "+newValue);
335            if (prefix.length() == 0)
336                prefix = "M";
337            
338            value = quantity;
339            
340            if (prefix.toUpperCase().equals(Prefix.PERCENT.getJavaChar())) {
341                currentPrefix = Prefix.PERCENT;
342    
343                // Work around all the default settings going on
344                initSliderValue = Integer.parseInt(value);
345                initPrefixValue = Prefix.MEGA;
346                initTextValue = "" + (int)Math.round(initSliderValue * maxmem / 100.0);
347    
348                sliderLabel.setText("Use "+value+"% ");
349                if (maxmem>0) {
350                    memVals.setSelectedItem(initPrefixValue);
351                    text.setText(initTextValue);
352                }
353                if (!doneInit) jrbSlider.setSelected(true);
354                return;
355            }
356    
357            for (Prefix tmp : PREFIXES) {
358                if (prefix.toUpperCase().equals(tmp.getJavaChar())) {
359                    currentPrefix = tmp;
360                    
361                    // Work around all the default settings going on
362                    initSliderValue = minSliderValue;
363                    initPrefixValue = currentPrefix;
364                    initTextValue = value;
365                    
366                    if (maxmem>0) {
367                        int multiplier = 1;
368                        if (currentPrefix.equals(Prefix.GIGA)) multiplier=1024;
369                        else if (currentPrefix.equals(Prefix.TERA)) multiplier=1024 * 1024;
370                        initSliderValue = (int)Math.round(Integer.parseInt(value) * 100.0 * multiplier / maxmem);
371                        initSliderValue = Math.max(Math.min(initSliderValue, maxSliderValue), minSliderValue);
372                        slider.setValue(initSliderValue);
373                        sliderLabel.setText("Use "+initSliderValue+"% ");
374                    }
375                    if (!doneInit) jrbNumber.setSelected(true);
376                    return;
377                }
378            }
379    
380            throw new IllegalArgumentException("Could not find matching memory prefix for \""+prefix+"\" in string: "+newValue);
381        }
382    }