//
// VisADApplet.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.browser;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.MemoryImageSource;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* An applet for connecting to a VisAD display available through a
* SocketSlaveDisplay server. The applet functions completely independently
* of VisAD, using only JDK 1.1 code, so that it can be imbedded
* within a web page for use in a web browser.
*/
public class VisADApplet extends Applet
implements ActionListener, MouseListener, MouseMotionListener, WidgetListener
{
/**
* Debugging flag.
*/
private static final boolean DEBUG = false;
/**
* The default host name for the SocketSlaveDisplay server.
*/
private static final String DEFAULT_HOST = "localhost";
/**
* The default port at which to connect.
*/
private static final int DEFAULT_PORT = 4567;
/**
* Code for refresh action.
*/
public static final int REFRESH = 0;
/**
* Code for mouse event action.
*/
public static final int MOUSE_EVENT = 1;
/**
* Code for message action.
*/
public static final int MESSAGE = 2;
/**
* Whether the applet client is connected to a server.
*/
private boolean connected = false;
/**
* IP address of the server.
*/
private String address = "";
/**
* Port of the server.
*/
private int port = DEFAULT_PORT;
/**
* Currently connected socket.
*/
private Socket socket = null;
/**
* Output stream of currently connected socket.
*/
private DataOutputStream out = null;
/**
* ID number.
*/
private int id;
/**
* Latest image from the server's display.
*/
private Image image = null;
/**
* Text field for typing in IP address of server.
*/
private TextField addressField;
/**
* Text field for typing in port of server.
*/
private TextField portField;
/**
* Button for connecting to the specified IP address and port.
*/
private Button connectButton;
/**
* Canvas for painting remote display image from the server.
*/
private Component canvas;
/**
* Frame for display widgets.
*/
private Frame frame;
/**
* Layout manager for widget frame.
*/
private GridBagLayout widgetLayout;
/**
* GridBagConstraints object for use in widget layout.
*/
private GridBagConstraints constraints;
/**
* Thread for communicating with server.
*/
private Thread commThread = null;
/**
* Hashtable for storing widgets.
*/
private Hashtable hashtable = new Hashtable();
private Applet myself;
/**
* Adds a component to the applet with the specified constraints.
*/
private void addComponent(Component c, GridBagLayout layout,
int x, int y, int w, int h, double wx, double wy)
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = w;
gbc.gridheight = h;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = wx;
gbc.weighty = wy;
layout.setConstraints(c, gbc);
add(c);
}
/**
* Adds a widget to the control panel.
*/
private void addWidget(Widget widget, String hash) {
// add widget to hashtable
hashtable.put(hash, widget);
// add widget to control panel
if (constraints.gridy > 0) {
Divider divider = new Divider();
widgetLayout.setConstraints(divider, constraints);
constraints.gridy++;
frame.add(divider);
}
widgetLayout.setConstraints(widget, constraints);
constraints.gridy++;
frame.add(widget);
frame.pack();
frame.setVisible(true);
}
/**
* Removes all widgets from the control panel.
*/
private synchronized void removeAllWidgets() {
// clear hashtable
hashtable = new Hashtable();
// clear control panel
constraints.gridy = 0;
frame.setVisible(false);
frame.removeAll();
}
/**
* Initializes the applet and lays out its GUI.
*/
public void init() {
// set background to white
setBackground(Color.white);
// lay out components with GridBagLayout
GridBagLayout gridbag = new GridBagLayout();
setLayout(gridbag);
myself = (Applet)this;
// construct GUI components
addressField = new TextField(DEFAULT_HOST);
portField = new TextField("" + DEFAULT_PORT, 4);
connectButton = new Button("Connect");
canvas = new Component() {
public void update(Graphics g) { paint(g); }
public void paint(Graphics g) {
if (connected) {
// connected; paint the remote display's image
if (image != null) {
g.drawImage(image, 0, 0, this);
}
}
else {
// not connected; paint directions on how to connect
g.setColor(Color.black);
g.drawString("VisADApplet", 80, 20);
g.drawString("To connect to a VisAD display available", 10, 50);
g.drawString("through a SocketSlaveDisplay server, type", 10, 70);
g.drawString("the IP address of the server into the IP", 10, 90);
g.drawString("address field and type the port of the", 10, 110);
g.drawString("server into the port field, then press", 10, 130);
g.drawString("the Connect button.", 10, 150);
}
}
};
// respond to GUI component events
addressField.addActionListener(this);
portField.addActionListener(this);
connectButton.addActionListener(this);
canvas.addMouseListener(this);
canvas.addMouseMotionListener(this);
// lay out GUI components
addComponent(new Label("IP address"), gridbag, 0, 0, 1, 1, 0.0, 0.0);
addComponent(addressField, gridbag, 1, 0, 1, 1, 1.0, 0.0);
addComponent(new Label("Port"), gridbag, 2, 0, 1, 1, 0.0, 0.0);
addComponent(portField, gridbag, 3, 0, 1, 1, 0.0, 0.0);
addComponent(connectButton, gridbag, 4, 0, 1, 1, 0.0, 0.0);
addComponent(canvas, gridbag, 0, 1, 5, 1, 1.0, 1.0);
// construct widget frame
frame = new Frame("Controls");
widgetLayout = new GridBagLayout();
frame.setLayout(widgetLayout);
constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.fill = GridBagConstraints.BOTH;
frame.setBackground(Widget.PALE_GRAY);
}
/**
* Close the current server connection
*/
public void disconnect() {
if (connected) {
connected = false;
// remove all widget listeners
for (int i=0; i<frame.getComponentCount(); i++) {
Component c = frame.getComponent(i);
if (c instanceof Widget) {
Widget w = (Widget) frame.getComponent(i);
w.removeWidgetListener(this);
}
}
// remove all widgets from control panel
removeAllWidgets();
repaint();
}
}
/**
* Requests a refresh from the server.
*/
private void requestRefresh() {
if (out != null) {
try {
out.writeInt(id);
out.writeInt(REFRESH);
}
catch (SocketException exc) {
// problem communicating with the server; it has probably disconnected
disconnect();
}
catch (IOException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
}
}
/**
* Sends the specified mouse event through the socket to the server.
*/
private void sendEvent(MouseEvent e) {
int mid = e.getID();
long when = e.getWhen();
int mods = e.getModifiers();
int x = e.getX();
int y = e.getY();
int clicks = e.getClickCount();
boolean popup = e.isPopupTrigger();
if (out != null) {
try {
out.writeInt(id);
out.writeInt(MOUSE_EVENT);
out.writeInt(mid);
out.writeLong(when);
out.writeInt(mods);
out.writeInt(x);
out.writeInt(y);
out.writeInt(clicks);
out.writeBoolean(popup);
}
catch (SocketException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
catch (IOException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
}
}
/**
* Sends the specified message through the socket to the server.
*/
private void sendMessage(String message) {
if (out != null) {
try {
out.writeInt(id);
out.writeInt(MESSAGE);
out.writeInt(message.length());
out.writeChars(message);
}
catch (SocketException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
catch (IOException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
}
}
/**
* Fired when a button is pressed or enter is pressed in a text box.
*/
public synchronized void actionPerformed(ActionEvent e) {
// highlight the connect button to indicate that connection is happening
connectButton.requestFocus();
// obtain the new IP address and port
String address = addressField.getText();
int port = this.port;
try {
port = Integer.parseInt(portField.getText());
}
catch (NumberFormatException exc) { }
portField.setText("" + port);
// connect to the new IP address and port
Socket sock = null;
try {
sock = new Socket(address, port);
}
catch (UnknownHostException exc) {
addressField.setText("" + this.address);
}
catch (IOException exc) {
if (DEBUG) exc.printStackTrace();
}
if (sock == null) return;
if (connected) {
// kill the old socket
try {
socket.close();
}
catch (IOException exc) {
if (DEBUG) exc.printStackTrace();
}
// wait for old communication thread to die
disconnect();
while (commThread.isAlive()) {
try {
Thread.sleep(100);
}
catch (InterruptedException exc) {
if (DEBUG) exc.printStackTrace();
}
}
}
// finish setting up new socket
socket = sock;
DataInputStream sin = null;
try {
sin = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
// get client ID number from server
id = 0;
while (true) {
id = sin.readInt();
if (id == 0) {
try {
Thread.sleep(100);
}
catch (InterruptedException exc) { }
}
else break;
}
connected = true;
}
catch (IOException exc) {
// problem communicating with server; it has probably disconnected
disconnect();
}
// set a new thread to manage communication with the server
final DataInputStream in = sin;
final VisADApplet applet = this;
commThread = new Thread(new Runnable() {
public void run() {
try {
// request a refresh so that the server sends the image
requestRefresh();
// loop until the socket gets closed
while (connected) {
// read the latest display image
int w = in.readInt();
if (w == 0) continue;
if (w == -1) {
// server is sending a message
int len = in.readInt();
char[] c = new char[len];
for (int i=0; i<len; i++) c[i] = in.readChar();
String message = new String(c);
// detect message type
if (message.startsWith("visad.ScalarMap\n")) {
// message is a scalar map update
// Parse message
StringTokenizer st = new StringTokenizer(message);
st.nextToken(); // skip scalar map id token
String scalar = st.nextToken();
String displayScalar = st.nextToken();
float min = Convert.getFloat(st.nextToken());
float max = Convert.getFloat(st.nextToken());
// update hashtable
String mapHash = scalar + " " + displayScalar;
String dsHash = "." + displayScalar;
Integer index = (Integer) hashtable.get(mapHash);
if (index == null) {
// new scalar map
Integer count = (Integer) hashtable.get(dsHash);
if (count == null) {
// new display scalar
count = new Integer(0);
}
index = count;
hashtable.put(mapHash, index);
hashtable.put(dsHash, new Integer(count.intValue() + 1));
}
// Handle supported display scalar types
if (displayScalar.equals("DisplayIsoContour")) {
// iso-contour map; update associated ContourWidget
String widgetHash = "Contour" + index.intValue();
ContourWidget widget =
(ContourWidget) hashtable.get(widgetHash);
widget.setName(scalar);
widget.setRange(min, max);
}
}
else {
// message is a control update
// Parse message, which should be of the form:
// class\nnumber\nstate
// where class is the class name of the control that has
// changed, number is the index into that class name's
// control list, and state is the save string
// corresponding to the control's new state.
StringTokenizer st = new StringTokenizer(message, "\n");
String controlClass = st.nextToken();
int index = Convert.getInt(st.nextToken());
String save = st.nextToken();
// parse class name
int dotIndex = controlClass.lastIndexOf(".");
int ctrlIndex = controlClass.lastIndexOf("Control");
String widgetName =
controlClass.substring(dotIndex + 1, ctrlIndex);
// handle special cases
if (widgetName.equals("GraphicsMode")) widgetName = "GMC";
// construct widget class name and hash table key
String widgetClass = "visad.browser." + widgetName + "Widget";
String widgetHash = widgetName + index;
// get widget from hashtable
Widget widget = (Widget) hashtable.get(widgetHash);
if (widget == null) {
// widget not found; instantiate widget of the proper type
try {
widget = (Widget) Class.forName(widgetClass).newInstance();
widget.addWidgetListener(applet);
}
catch (ClassNotFoundException exc) {
if (DEBUG) {
// widget class does not exist
System.err.println("Warning: ignoring status of " +
"unknown " + widgetName + " widget.");
}
}
catch (InstantiationException exc) {
if (DEBUG) {
// widget class cannot be instantiated
System.err.println("Warning: ignoring status of " +
"invalid " + widgetName + "widget.");
}
}
catch (IllegalAccessException exc) {
if (DEBUG) {
// widget class constructor cannot be accessed
System.err.println("Warning: ignoring status of " +
"restricted " + widgetName + "widget.");
}
}
if (widget != null) addWidget(widget, widgetHash);
}
if (widget != null) {
// set widget's state to match save string from message
widget.setSaveString(save);
}
}
}
else {
// server is sending an image
int h = in.readInt();
int len = in.readInt();
byte[] pixels = new byte[len];
int p = 0;
while (p < len) p += in.read(pixels, p, len - p);
int[] pix = Convert.bytesToInt(pixels);
// decode pixels from RLE
int[] decoded = Convert.decodeRLE(pix);
// reconstruct the image locally
if (image != null) image.flush();
image = createImage(new MemoryImageSource(w, h, decoded, 0, w));
MediaTracker tracker = new MediaTracker(myself);
tracker.addImage(image,0);
try { tracker.waitForAll(); }
catch (Exception tex) {;}
// redraw the applet's display canvas
canvas.paint(canvas.getGraphics());
}
}
}
catch (SocketException exc) {
// problem communicating with server; it has probably disconnected
applet.disconnect();
}
catch (IOException exc) {
// problem communicating with server; it has probably disconnected
applet.disconnect();
}
}
});
commThread.start();
}
public void mouseClicked(MouseEvent e) {
// This event currently generates a "type not recognized" error
// sendEvent(e);
}
public void mouseEntered(MouseEvent e) {
sendEvent(e);
}
public void mouseExited(MouseEvent e) {
sendEvent(e);
}
public void mousePressed(MouseEvent e) {
sendEvent(e);
}
public void mouseReleased(MouseEvent e) {
sendEvent(e);
}
public void mouseDragged(MouseEvent e) {
sendEvent(e);
}
public void mouseMoved(MouseEvent e) {
// This event currently generates a "type not recognized" error
// sendEvent(e);
}
public void widgetChanged(WidgetEvent e) {
Widget widget = e.getWidget();
String widgetClass = widget.getClass().getName();
// parse class name
int dotIndex = widgetClass.lastIndexOf(".");
int wdgtIndex = widgetClass.lastIndexOf("Widget");
String widgetName = widgetClass.substring(dotIndex + 1, wdgtIndex);
// handle special cases
String controlName = widgetName;
if (controlName.equals("GMC")) controlName = "GraphicsMode";
// construct control class name
String controlClass = "visad." + controlName + "Control";
String save = widget.getSaveString();
// determine widget number
int i = 0;
int index = -1;
Widget w;
do {
w = (Widget) hashtable.get(widgetName + i);
if (w == widget) {
index = i;
break;
}
i++;
}
while (w != null);
// send message to server
String message = controlClass + "\n" + index + "\n" + save;
sendMessage(message);
}
}