// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/EditableOMRangeRings.java,v $
// $RCSfile: EditableOMRangeRings.java,v $
// $Revision: 1.15 $
// $Date: 2009/01/21 01:24:41 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import com.bbn.openmap.I18n;
import com.bbn.openmap.event.UndoEvent;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PaletteHelper;
/**
*/
public class EditableOMRangeRings
extends EditableOMCircle {
// TODO need to update the memory mechanism between OMRangeRings that are
// being edited/created so that statics aren't being used. The only other
// place this info could be held is either in the drawing tool or in the
// OMRangeRingsLoader (OMCircleLoader), and I don't think we have a handle to
// either of them from here. Gah! I hate using statics, they make me feel
// dirty.
protected static int lastInterval;
protected static Length lastUnit;
protected static boolean snapToInterval = false;
/**
* Create the EditableOMRangeRings, setting the state machine to create the
* circle off of the gestures.
*/
public EditableOMRangeRings() {
createGraphic(null);
}
/**
* Create an EditableOMRangeRings with the circleType and renderType
* parameters in the GraphicAttributes object.
*/
public EditableOMRangeRings(GraphicAttributes ga) {
createGraphic(ga);
}
/**
* Create the EditableOMRangeRings with an OMCircle already defined, ready
* for editing.
*
* @param omc OMCircle that should be edited.
*/
public EditableOMRangeRings(OMRangeRings omc) {
setGraphic(omc);
}
/**
* Create and set the graphic within the state machine. The GraphicAttributes
* describe the type of circle to create.
*/
public void createGraphic(GraphicAttributes ga) {
init();
stateMachine.setUndefined();
int renderType = OMGraphic.RENDERTYPE_LATLON;
if (ga != null) {
renderType = ga.getRenderType();
}
if (Debug.debugging("eomc")) {
Debug.output("EditableOMRangeRings.createGraphic(): rendertype = " + renderType);
}
circle = new OMRangeRings(90f, -180f, 0f);
if (ga != null) {
ga.setTo(circle, true);
}
}
/**
* Modifies the gui to not include line type adjustments, and adds widgets to
* control range ring settings.
*
* @param graphicAttributes the GraphicAttributes to use to get the GUI
* widget from to control those parameters for this EOMG.
* @return java.awt.Component to use to control parameters for this EOMG.
*/
public Component getGUI(GraphicAttributes graphicAttributes) {
Debug.message("eomg", "EditableOMRangeRings.getGUI");
if (graphicAttributes != null) {
// JComponent panel = graphicAttributes.getColorAndLineGUI();
JComponent toolbar = createAttributePanel(graphicAttributes);
// panel.add(getRangeRingGUI());
getRangeRingGUI(graphicAttributes.getOrientation(), toolbar);
return toolbar;
} else {
return getRangeRingGUI();
}
}
public void updateInterval(int val) {
int oldInterval = ((OMRangeRings) circle).getInterval();
((OMRangeRings) circle).setInterval(val);
lastInterval = val;
if (intervalField != null) {
intervalField.setText(Integer.toString(val));
}
if (snapToInterval) {
setRadius(circle.getRadius());
}
if (oldInterval != val) {
updateCurrentState(null);
}
redraw(null, true);
}
public void updateInterval(String intervalStr) {
int oldValue = ((OMRangeRings) circle).getInterval();
int value = interpretValue(intervalStr);
if (value <= 0) {
value = oldValue;
}
updateInterval(value);
}
public int interpretValue(String intervalStr) {
int value = -1;
try {
if (intervalStr.toLowerCase().endsWith("m")) {
intervalStr = intervalStr.substring(0, intervalStr.length() - 1);
value = (int) df.parse(intervalStr).intValue() * 1000000;
} else if (intervalStr.toLowerCase().endsWith("k")) {
intervalStr = intervalStr.substring(0, intervalStr.length() - 1);
value = df.parse(intervalStr).intValue() * 1000;
} else if (intervalStr.trim().length() == 0) {
// do nothing
} else {
value = df.parse(intervalStr).intValue();
}
} catch (java.text.ParseException e) {
Debug.error("RangeRing interval value not valid: " + intervalStr);
} catch (NumberFormatException e) {
Debug.error("RangeRing interval value not valid: " + intervalStr);
}
return value;
}
// Need these three for UndoEvents to be able to update them if an update
// event gets implemented.
protected JTextField intervalField = null;
protected JComboBox unitsCombo = null;
protected JCheckBox snapCheckBox = null;
protected JToolBar rrToolBar = null;
protected transient DecimalFormat df = new DecimalFormat();
protected JComponent attributeBox;
protected JComponent getRangeRingGUI() {
return getRangeRingGUI(SwingConstants.HORIZONTAL, (JComponent) null);
}
/**
* Get the GUI associated with changing the Text.
*
* @param orientation SwingConstants.HORIZONTAL/VERTICAL
* @param guiComp the JComponent to add stuff to. If the orientation is
* HORIZONTAL, the components will be added directly to this
* component, or to a new JComponent that is returned if null. If the
* orientation is Vertical, a button will be added to the guiComp, or
* returned. This button will call up a dialog box with the settings,
* since they don't really lay out vertically.
* @return JComponent for controlling range-ring specific attributes.
*/
protected JComponent getRangeRingGUI(int orientation, JComponent guiComp) {
attributeBox = null;
if (guiComp == null || orientation == SwingConstants.VERTICAL) {
attributeBox = javax.swing.Box.createHorizontalBox();
attributeBox.setAlignmentX(Component.CENTER_ALIGNMENT);
attributeBox.setAlignmentY(Component.CENTER_ALIGNMENT);
if (orientation == SwingConstants.HORIZONTAL) {
guiComp = attributeBox;
}
} else if (orientation == SwingConstants.HORIZONTAL) {
attributeBox = guiComp;
}
if (guiComp == null) {
guiComp = new JPanel();
}
guiComp.add(PaletteHelper.getToolBarFill(orientation));
if (orientation == SwingConstants.VERTICAL) {
JButton launchButton = new JButton("RR");
launchButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (attributeBox != null) {
JDialog dialog = new JDialog();
dialog.setContentPane(attributeBox);
dialog.setModal(true);
dialog.pack();
dialog.setLocationRelativeTo((JButton) ae.getSource());
dialog.setVisible(true);
}
}
});
guiComp.add(launchButton);
}
configureRangeRings();
intervalField = makeIntervalField();
attributeBox.add(intervalField);
unitsCombo = makeUnitsCombo();
attributeBox.add(unitsCombo);
snapCheckBox = makeSnapCheckBox();
attributeBox.add(snapCheckBox);
return guiComp;
}
private void configureRangeRings() {
((OMRangeRings) circle).setInterval(getInterval());
((OMRangeRings) circle).setIntervalUnits(getUnits());
}
private int getInterval() {
return (!isNewRing()) ? ((OMRangeRings) circle).getInterval() : haveUserSpecifiedValue() ? lastInterval
: OMRangeRings.DEFAULT_INTERVAL;
}
private Length getUnits() {
return (!isNewRing()) ? ((OMRangeRings) circle).getIntervalUnits() : haveUserSpecifiedValue() ? lastUnit : null;
}
private boolean isNewRing() {
// we rely on interval units not being initialized during construction
return (((OMRangeRings) circle).getIntervalUnits() == null);
}
private boolean haveUserSpecifiedValue() {
// lastUnit is not null if the user made a selection with the comboBox
return (lastUnit != null);
}
private JTextField makeIntervalField() {
JTextField field = new JTextField(Integer.toString(((OMRangeRings) circle).getInterval()), 5);
field.setMargin(new Insets(0, 1, 0, 1));
// without minimum size set, field can be too small to use
field.setMinimumSize(new Dimension(40, 18));
field.setHorizontalAlignment(JTextField.RIGHT);
field.setToolTipText(i18n.get(this, "intervalField.tooltip", "Value for interval between rings."));
field.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
updateInterval(((JTextField) (ae.getSource())).getText());
}
});
// Users forget to hit Enter, which is required for an action event,
// then wonder why the rings they draw don't have the desired value.
// Adding a focus listener addresses this issue.
field.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent event) {
if (!event.isTemporary()) {
updateInterval(((JTextField) (event.getSource())).getText());
}
}
});
return field;
}
private JComboBox makeUnitsCombo() {
Length[] available = Length.values();
String[] unitStrings = new String[available.length + 1];
String current = null;
Length l = ((OMRangeRings) circle).getIntervalUnits();
if (l != null) {
current = l.toString();
}
int currentIndex = unitStrings.length - 1;
for (int i = 0; i < available.length; i++) {
unitStrings[i] = available[i].toString();
if (unitStrings[i] != null && unitStrings[i].equals(current)) {
currentIndex = i;
}
}
unitStrings[unitStrings.length - 1] = i18n.get(this, "unitStrings.concentric", "concentric");
JComboBox combo = new JComboBox(unitStrings);
combo.setBorder(new EmptyBorder(0, 1, 0, 1));
combo.setSelectedIndex(currentIndex);
combo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox jcb = (JComboBox) e.getSource();
OMRangeRings rr = (OMRangeRings) circle;
Length newLength = Length.get((String) jcb.getSelectedItem());
Length oldLength = rr.getIntervalUnits();
/*
* If newLength is not null and oldLength is not null, just
* translate the distance that is current specified. If newLength is
* null, then find out how many rings are on the range ring and set
* the interval to that. If oldLength is null, find out the radius
* and divide it by the number of rings - 1.
*/
int value = interpretValue(intervalField.getText());
if (value <= 0) {
value = 4;
}
if (newLength != null && oldLength != null) {
value = (int) newLength.fromRadians(oldLength.toRadians(value));
} else {
int numSubCircles;
if (rr.subCircles == null || rr.subCircles.length == 0) {
numSubCircles = 1;
} else {
numSubCircles = rr.subCircles.length;
}
if (newLength == null) {
value = numSubCircles;
} else if (oldLength == null) {
value = (int) Math.ceil(newLength.fromRadians(Length.DECIMAL_DEGREE.toRadians(rr.getRadius()))
/ numSubCircles);
}
}
((OMRangeRings) circle).setIntervalUnits(newLength);
lastUnit = newLength;
updateInterval(value);
}
});
return combo;
}
private JCheckBox makeSnapCheckBox() {
String snapText = i18n.get(this, "snapToInterval", "Snap");
JCheckBox snapBox = new JCheckBox(snapText, isSnapToInterval());
snapText = i18n.get(this, "snapToInterval", I18n.TOOLTIP, "Round radius to nearest interval value.");
snapBox.setToolTipText(snapText);
snapBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
setSnapToInterval(((JCheckBox) ae.getSource()).isSelected());
if (snapToInterval) {
setRadius(circle.getRadius());
}
updateCurrentState(null);
redraw(null, true);
}
});
return snapBox;
}
protected boolean drawLabelsHolder = true;
/**
* A convenience method that gives an EditableOMGraphic a chance to modify
* the OMGraphic so it can be drawn quickly, by turning off labels, etc,
* right before the XORpainting happens. The OMGraphic should be configured
* so that the render method does the least amount of painting possible. Note
* that the DrawingAttributes for the OMGraphic have already been set to
* DrawingAttributes.DEFAULT (black line, clear fill).
*/
protected void modifyOMGraphicForEditRender() {
OMRangeRings omrr = (OMRangeRings) getGraphic();
drawLabelsHolder = omrr.getDrawLabels();
omrr.setDrawLabels(false);
}
/**
* A convenience method that gives an EditableOMGraphic a chance to reset the
* OMGraphic so it can be rendered normally, after it has been modified for
* quick paints. The DrawingAttributes for the OMGraphic have already been
* reset to their normal settings, from the DrawingAttributes.DEFAULT
* settings that were used for the quick paint.
*/
protected void resetOMGraphicAfterEditRender() {
((OMRangeRings) getGraphic()).setDrawLabels(drawLabelsHolder);
}
public boolean isSnapToInterval() {
return snapToInterval;
}
public void setSnapToInterval(boolean sti) {
snapToInterval = sti;
}
protected void setRadius(double radius) {
if (circle != null) {
if (snapToInterval) {
OMRangeRings rr = (OMRangeRings) circle;
Length units = rr.getIntervalUnits();
if (units != null) {
double rds = units.fromRadians(Length.DECIMAL_DEGREE.toRadians(radius));
radius = Math.round(rds / rr.getInterval()) * rr.getInterval();
radius = Length.DECIMAL_DEGREE.fromRadians(units.toRadians(radius));
}
}
circle.setRadius(radius);
}
}
/**
* Create an UndoEvent that can get an OMRangeRing back to what it looks like
* right now.
*/
protected UndoEvent createUndoEventForCurrentState(String whatHappened) {
if (whatHappened == null) {
whatHappened = i18n.get(this.getClass(), "rangeRingUndoString", "Edit");
}
return new OMRangeRingUndoEvent(this, whatHappened);
}
/**
* Subclass for undoing edits for OMRangeRing classes, handles events that
* may affect the extra GUI widgets.
*
* @author ddietrick
*/
public static class OMRangeRingUndoEvent
extends OMGraphicUndoEvent
implements UndoEvent {
boolean snap;
public OMRangeRingUndoEvent(EditableOMRangeRings eomp, String description) {
super(eomp, description);
snap = eomp.isSnapToInterval();
}
protected void setSubclassState() {
OMRangeRings rrStateHolder = (OMRangeRings) stateHolder;
EditableOMRangeRings eomrr = (EditableOMRangeRings) eomg;
if (eomrr.snapCheckBox != null) {
eomrr.snapCheckBox.setSelected(snap);
eomrr.setSnapToInterval(snap);
}
if (eomrr.intervalField != null) {
eomrr.intervalField.setText(Integer.toString(rrStateHolder.getInterval()));
}
Length intervalUnits = rrStateHolder.getIntervalUnits();
if (eomrr.unitsCombo != null && intervalUnits != null) {
eomrr.unitsCombo.setSelectedItem(intervalUnits.toString());
}
}
}
}