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