Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (C) 2008-2012 AgroSense Foundation.
*
* AgroSense is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* There are special exceptions to the terms and conditions of the GPLv3 as it is applied to
* this software, see the FLOSS License Exception
* .
*
* AgroSense is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AgroSense. If not, see .
*/
package eu.limetri.client.mapviewer.nb.jxmap.map;
import eu.limetri.client.mapviewer.api.RangedLegendPalette;
import eu.limetri.client.mapviewer.api.RangedLegendPalette.Range;
import java.awt.Color;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Collection;
import javax.swing.ButtonGroup;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultFormatter;
import net.miginfocom.swing.MigLayout;
import org.openide.util.NbBundle;
/**
*
* @author johan
*/
@NbBundle.Messages({
"range_settings_panel.name=Range settings",
"range_settings_panel.label data range=Data range",
"range_settings_panel.label custom range=Custom range",
})
public class CustomRangePanel extends JPanel {
private final RangedLegendPalette palette;
private final Range customRange;
private JRadioButton dataOption;
private RangeRow dataRangeRow;
private JRadioButton customOption;
private RangeRow customRangeRow;
public CustomRangePanel(RangedLegendPalette palette) {
this.palette = palette;
this.customRange = new Range<>(palette.getMinValue(), palette.getMaxValue());
initComponents();
}
private void initComponents() {
setLayout(new MigLayout("wrap 2"));
// 1) data range, show current values read-only
// 2) create custom range
// 3) maybe later: select custom ranges from combobox;
// possible sources: favorites, interchangeable ranges (similar to palette behavior), measurement types, ...
dataOption = new JRadioButton(Bundle.range_settings_panel_label_data_range());
dataOption.setSelected(!palette.usesCustomRange());
dataRangeRow = new RangeRow(palette.getDataRange(), true);
customOption = new JRadioButton(Bundle.range_settings_panel_label_custom_range());
customOption.setSelected(palette.usesCustomRange());
customRangeRow = new RangeRow(customRange, false);
// select the custom option when one of the custom range fields gains focus:
customRangeRow.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent fe) {
if (!fe.isTemporary()) customOption.setSelected(true);
}
});
ButtonGroup group = new ButtonGroup();
group.add(dataOption);
group.add(customOption);
add(dataOption, "top");
add(dataRangeRow);
add(customOption, "top");
add(customRangeRow);
// spacer, part of custom range message was cut off
add(new JLabel(), "span");
}
//bind to OK button
protected void apply(Collection> palettes) {
if (dataOption.isSelected()) {
for (RangedLegendPalette p : palettes) {
p.applyDataRange();
}
} else if (customOption.isSelected()) {
for (RangedLegendPalette p : palettes) {
p.setRange(customRange);
}
}
}
private static Format createFormat() {
return ParseEntireString.of(new DecimalFormat());
}
@NbBundle.Messages({
"RangeRow error min invalid=Lower bound is not a valid number",
"RangeRow error max invalid=Upper bound is not a valid number",
"RangeRow error min and max invalid=Neither lower or upper bound is a valid number ",
"RangeRow error range invalid=Lower bound is larger than upper bound",
})
private class RangeRow extends JPanel {
private final boolean readOnly;
private final JFormattedTextField minField = new JFormattedTextField(createFormat());
private final JFormattedTextField maxField = new JFormattedTextField(createFormat());
private final JLabel msgLabel = new JLabel("");
private final Range range;
private boolean minValid = true;
private boolean maxValid = true;
private boolean rangeValid = true;
public RangeRow(Range range, boolean readonly) {
this.range = range;
this.readOnly = readonly;
initComponents();
bind();
}
private void initComponents() {
setLayout(new MigLayout("wrap 3, insets 0 0 0 0"));
initTexField(minField, range.getMin());
initTexField(maxField, range.getMax());
msgLabel.setForeground(Color.red);
msgLabel.setVisible(false);
add(minField);
add(new JLabel(" - "));
add(maxField);
//msg on its own row:
add(msgLabel, "span");
}
private void initTexField(final JFormattedTextField field, Number value) {
field.setColumns(10);
field.setValue(toDouble(value) / palette.getScale());
if (readOnly) {
field.setEnabled(false);
} else {
//don't automatically revert invalid values (show msg instead):
field.setFocusLostBehavior(JFormattedTextField.COMMIT);
//post all valid edits: allows for applying all validations while still typing instead of on focus loss
((DefaultFormatter)field.getFormatter()).setCommitsOnValidEdit(true);
//caret fix: default behavior puts it before 1st char, no matter where you click
field.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
final int dot = field.getCaret().getDot();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
field.getCaret().setDot(dot);
}
});
}
});
}
}
@Override
public void addFocusListener(FocusListener fl) {
//delegate to focusable components (all text fields):
minField.addFocusListener(fl);
maxField.addFocusListener(fl);
}
private void bind() {
if (!readOnly) {
//"value" won't fire when changing invalid text back to the last valid value
minField.addPropertyChangeListener("value", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
Number nMin = (Number) pce.getNewValue();
Number nMax = (Number) maxField.getValue();
//compare with other value from field, not from range, and post both:
// e.g. while editing the min value, the current max value showing might not be in
// the range object yet because of a previous range validation failure.
postIfValid(nMin, nMax);
}
});
maxField.addPropertyChangeListener("value", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
Number nMax = (Number) pce.getNewValue();
Number nMin = (Number) minField.getValue();
postIfValid(nMin, nMax);
}
});
//"editValid" will fire before "value"
minField.addPropertyChangeListener("editValid", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
minValid = (Boolean) pce.getNewValue();
updateMsg();
}
});
maxField.addPropertyChangeListener("editValid", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
maxValid = (Boolean) pce.getNewValue();
updateMsg();
}
});
}
}
private void postIfValid(Number nMin, Number nMax) {
// the textfields post their text to their value if parsing succeeds;
// we'll post both values to the range if the combination is valid.
double min = toDouble(nMin) * palette.getScale();
double max = toDouble(nMax) * palette.getScale();
rangeValid = !(min > max);
updateMsg();
if(isStateValid()) {
range.setMin(min);
range.setMax(max);
}
}
private boolean isStateValid() {
return minValid && maxValid && rangeValid;
}
private void updateMsg() {
if (!(minValid || maxValid)) setMsg(Bundle.RangeRow_error_min_and_max_invalid());
else if (!minValid) setMsg(Bundle.RangeRow_error_min_invalid());
else if (!maxValid) setMsg(Bundle.RangeRow_error_max_invalid());
else if (!rangeValid) setMsg(Bundle.RangeRow_error_range_invalid());
else clearMsg();
}
private double toDouble(Number n) {
return n == null ? 0.0 : n.doubleValue();
}
private void setMsg(String msg) {
msgLabel.setText(msg);
msgLabel.setVisible(true);
}
private void clearMsg() {
msgLabel.setVisible(false);
}
}
//Format decorator that ensures the entire source string is used in parsing:
// e.g. NumberFormat allows trailing characters that won't cause an error ("12bla" is ok, "bla12" is not)
// and which (in case of a JFormattedTextField) will disappear again once focus is lost.
//
//TODO: extract to util library
private static class ParseEntireString extends Format {
private final Format original;
public ParseEntireString(Format original) {
this.original = original;
}
public static Format of(Format original) {
return new ParseEntireString(original);
}
@Override
public StringBuffer format(Object o, StringBuffer sb, FieldPosition fp) {
return original.format(o, sb, fp);
}
@Override
public Object parseObject(String string, ParsePosition pp) {
int initialIndex = pp.getIndex();
Object result = original.parseObject(string, pp);
if (result != null && pp.getIndex() < string.length()) {
int errorIndex = pp.getIndex();
pp.setIndex(initialIndex);
pp.setErrorIndex(errorIndex);
return null;
}
return result;
}
}
}