/*
* Copyright 2006, United States Government as represented by the Administrator
* for the National Aeronautics and Space Administration. No copyright is
* claimed in the United States under Title 17, U.S. Code. All Other Rights
* Reserved.
*/
package gov.nasa.ial.mde.ui;
import gov.nasa.ial.mde.properties.MdeSettings;
import gov.nasa.ial.mde.solver.Solver;
import gov.nasa.ial.mde.ui.util.ComponentUtil;
import gov.nasa.ial.mde.util.ResourceUtil;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;
/**
* <code>ZoomButtons</code> is a reusable four button GUI widget that
* provides controls for drawing, zooming in, zooming out or resetting MDE graph
* solution bounds to the default values. It effectively makes a callback to the
* input Solver object to recompute the solution over the new bounds. (The
* Solver object will then notify any registered listeners that the solution
* bounds have changed.)
*
* To the end-user, the effect of each button press is as follows: <blockquote>
* draw graph - (re)draw the graph using the current bounds <br>
* zoom in - zoom in on the graph (decreased left, right, upper, lower bounds)
* <br>
* zoom out - zoom out on the graph <br>
* reset graph - draw the graph using the default bounds </blockquote>
*
* <p>
* This class uses the look and feel of the MathTrax application, i.e.,
* roll-over "Draw" and "Reset" icons, a plus sign (+) icon for the zoom in
* button and a minus sign (-) icon for the zoom out button.
* <p>
* All the buttons have accessibleNames and accessibleDescriptions.
* <p>
* The ZoomButtons constructor takes an instance of Solver. Every click of
* a button calls <code>Solver.solve</code> after recomputing the new bounds
* (for zoom in/out and reset). Any components that are registered listeners for
* that Solver object will be notified that the Solution bounds have changed and
* can respond accordingly.
* <p>
*
* Zooming in decreases the left, right, top and bottom solution bounds
* symmetrically, according to a scale factor. You can set the zoom scale factor
* with the <code>setIncrement</code> method. The default scale factor is
* sqrt(2). The scaling algorithm is:
*
* <code>
* <blockquote>...
* <br>if (zoomIn) {
* <br>// Zoom in
* <br> newHalfWidth = (right - left) / (2.0 * scaleFactor);
* <br> newHalfHeight = (top - bottom) / (2.0 * scaleFactor);
* <br>} else {
* <br>// Zoom out
* <br> newHalfWidth = scaleFactor * (right - left) / 2.0;
* <br>newHalfHeight = scaleFactor * (top - bottom) / 2.0;
* <br>}
*
* </blockquote>
*</code>
* <p>
*/
public class ZoomButtons extends JPanel implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1094388747710888202L;
private JButton zoomOutBtn;
private JButton zoomInBtn;
private JButton resetBtn;
private JButton graphButton;
private String zoomOutLabel;
private String zoomInLabel;
private String resetLabel;
private String graphLabel;
private double scaleFactor;
private Solver solver;
/**
* You can use the ZoomButtons.ZOOM_IN flag when calling the
* <code>zoomGraph</code> method for a zoom in.
*/
public static final boolean ZOOM_IN = true;
/**
* You can use the ZoomButtons.ZOOM_OUT flag when calling the
* <code>zoomGraph</code> method for a zoom out.
*/
public static final boolean ZOOM_OUT = false;
@SuppressWarnings("unused")
private ZoomButtons() {
throw new RuntimeException("Default constructor not allowed.");
}
/**
* Create a new ZoomButtons graph controls widget using the given
* Solver object.
*
* @param solver a reference to the solver.
*/
public ZoomButtons(Solver solver) {
this.solver = solver;
scaleFactor = Math.sqrt(2.0); // was 2.0;
zoomOutLabel = "Outward Zoom"; //"Increase X";
zoomInLabel = "Inward Zoom"; //"Decrease X";
resetLabel = "Reset Graph";
graphLabel = "Graph It";
buttonInit();
Box topBox = new Box(BoxLayout.X_AXIS);
topBox.add(Box.createHorizontalGlue());
topBox.add(graphButton);
topBox.add(Box.createHorizontalGlue());
Box middleBox = new Box(BoxLayout.X_AXIS);
middleBox.add(Box.createHorizontalGlue());
middleBox.add(zoomInBtn);
middleBox.add(zoomOutBtn);
middleBox.add(Box.createHorizontalGlue());
Box bottomBox = new Box(BoxLayout.X_AXIS);
bottomBox.add(Box.createHorizontalGlue());
bottomBox.add(resetBtn);
bottomBox.add(Box.createHorizontalGlue());
JPanel mainPnl = new JPanel();
mainPnl.setLayout(new BoxLayout(mainPnl, BoxLayout.Y_AXIS));
mainPnl.setBorder(BorderFactory.createLoweredBevelBorder());
mainPnl.add(topBox);
mainPnl.add(middleBox);
mainPnl.add(bottomBox);
// Update the background color and of the children as well.
ComponentUtil.setBackground(mainPnl, ColorDefaults.BUTTON_BG_COLOR);
setLayout(new BorderLayout());
setBorder(BorderFactory.createTitledBorder(BorderFactory
.createEtchedBorder(), "Graph Controls", TitledBorder.LEFT,
TitledBorder.TOP));
add(mainPnl, BorderLayout.CENTER);
// Update the background color and of the children as well.
setBackground(ColorDefaults.PANEL_BG_COLOR);
}
/**
* Set button graphics icons, accessible names and accessible descriptions.
*/
protected void buttonInit() {
try {
ResourceUtil ru = new ResourceUtil(MdeSettings.RESOURCES_PATH);
ImageIcon graphIcon1 = new ImageIcon(ru.getImage("draw1.gif"),
"Graph Button");
ImageIcon graphIcon2 = new ImageIcon(ru.getImage("draw2.gif"),
"Graph Button Mouseover");
ImageIcon plusIcon1 = new ImageIcon(ru.getImage("Plus1_s1.gif"),
"Zoom In Button");
ImageIcon plusIcon2 = new ImageIcon(ru.getImage("Plus2_s1.gif"),
"Zoom In Mouseover");
ImageIcon minusIcon1 = new ImageIcon(ru.getImage("Minus1_s1.gif"),
"Zoom Out");
ImageIcon minusIcon2 = new ImageIcon(ru.getImage("Minus2_s1.gif"),
"Zoom Out Mouseover");
ImageIcon renameIcon1 = new ImageIcon(ru.getImage("reset1.gif"),
"Reset Button");
ImageIcon renameIcon2 = new ImageIcon(ru.getImage("reset2.gif"),
"Reset Mouseover");
// ImageIcon graphIcon1 = new ImageIcon(ru.getImage("draw1.gif"),
// "Graph Button");
// ImageIcon graphIcon2 = new ImageIcon(ru.getImage("draw2.gif"),
// "Graph Button Mouseover");
// ImageIcon plusIcon1 = new ImageIcon(ru.getImage("draw1.gif"),
// "Zoom In Button");
// ImageIcon plusIcon2 = new ImageIcon(ru.getImage("draw1.gif"),
// "Zoom In Mouseover");
// ImageIcon minusIcon1 = new ImageIcon(ru.getImage("draw1.gif"),
// "Zoom Out");
// ImageIcon minusIcon2 = new ImageIcon(ru.getImage("draw1.gif"),
// "Zoom Out Mouseover");
// ImageIcon renameIcon1 = new ImageIcon(ru.getImage("draw1.gif"),
// "Reset Button");
// ImageIcon renameIcon2 = new ImageIcon(ru.getImage("draw1.gif"),
// "Reset Mouseover");
zoomOutBtn = new JButton(minusIcon1);
zoomOutBtn.getAccessibleContext().setAccessibleName(zoomOutLabel);
String zoomOutBtnAD = "Navigation Shortcut CTRL+O. Use the zoom out button to increase the graph's X bounds. Each click will increase "
+ "the left and right bounds by 2 units.";
zoomOutBtn.getAccessibleContext().setAccessibleDescription(
zoomOutBtnAD);
zoomOutBtn.setToolTipText("Zoom Out (CTRL-O)");
zoomOutBtn.setBorderPainted(false);
zoomOutBtn.setRolloverIcon(minusIcon2);
zoomOutBtn.setBackground(ColorDefaults.BUTTON_BG_COLOR);
zoomOutBtn.setFocusPainted(true);
zoomOutBtn.setHorizontalAlignment(SwingConstants.RIGHT);
zoomOutBtn.addActionListener(this);
zoomOutBtn.setActionCommand(zoomOutLabel);
zoomInBtn = new JButton(plusIcon1);
zoomInBtn.getAccessibleContext().setAccessibleName(zoomInLabel);
String zoomInBtnAD = "Navigation Shortcut CTRL+I. Use the zoom in button to decrease the graph's X bounds. Each click will decrease "
+ "the left and right bounds by 2 units.";
zoomInBtn.getAccessibleContext().setAccessibleDescription(
zoomInBtnAD);
zoomInBtn.setToolTipText("Zoom In (CTRL + I)");
zoomInBtn.setRolloverIcon(plusIcon2);
zoomInBtn.setBorderPainted(false);
zoomInBtn.setBackground(ColorDefaults.BUTTON_BG_COLOR);
zoomInBtn.setFocusPainted(true);
zoomInBtn.setHorizontalAlignment(SwingConstants.LEFT);
zoomInBtn.addActionListener(this);
zoomInBtn.setActionCommand(zoomInLabel);
resetBtn = new JButton(renameIcon1);
resetBtn.getAccessibleContext().setAccessibleName(resetLabel);
String resetBtnAD = "Navigation Shortcut CTRL+R. The Reset button changes the graph bounds back to the default values. ";
resetBtn.getAccessibleContext()
.setAccessibleDescription(resetBtnAD);
resetBtn.setToolTipText("Reset the graph (CTRL + R)");
resetBtn.setRolloverIcon(renameIcon2);
resetBtn.setBorderPainted(false);
resetBtn.setBackground(ColorDefaults.BUTTON_BG_COLOR);
resetBtn.setFocusPainted(true);
resetBtn.setActionCommand(resetLabel);
resetBtn.addActionListener(this);
graphButton = new JButton(graphIcon1);
graphButton.getAccessibleContext().setAccessibleName(
"Draw The Graph");
String graphButtonAD = "Use the Draw button to draw the graph. The graph is automatically drawn when you type or "
+ "select an equation and hit enter, when you load a data file, and when you view physics results.";
graphButton.getAccessibleContext().setAccessibleDescription(
graphButtonAD);
graphButton.setToolTipText("Draw the Graph");
graphButton.setRolloverIcon(graphIcon2);
graphButton.setDisabledIcon(graphIcon1);
graphButton.setBorderPainted(false);
graphButton.setBackground(ColorDefaults.BUTTON_BG_COLOR);
graphButton.setFocusPainted(true);
graphButton.setActionCommand(graphLabel);
graphButton.addActionListener(this);
} // end try
catch (IOException ioe) {
throw new RuntimeException(
"Missing a gif file for one or more display control buttons");
}
}
/**
* Set the scale factor for zooming in and out. The default for this class
* is SQRT(2).
*
* @param scaleFactor the scale factor for zooming
*/
public void setScaleFactor(double scaleFactor) {
if (scaleFactor <= 1.0) {
throw new IllegalArgumentException("Scale-factor must be > 1");
}
this.scaleFactor = scaleFactor;
}
/**
* Return the current scale factor used for zoom in/out calculations.
*
* @return the current scale factor used for zoom in/out calculations.
*/
public double getScaleFactor() {
return scaleFactor;
}
/**
* zoomGraph computes new bounds for a zoom in or a zoom out request. It
* calls Solver.solve to recompute the graph solution over the new bounds.
* External classes can make use of this zoomGraph method (if desired),
* e.g., for implementation of keyboard shortcuts that execute zooming.
*
* @param zoomIn
* true if we want to zoom in on the graph. false if we want to
* zoom out.
*/
public void zoomGraph(boolean zoomIn) {
double left = solver.getLeft();
double right = solver.getRight();
double top = solver.getTop();
double bottom = solver.getBottom();
if (left > right) {
double tmp = left;
left = right;
right = tmp;
} else if (left == right) {
left -= 1.0E-12;
right += 1.0E-12;
}
if (bottom > top) {
double tmp = bottom;
bottom = top;
top = tmp;
} else if (bottom == top) {
bottom -= 1.0E-12;
top += 1.0E-12;
}
double xCenter = (left + right) / 2.0;
double yCenter = (top + bottom) / 2.0;
double newHalfWidth, newHalfHeight;
if (zoomIn) {
// Zoom in
newHalfWidth = (right - left) / (2.0 * scaleFactor);
newHalfHeight = (top - bottom) / (2.0 * scaleFactor);
} else {
// Zoom out
newHalfWidth = scaleFactor * (right - left) / 2.0;
newHalfHeight = scaleFactor * (top - bottom) / 2.0;
}
double newLeft = xCenter - newHalfWidth;
double newRight = xCenter + newHalfWidth;
double newTop = yCenter + newHalfHeight;
double newBottom = yCenter - newHalfHeight;
solver.solve(newLeft, newRight, newTop, newBottom);
}
/**
* Performs the appropriate action depending on which of the four buttons
* have been pressed. zoomGraph is called with the appropriate flag if one
* of the zoom buttons was pressed. Solver.solve is called with default
* bounds if the reset button was pressed. Solver.solve is called with the
* current bounds if the draw button was pressed.
*
* @param evt the action event.
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent evt) {
String arg = evt.getActionCommand();
if (arg.equals(zoomOutLabel)) {
zoomGraph(ZOOM_OUT);
} else if (arg.equals(zoomInLabel)) {
zoomGraph(ZOOM_IN);
} else if (arg.equals(resetLabel)) {
// Reset the solution by using the preferred bounds.
solver.solve(solver.getPreferredBounds());
} else {
solver.solve();
}
}
}