/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.examples;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.event.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.render.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
/**
* A utility class to interactively build a polyline. When armed, the class monitors mouse events and adds new postions
* to a polyline as the user identifies them. The interaction sequence for creating a line is as follows: <ul> <li> Arm
* the line builder by calling its {@link #setArmed(boolean)} method with an argument of true. </li> <li> Place the
* cursor at the first desired polyline position. Press and release mouse button one. </li> <li> Press button one near
* the next desired position, drag the mouse to the exact position, then release the button. The proposed line segment
* will echo while the mouse is dragged. Continue selecting new positions this way until the polyline contains all
* desired positions. </li> <li> Disarm the <code>LineBuilder</code> object by calling its {@link #setArmed(boolean)}
* method with an argument of false. </li> </ul>
* <p/>
* While the line builder is armed, pressing and immediately releasing mouse button one while also pressing the control
* key (Ctl) removes the last position from the polyline. </p>
* <p/>
* Mouse events the line builder acts on while armed are marked as consumed. These events are mouse pressed, released,
* clicked and dragged. These events are not acted on while the line builder is not armed. The builder can be
* continuously armed and rearmed to allow intervening maneuvering of the globe while building a polyline. A user can
* add positions, pause entry, maneuver the view, then continue entering positions. </p>
* <p/>
* Arming and disarming the line builder does not change the contents or attributes of the line builder's layer. </p>
* <p/>
* The polyline and a layer contatining it may be specified when a <code>LineBuilder</code> is constructed. </p>
* <p/>
* This class contains a <code>main</code> method implementing an example program illustrating use of
* <code>LineBuilder</code>. </p>
*
* @author tag
* @version $Id: LineBuilder.java 3623 2007-11-26 07:12:44Z tgaskins $
*/
public class LineBuilder extends AVListImpl
{
private final WorldWindow wwd;
private boolean armed = false;
private ArrayList<Position> positions = new ArrayList<Position>();
private final RenderableLayer layer;
private final Polyline line;
private boolean active = false;
/**
* Construct a new line builder using the specified polyline and layer and drawing events from the specified world
* window. Either or both the polyline and the layer may be null, in which case the necessary object is created.
*
* @param wwd the world window to draw events from.
* @param lineLayer the layer holding the polyline. May be null, in which case a new layer is created.
* @param polyline the polyline object to build. May be null, in which case a new polyline is created.
*/
public LineBuilder(final WorldWindow wwd, RenderableLayer lineLayer, Polyline polyline)
{
this.wwd = wwd;
if (polyline != null)
{
line = polyline;
}
else
{
this.line = new Polyline();
this.line.setFollowTerrain(true);
}
this.layer = lineLayer != null ? lineLayer : new RenderableLayer();
this.layer.addRenderable(this.line);
this.wwd.getModel().getLayers().add(this.layer);
this.wwd.getInputHandler().addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (armed && (mouseEvent.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
{
if (!mouseEvent.isControlDown())
{
active = true;
addPosition();
}
}
mouseEvent.consume();
}
}
public void mouseReleased(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (positions.size() == 1)
removePosition();
active = false;
mouseEvent.consume();
}
}
public void mouseClicked(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (mouseEvent.isControlDown())
removePosition();
mouseEvent.consume();
}
}
});
this.wwd.getInputHandler().addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent mouseEvent)
{
if (armed && (mouseEvent.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
{
// Don't update the polyline here because the wwd current cursor position will not
// have been updated to reflect the current mouse position. Wait to update in the
// position listener, but consume the event so the view doesn't respond to it.
if (active)
mouseEvent.consume();
}
}
});
this.wwd.addPositionListener(new PositionListener()
{
public void moved(PositionEvent event)
{
if (!active)
return;
if (positions.size() == 1)
addPosition();
else
replacePosition();
}
});
}
/**
* Returns the layer holding the polyline being created.
*
* @return the layer containing the polyline.
*/
public RenderableLayer getLayer()
{
return this.layer;
}
/**
* Returns the layer currently used to display the polyline.
*
* @return the layer holding the polyline.
*/
public Polyline getLine()
{
return this.line;
}
/**
* Removes all positions from the polyline.
*/
public void clear()
{
while (this.positions.size() > 0)
this.removePosition();
}
/**
* Identifies whether the line builder is armed.
*
* @return true if armed, false if not armed.
*/
public boolean isArmed()
{
return this.armed;
}
/**
* Arms and disarms the line builder. When armed, the line builder monitors user input and builds the polyline in
* response to the actions mentioned in the overview above. When disarmed, the line builder ignores all user input.
*
* @param armed true to arm the line builder, false to disarm it.
*/
public void setArmed(boolean armed)
{
this.armed = armed;
}
private void addPosition()
{
Position curPos = this.wwd.getCurrentPosition();
if (curPos == null)
return;
this.positions.add(curPos);
this.line.setPositions(this.positions);
this.firePropertyChange("LineBuilder.AddPosition", null, curPos);
this.wwd.redraw();
}
private void replacePosition()
{
Position curPos = this.wwd.getCurrentPosition();
if (curPos == null)
return;
int index = this.positions.size() - 1;
if (index < 0)
index = 0;
Position currentLastPosition = this.positions.get(index);
this.positions.set(index, curPos);
this.line.setPositions(this.positions);
this.firePropertyChange("LineBuilder.ReplacePosition", currentLastPosition, curPos);
this.wwd.redraw();
}
private void removePosition()
{
if (this.positions.size() == 0)
return;
Position currentLastPosition = this.positions.get(this.positions.size() - 1);
this.positions.remove(this.positions.size() - 1);
this.line.setPositions(this.positions);
this.firePropertyChange("LineBuilder.RemovePosition", currentLastPosition, null);
this.wwd.redraw();
}
// ===================== Control Panel ======================= //
// The following code is an example program illustrating LineBuilder usage. It is not required by the
// LineBuilder class, itself.
private static class LinePanel extends JPanel
{
private final WorldWindow wwd;
private final LineBuilder lineBuilder;
private JButton newButton;
private JButton pauseButton;
private JButton endButton;
private JLabel[] pointLabels;
public LinePanel(WorldWindow wwd, LineBuilder lineBuilder)
{
super(new BorderLayout());
this.wwd = wwd;
this.lineBuilder = lineBuilder;
this.makePanel(new Dimension(200, 400));
lineBuilder.addPropertyChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent propertyChangeEvent)
{
fillPointsPanel();
}
});
}
private void makePanel(Dimension size)
{
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 5, 5));
newButton = new JButton("New");
newButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
lineBuilder.clear();
lineBuilder.setArmed(true);
pauseButton.setText("Pause");
pauseButton.setEnabled(true);
endButton.setEnabled(true);
newButton.setEnabled(false);
((Component) wwd).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
});
buttonPanel.add(newButton);
newButton.setEnabled(true);
pauseButton = new JButton("Pause");
pauseButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
lineBuilder.setArmed(!lineBuilder.isArmed());
pauseButton.setText(!lineBuilder.isArmed() ? "Resume" : "Pause");
((Component) wwd).setCursor(Cursor.getDefaultCursor());
}
});
buttonPanel.add(pauseButton);
pauseButton.setEnabled(false);
endButton = new JButton("End");
endButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
lineBuilder.setArmed(false);
newButton.setEnabled(true);
pauseButton.setEnabled(false);
pauseButton.setText("Pause");
endButton.setEnabled(false);
((Component) wwd).setCursor(Cursor.getDefaultCursor());
}
});
buttonPanel.add(endButton);
endButton.setEnabled(false);
JPanel pointPanel = new JPanel(new GridLayout(0, 1, 0, 10));
pointPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.pointLabels = new JLabel[20];
for (int i = 0; i < this.pointLabels.length; i++)
{
this.pointLabels[i] = new JLabel("");
pointPanel.add(this.pointLabels[i]);
}
// Put the point panel in a container to prevent scroll panel from stretching the vertical spacing.
JPanel dummyPanel = new JPanel(new BorderLayout());
dummyPanel.add(pointPanel, BorderLayout.NORTH);
// Put the point panel in a scroll bar.
JScrollPane scrollPane = new JScrollPane(dummyPanel);
scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
if (size != null)
scrollPane.setPreferredSize(size);
// Add the buttons, scroll bar and inner panel to a titled panel that will resize with the main window.
JPanel outerPanel = new JPanel(new BorderLayout());
outerPanel.setBorder(
new CompoundBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9), new TitledBorder("Line")));
outerPanel.setToolTipText("Line control and info");
outerPanel.add(buttonPanel, BorderLayout.NORTH);
outerPanel.add(scrollPane, BorderLayout.CENTER);
this.add(outerPanel, BorderLayout.CENTER);
}
private void fillPointsPanel()
{
int i = 0;
for (Position pos : lineBuilder.getLine().getPositions())
{
if (i == this.pointLabels.length)
break;
String las = String.format("Lat %7.4f\u00B0", pos.getLatitude().getDegrees());
String los = String.format("Lon %7.4f\u00B0", pos.getLongitude().getDegrees());
pointLabels[i++].setText(las + " " + los);
}
for (; i < this.pointLabels.length; i++)
pointLabels[i++].setText("");
}
}
/**
* Marked as deprecated to keep it out of the javadoc.
*
* @deprecated
*/
public static class AppFrame extends ApplicationTemplate.AppFrame
{
public AppFrame()
{
super(true, false, false);
LineBuilder lineBuilder = new LineBuilder(this.getWwd(), null, null);
this.getContentPane().add(new LinePanel(this.getWwd(), lineBuilder), BorderLayout.WEST);
}
}
/**
* Marked as deprecated to keep it out of the javadoc.
*
* @param args the arguments passed to the program.
* @deprecated
*/
public static void main(String[] args)
{
//noinspection deprecation
ApplicationTemplate.start("World Wind Line Builder", LineBuilder.AppFrame.class);
}
}