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 }