// ********************************************************************** // // <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()); } } } }