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 }