package edu.colostate.vchill;
import edu.colostate.vchill.ChillDefines.Mode;
import edu.colostate.vchill.ascope.ViewAScopeWindow;
import edu.colostate.vchill.bookmark.Bookmark;
import edu.colostate.vchill.bookmark.BookmarkControl;
import edu.colostate.vchill.chill.ChillMomentFieldScale;
import edu.colostate.vchill.color.XMLControl;
import edu.colostate.vchill.connection.Controller;
import edu.colostate.vchill.data.Ray;
import edu.colostate.vchill.gui.ViewFileBrowser;
import edu.colostate.vchill.gui.WindowManager;
import edu.colostate.vchill.map.MapInstruction;
import edu.colostate.vchill.map.MapTextParser;
import edu.colostate.vchill.numdump.NumDumpWindow;
import edu.colostate.vchill.plot.ViewPlotMethod;
import edu.colostate.vchill.plot.ViewPlotWindow;
import javax.security.auth.login.LoginException;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* This is the central control of the entire program and attempts to
* handle the "single choice" principle of code design. This is the
* module all of the GUI code accesses before any requests are passed
* down to the lower levels for actual data requests and storage. This
* module, combined with all the other ViewConrol* classes provide a
* method of making requests alter data in one location, making
* modification easier. NOTE: Major updates have been made and Doc is
* somewhat out of date. Future design decisions may alter the structure
* again.
*
* @author Justin Carlson
* @author Jochen Deyke
* @author Alexander Deyke
* @author jpont
* @version 2010-08-02
* @created April 7, 2003
*/
public final class ViewControl {
/**
* Source for default settings - must load this first
*/
private static final Config config = Config.getInstance();
/**
* Scaling information
*/
private static final ScaleManager sm = ScaleManager.getInstance();
/**
* Used for opening/closing windows etc
*/
private static final WindowManager wm = WindowManager.getInstance();
/**
* Singleton reference
*/
private static final ViewControl vc = new ViewControl();
/**
* Color management; stores the currentl color map
*/
private final XMLControl colors = new XMLControl();
/**
* The Controller
*/
private final Controller controller;
/**
* The message for dir requests etc
*/
private ControlMessage controlMsg;
/**
* The message passing utility that is used to communicate wtih the ViewControlThread. Uses ControlMessages.
*/
private final ControlSyncQueue<ControlMessage> plotQueue;
/**
* Instructions for plotting the map overlay
*/
private List<MapInstruction> map = new LinkedList<MapInstruction>();
/**
* for advancing in ray mode
*/
private int lastRayPlotted;
private ViewControlThread vct;
/**
* This returns a reference to the only single ViewControl
* class. This enables the program to be more modular, and
* reduce every class in the GUI from needing direct access
* to the central module.
*
* @return The ViewControl object
*/
public static ViewControl getInstance() {
return vc;
}
public ViewControlThread getViewControlThread() {
return vct;
}
/**
* Private default constructor prevents instantiation
*/
private ViewControl() {
this.controller = new Controller();
this.controlMsg = new ControlMessage(null, null, null, null);
this.plotQueue = new ControlSyncQueue<ControlMessage>();
//Create the thread that will handle all the actual plotting requests.
this.vct = new ViewControlThread(this.plotQueue, this.controller);
Thread plotThread = new Thread(this.vct, "ViewControlThread");
plotThread.setPriority(Thread.MIN_PRIORITY);
plotThread.start();
}
/**
* @param controlURL server:port to connect to for antenna control
* @param dataURL server:port to connect to for realtime data
*/
public synchronized void startRadarControl(final String controlURL, final String dataURL) {
this.setCurrentURL(dataURL);
try {
this.controller.createRealtimeConnection(dataURL);
} catch (SocketTimeoutException ste) {
System.err.println("Can't connect: Connection timed out");
DialogUtil.showErrorDialog("Connection failed", ste.getMessage());
} catch (Exception e) {
System.err.println("Can't connect:");
e.printStackTrace();
DialogUtil.showErrorDialog("Connection failed", e.getMessage());
}
}
public synchronized void setCurrentURL(final String url) {
String oldUrl = this.controlMsg.getURL();
this.controlMsg = this.controlMsg.setURL(url);
if (url == null || !url.equals(oldUrl)) sm.switchServer();
}
public synchronized void setCurrentFile(final String filename) {
this.controlMsg = this.controlMsg.setFile(filename);
}
public synchronized void setCurrentDirectory(final String dir) {
this.controlMsg = this.controlMsg.setDir(dir);
}
public synchronized void setCurrentSweep(final String sweep) {
this.controlMsg = this.controlMsg.setSweep(sweep);
newPlot();
}
public synchronized ControlMessage getControlMessage() {
return this.controlMsg;
}
public void addDirectory(final String dir) {
this.addFile(new File(dir));
}
public void addFile(final File file) {
this.controller.addFile(file);
}
public Collection<String> getConnections() {
return this.controller.getConnectionList();
}
/**
* Gets the list of available (sub-)directories and files
*/
public Collection<String> getDirectory() {
try {
ControlMessage currMsg = this.getControlMessage();
return this.controller.getDirectory(currMsg);
} catch (Exception e) {
System.err.println("Error in get dir:");
e.printStackTrace();
return null;
}
}
/**
* Gets the list of available sweeps
*
* @return The sweeps value
*/
public Collection<String> getSweeps() {
try {
ControlMessage currMsg = this.getControlMessage();
return this.controller.getSweepList(currMsg);
} catch (Exception e) {
System.err.println("Error in get Sweeps:");
e.printStackTrace();
return null;
}
}
//Connection options.
public synchronized void connect(String url) {
System.out.println("ViewControl: trying to connect to: " + url);
this.setCurrentURL(url);
try {
this.controller.connect(this.controlMsg);
/*
* The 2 servers on vchill together serve all the data and so it's
* important to connect to both.
*/
if (url.equalsIgnoreCase("vchill.chill.colostate.edu:2510")) {
/* Connect to the new server as well. */
this.setCurrentURL("vchill.chill.colostate.edu:2513");
this.controller.connect(this.controlMsg);
} else if (url.equalsIgnoreCase("vchill.chill.colostate.edu:2513")) {
/* Connect to the old server as well. */
this.setCurrentURL("vchill.chill.colostate.edu:2510");
this.controller.connect(this.controlMsg);
}
} catch (LoginException le) {
this.setCurrentURL(null);
} catch (SocketTimeoutException ste) {
System.err.println("Can't connect: Connection timed out");
this.setCurrentURL(null);
DialogUtil.showErrorDialog("Connection failed", ste.getMessage());
} catch (Exception e) {
System.err.println("Can't connect:");
e.printStackTrace();
DialogUtil.showErrorDialog("Connection failed", e.getMessage());
}
}
public boolean isConnected(final String url) {
ControlMessage currMsg = this.getControlMessage();
try {
return this.controller.isConnected(currMsg.setURL(url));
} catch (Exception e) {
return false;
}
}
public boolean isConnected() {
ControlMessage currMsg = this.getControlMessage();
try {
return this.controller.isConnected(currMsg);
} catch (Exception e) {
return false;
}
}
public synchronized void reconnect() {
System.out.println("ViewControl: trying to reconnect to: " + this.controlMsg.getURL());
try {
this.controller.reconnect(this.controlMsg);
} catch (IOException ioe) {
System.out.println("Error reconnecting");
ioe.printStackTrace();
}
}
public synchronized void reconnect(String url) {
System.out.println("ViewControl: trying to reconnect to: " + url);
try {
this.controller.reconnect(this.controlMsg.setURL(url));
} catch (IOException ioe) {
System.out.println("Error reconnecting");
ioe.printStackTrace();
}
}
public synchronized void disconnect() {
this.disconnect(this.controlMsg);
}
public synchronized void disconnect(final String url) {
this.setCurrentURL(url);
this.disconnect();
}
public synchronized void disconnect(final ControlMessage msg) {
System.out.println("ViewControl: attempting to disconnect");
try {
this.controller.disconnect(msg);
} catch (Exception e) {
System.err.println("Disconnection failed: " + e);
e.printStackTrace();
DialogUtil.showErrorDialog("Disconnect failed", e.toString());
}
}
public synchronized void stopPlot() {
System.out.println("ViewControl: Stop has been called.");
this.plotQueue.stop(); //empty the list of sweeps to plot in the view.
this.controller.stop(this.controlMsg); //stop the socket from getting enqueued requests.
}
public synchronized void setMessage(final ControlMessage msg) {
if (msg == null) return;
String oldUrl = this.controlMsg.getURL();
this.controlMsg = msg;
if (!msg.getURL().equals(oldUrl)) sm.switchServer();
newPlot();
}
public synchronized void rePlot() {
if (this.controlMsg == null) return;
switch (config.getPlottingMode()) {
case Ray:
this.plotRay(this.lastRayPlotted);
break;
default:
this.plotQueue.add(this.controlMsg);
}
}
/**
* Enqueues a request to plot
*/
private void newPlot() {
if (this.controlMsg == null || config.isRealtimeModeEnabled()) return;
switch (config.getPlottingMode()) {
case Ray:
this.plotRay(0);
break;
default:
this.plotQueue.add(this.controlMsg);
}
}
public XMLControl getColorControl() {
return this.colors;
}
/**
* Load a specific color scheme.
*
* @param filename The name of the file containing the color scheme to load
*/
public synchronized void loadColors(final String filename) {
//Check to ensure that there are in fact xml files that have been found.
if (filename == null) throw new IllegalArgumentException("Can't load colors from null");
//Load the color file if the String seems legal.
this.colors.load(filename);
wm.changeWindowColors();
edu.colostate.vchill.color.Config.getInstance().setColorFileName(filename);
}
/**
* Load map points from files specified in gui.Config
*/
public synchronized void loadMaps() {
edu.colostate.vchill.gui.Config guiconfig = edu.colostate.vchill.gui.Config.getInstance();
Collection<String> filenames = guiconfig.getMapFileNames();
Collection<String> badfiles = new LinkedList<String>();
if (filenames.size() == 0) {
this.map.clear(); //for loop won't affect, so just clear
} else {
this.map = null; //for loop will create/append, so get rid of old
}
config.setMapEnabled(filenames.size() > 0);
for (String name : filenames)
try {
this.map = new MapTextParser().parse(name, this.map);
} catch (Exception e) {
System.err.println("ERROR: Failed to load map from " + name);
e.printStackTrace();
badfiles.add(name);
}
guiconfig.removeMapFileNames(badfiles);
wm.replotOverlay();
}
/**
* Sets plot color interval
*
* @param type The data type to set the scale for
* @param max The maximum value to put on the scale
* @param min The minimum value to put on the scale
*/
public synchronized void setPlotInterval(final String type, final String max, final String min) {
ChillMomentFieldScale scale = sm.getScale(type);
if (max != null) scale.setMax(Double.parseDouble(max));
if (min != null) scale.setMin(Double.parseDouble(min));
}
/**
* Gets the ray at the specified x, y location.
*
* @param plotMethod the plot method to use for determining needed info
* @param type the type of data desired
* @param x X coordinate of where the mouse was clicked
* @param y Y coordinate of where the mouse was clicked
* @return An object array consisting of the actual
* ray and the ray number.
*/
public Object[] getRay(final ViewPlotMethod plotMethod, final String type, final int x, final int y) {
ControlMessage currMsg = this.getControlMessage();
if (x != -1 && y != -1) {
int rayNum = plotMethod.getRayNumFromXY(x, y);
int maxDisplayableRays = plotMethod.getMaxDisplayableRays();
if (rayNum != -1) {
Ray ray = (Ray) this.controller.getRay(currMsg, type, rayNum);
if (ray == null)
return null;
//TH plots can wrap around and so make sure to get the
//newest ray at this particular location
while (ray != null) {
rayNum += maxDisplayableRays;
ray = (Ray) this.controller.getRay(currMsg, type, rayNum);
}
rayNum -= maxDisplayableRays;
ray = (Ray) this.controller.getRay(currMsg, type, rayNum);
return new Object[]{ray, rayNum};
}
}
return null;
}
/**
* Gets the ray at the specified azimuth.
*
* @param type the type of ray desired
* @param azimuth the angle desired in degrees
*/
public Ray getRayAtAz(final String type, final double azimuth) {
ControlMessage currMsg = this.getControlMessage();
if (!currMsg.isValid())
return null;
int i = 0;
double prevAngle, currAngle;
Ray prevRay = (Ray) this.controller.getRay(currMsg, type, i++);
Ray currRay = (Ray) this.controller.getRay(currMsg, type, i++);
prevAngle = prevRay.getStartAzimuth();
currAngle = ((Ray) this.controller.getRay(currMsg, type, 10)).getStartAzimuth();
double diff = currAngle - prevAngle;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
// Note that when figuring out which ray to get that
// gaps in the display are filled in by using color
// data from the current ray and not the previous ray.
if (diff > 0) { //increasing azimuth
while (currRay != null) {
prevAngle = prevRay.getStartAzimuth();
currAngle = currRay.getStartAzimuth();
if (azimuth > prevAngle && azimuth <= currAngle) {
plotRay(i - 1);
return currRay;
}
prevRay = currRay;
currRay = (Ray) this.controller.getRay(currMsg, type, i++);
}
} else if (diff < 0) { //decreasing azimuth
while (currRay != null) {
prevAngle = prevRay.getStartAzimuth();
currAngle = currRay.getStartAzimuth();
if (azimuth <= prevAngle && azimuth > currAngle) {
plotRay(i - 2);
return prevRay;
}
prevRay = currRay;
currRay = (Ray) this.controller.getRay(currMsg, type, i++);
}
}
return null;
}
/**
* Gets the ray at the specified elevation.
*
* @param type the type of ray desired
* @param elevation the desired elevation angle
*/
public Ray getRayAtEl(final String type, final double elevation) {
ControlMessage currMsg = this.getControlMessage();
if (!currMsg.isValid())
return null;
int i = 0;
double prevAngle, currAngle;
Ray prevRay = (Ray) this.controller.getRay(currMsg, type, i++);
Ray currRay = (Ray) this.controller.getRay(currMsg, type, i++);
prevAngle = prevRay.getStartElevation();
if (prevAngle > 180) prevAngle -= 360;
currAngle = ((Ray) this.controller.getRay(currMsg, type, this.controller.getNumberOfRays(currMsg, type) - 1)).getStartElevation();
if (currAngle > 180) currAngle -= 360;
if (currAngle > prevAngle) { //increasing elevation
while (currRay != null) {
prevAngle = prevRay.getStartElevation();
currAngle = currRay.getStartElevation();
if (elevation >= prevAngle && elevation < currAngle) {
plotRay(i - 2);
return prevRay;
}
prevRay = currRay;
currRay = (Ray) this.controller.getRay(currMsg, type, i++);
}
} else if (currAngle < prevAngle) { //decreasing elevation
while (currRay != null) {
prevAngle = prevRay.getStartElevation();
currAngle = currRay.getStartElevation();
if (elevation < prevAngle && elevation >= currAngle) {
plotRay(i - 1);
return currRay;
}
prevRay = currRay;
currRay = (Ray) this.controller.getRay(currMsg, type, i++);
}
}
return null;
}
/**
* Retrieves the data value matching a given azimuth and range
*
* @param mode the plot mode of data desired
* @param type the type of data desired
* @param azimuth the angle desired in degrees
* @param elevation the desired elevation angle
* @param gateNum the desired gate number
* @param plotMethod the plot method to use for determining needed info
* @param x X coordinate of where the mouse was clicked
* @param y Y coordinate of where the mouse was clicked
*/
public double getDataValue(final String mode, final String type,
final double azimuth, final double elevation, final int gateNum, final ViewPlotMethod plotMethod, final int x, final int y) {
ControlMessage currMsg = this.getControlMessage();
if (!currMsg.isValid())
return Double.NaN;
try {
if (mode.equals("PPI")) {
Ray ray = getRayAtAz(type, azimuth);
if (ray == null)
return Double.NaN;
else
return ray.getData()[gateNum];
} else if (mode.equals("RHI")) {
Ray ray = getRayAtEl(type, elevation);
if (ray == null)
return Double.NaN;
else
return ray.getData()[gateNum];
} else { //constant azimuth & elevation
Object[] rayInfo = this.getRay(plotMethod, type, x, y);
if (rayInfo != null) {
plotRay((Integer) rayInfo[1]);
return ((Ray) rayInfo[0]).getData()[gateNum];
}
}
} catch (NullPointerException npe) {
System.out.println("data value readout not available");
} catch (ArrayIndexOutOfBoundsException aioobe) {
} catch (Exception e) {
System.out.println("exception: " + e);
e.printStackTrace();
}
return Double.NaN;
}
public void plotRay(final int index) {
ControlMessage currMsg = this.getControlMessage();
this.lastRayPlotted = index;
if (config.isRealtimeModeEnabled() || config.getPlottingMode() == Mode.Ray)
for (final ViewPlotWindow win : wm.getPlotList()) {
Ray prev = null;
Ray next = null;
Ray thresh = null;
try {
prev = (Ray) this.controller.getRay(currMsg, win.getType(), index - 1);
} catch (ArrayIndexOutOfBoundsException aioobe) {
} //ignore - previous not vital
try {
next = (Ray) this.controller.getRay(currMsg, win.getType(), index + 1);
} catch (ArrayIndexOutOfBoundsException aioobe) {
} //ignore - next not vital
try {
thresh = (Ray) this.controller.getRay(currMsg, config.getThresholdType(), index);
} catch (ArrayIndexOutOfBoundsException aioobe) {
} //ignore - threshold not vital
try {
Ray curr = (Ray) this.controller.getRay(currMsg, win.getType(), index);
win.plot(prev, curr, next, thresh);
win.rePlotDrawingArea();
} catch (ArrayIndexOutOfBoundsException aioobe) {
continue;
} //better luck next window
}
for (final ViewAScopeWindow win : wm.getAScopeList()) {
Ray secondary = null;
try {
secondary = (Ray) this.controller.getRay(currMsg, win.getSecondary(), index);
} catch (ArrayIndexOutOfBoundsException aioobe) {
} //ignore - secondary not vital
try {
Ray primary = (Ray) this.controller.getRay(currMsg, win.getType(), index);
win.plot(primary, secondary);
win.repaint(win.getVisibleRect());
} catch (ArrayIndexOutOfBoundsException aioobe) {
continue;
} //better luck next window
}
for (final NumDumpWindow win : wm.getNumDumpList()) {
try {
Ray ray = (Ray) this.controller.getRay(currMsg, win.getType(), index);
Ray thr = (Ray) this.controller.getRay(currMsg, config.getThresholdType(), index);
win.plot(ray, thr);
} catch (ArrayIndexOutOfBoundsException aioobe) {
continue;
} //better luck next window
}
}
/**
* Goes to the next sweep in the current volume if volume mode is enabled
*/
public synchronized void sweepDone() {
if (!this.plotQueue.stopping()) {
switch (config.getPlottingMode()) {
case Volume:
ViewFileBrowser.getInstance().getActions().selectNext();
break;
case Continuous:
ViewFileBrowser.getInstance().getActions().selectNextAcrossBounds();
break;
}
}
}
/**
* Goes to the first ray in the current sweep
*/
public synchronized void rayFirst() {
plotRay(0);
}
/**
* Goes to the previous ray in the current sweep
*/
public synchronized void rayPrev() {
plotRay(Math.max(0, this.lastRayPlotted - config.getRayStep()));
}
/**
* Goes to the next ray in the current sweep
*/
public synchronized void rayNext() {
plotRay(Math.min(
this.controller.getNumberOfRays(this.controlMsg, config.getThresholdType()) - 1,
this.lastRayPlotted + config.getRayStep()));
}
/**
* Goes to the last ray in the current sweep
*/
public synchronized void rayLast() {
plotRay(this.controller.getNumberOfRays(
this.controlMsg, config.getThresholdType()) - 1);
}
/**
* Returns the map overlay for plotting
*
* @return a List of MapInstructions for plotting the map overlay
*/
public synchronized List<MapInstruction> getMap() {
return this.map;
}
public synchronized void createBookmark() {
this.createBookmark(this.controlMsg);
}
public synchronized void createBookmark(final ControlMessage msg) {
if (msg == null) {
DialogUtil.showErrorDialog("No sweep selected", "Please select a sweep first");
return;
}
BookmarkControl bmc = BookmarkControl.getInstance();
Bookmark bookmark = new Bookmark();
bookmark.url = msg.getURL();
bookmark.dir = msg.getDir();
bookmark.file = msg.getFile();
bookmark.sweep = msg.getSweep();
if (bookmark.url == null || bookmark.dir == null || bookmark.file == null || bookmark.sweep == null) {
DialogUtil.showErrorDialog("No sweep selected", "Please select a sweep first");
return;
}
bookmark.dir = bookmark.dir.split(" ")[0]; //chop off extra data
String[] bits = bookmark.file.split(" ");
bookmark.scan_type = bits.length > 1 ? bits[bits.length - 1] : "???";
bookmark.file = bookmark.file.split(" ")[0];
for (String type : sm.getTypes()) {
ChillMomentFieldScale scale = sm.getScale(type);
Bookmark.Scale bmScale = new Bookmark.Scale();
bmScale.autoscale = false;
bmScale.minval = Double.toString(scale.getMin());
bmScale.maxval = Double.toString(scale.getMax());
bookmark.scale.put(type, bmScale);
}
bookmark.x = config.getCenterX();
bookmark.y = config.getCenterY();
bookmark.range = config.getPlotRange();
bookmark.rhi_height = Double.toString(config.getMaxPlotHeight());
bookmark.ring = Integer.toString(config.getGridSpacing());
//strip off prefix
Collection<String> cats = bmc.getCategoryList();
ArrayList<String> choices = new ArrayList<String>(cats.size());
for (String cat : cats)
if (cat.startsWith(BookmarkControl.USER_PREFIX))
choices.add(cat.substring(cat.indexOf(":") + 1, cat.length()));
//category
final JComboBox category = new JComboBox(choices.toArray());
category.setEditable(true);
final JLabel categoryLabel = new JLabel("Category: ");
categoryLabel.setDisplayedMnemonic('C');
categoryLabel.setLabelFor(category);
//name
final JTextField name = new JTextField();
final JLabel nameLabel = new JLabel("Name: ");
nameLabel.setDisplayedMnemonic('N');
nameLabel.setLabelFor(name);
//comment
final JTextArea comment = new JTextArea(25, 40);
final JLabel commentLabel = new JLabel("Comment: ");
commentLabel.setDisplayedMnemonic('m');
commentLabel.setLabelFor(comment);
//put it together
JOptionPane pane = new JOptionPane(new Object[]{
categoryLabel, category, "\n",
nameLabel, name, "\n",
commentLabel, new JScrollPane(comment),
}, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
pane.setWantsInput(false);
JDialog dialog = pane.createDialog(null, "Create Bookmark");
dialog.pack();
dialog.setVisible(true);
//get result
Integer value = (Integer) pane.getValue();
if (value == null || value.intValue() == JOptionPane.CANCEL_OPTION || value.intValue() == JOptionPane.CLOSED_OPTION)
return;
bookmark.comment = comment.getText();
bmc.addBookmark(BookmarkControl.USER_PREFIX + category.getSelectedItem(), name.getText(), bookmark);
}
public void clearCache() {
System.out.println("Clearing cache");
this.controller.clearCache();
}
}