summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tesseract/java/com/google/scrollview')
-rw-r--r--tesseract/java/com/google/scrollview/Makefile.am4
-rw-r--r--tesseract/java/com/google/scrollview/ScrollView.java403
-rw-r--r--tesseract/java/com/google/scrollview/events/Makefile.am5
-rw-r--r--tesseract/java/com/google/scrollview/events/SVEvent.java87
-rw-r--r--tesseract/java/com/google/scrollview/events/SVEventHandler.java301
-rw-r--r--tesseract/java/com/google/scrollview/events/SVEventType.java31
-rw-r--r--tesseract/java/com/google/scrollview/ui/Makefile.am7
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVAbstractMenuItem.java57
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVCheckboxMenuItem.java57
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVEmptyMenuItem.java46
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVImageHandler.java74
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVMenuBar.java130
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVMenuItem.java60
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVPopupMenu.java144
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVSubMenuItem.java39
-rw-r--r--tesseract/java/com/google/scrollview/ui/SVWindow.java648
16 files changed, 2093 insertions, 0 deletions
diff --git a/tesseract/java/com/google/scrollview/Makefile.am b/tesseract/java/com/google/scrollview/Makefile.am
new file mode 100644
index 00000000..7314b233
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/Makefile.am
@@ -0,0 +1,4 @@
+SUBDIRS = events ui
+
+EXTRA_DIST = \
+ ScrollView.java
diff --git a/tesseract/java/com/google/scrollview/ScrollView.java b/tesseract/java/com/google/scrollview/ScrollView.java
new file mode 100644
index 00000000..c3494af3
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ScrollView.java
@@ -0,0 +1,403 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview;
+
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.ui.SVImageHandler;
+import com.google.scrollview.ui.SVWindow;
+import org.piccolo2d.nodes.PImage;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * The ScrollView class is the main class which gets started from the command
+ * line. It sets up LUA and handles the network processing.
+ * @author wanke@google.com
+ */
+public class ScrollView {
+
+ /** The port our server listens at. */
+ public static int SERVER_PORT = 8461;
+
+ /**
+ * All SVWindow objects share the same connection stream. The socket is needed
+ * to detect when the connection got closed, in/out are used to send and
+ * receive messages.
+ */
+ private static Socket socket;
+ private static PrintStream out;
+ public static BufferedReader in;
+ public static float polylineXCoords[]; // The coords being received.
+ public static float polylineYCoords[]; // The coords being received.
+ public static int polylineSize; // The size of the coords arrays.
+ public static int polylineScanned; // The size read so far.
+ private static ArrayList<SVWindow> windows; // The id to SVWindow map.
+ private static Pattern intPattern; // For checking integer arguments.
+ private static Pattern floatPattern; // For checking float arguments.
+
+ /** Keeps track of the number of messages received. */
+ static int nrInputLines = 0;
+
+ /** Prints all received messages to the console if true. */
+ static boolean debugViewNetworkTraffic = false;
+
+ /** Add a new message to the outgoing queue */
+ public static void addMessage(SVEvent e) {
+ if (debugViewNetworkTraffic) {
+ System.out.println("(S->c) " + e.toString());
+ }
+ String str = e.toString();
+ // Send the whole thing as UTF8.
+ try {
+ byte [] utf8 = str.getBytes("UTF8");
+ out.write(utf8, 0, utf8.length);
+ } catch (java.io.UnsupportedEncodingException ex) {
+ System.out.println("Oops... can't encode to UTF8... Exiting");
+ System.exit(0);
+ }
+ out.println();
+ // Flush the output and check for errors.
+ boolean error = out.checkError();
+ if (error) {
+ System.out.println("Connection error. Quitting ScrollView Server...");
+ System.exit(0);
+ }
+ }
+
+ /** Read one message from client (assuming there are any). */
+ public static String receiveMessage() throws IOException {
+ return in.readLine();
+ }
+
+ /**
+ * The main program loop. Basically loops trough receiving messages and
+ * processing them and then sending messages (if there are any).
+ */
+ private static void IOLoop() {
+ String inputLine;
+
+ try {
+ while (!socket.isClosed() && !socket.isInputShutdown() &&
+ !socket.isOutputShutdown() &&
+ socket.isConnected() && socket.isBound()) {
+ inputLine = receiveMessage();
+ if (inputLine == null) {
+ // End of stream reached.
+ break;
+ }
+ nrInputLines++;
+ if (debugViewNetworkTraffic) {
+ System.out.println("(c->S," + nrInputLines + ")" + inputLine);
+ }
+
+ if (polylineSize > polylineScanned) {
+ // We are processing a polyline.
+ // Read pairs of coordinates separated by commas.
+ boolean first = true;
+ for (String coordStr : inputLine.split(",")) {
+ int coord = Integer.parseInt(coordStr);
+ if (first) {
+ polylineXCoords[polylineScanned] = coord;
+ } else {
+ polylineYCoords[polylineScanned++] = coord;
+ }
+ first = !first;
+ }
+ assert first;
+ } else {
+ // Process this normally.
+ processInput(inputLine);
+ }
+ }
+ }
+ // Some connection error
+ catch (IOException e) {
+ System.out.println("Connection error. Quitting ScrollView Server...");
+ }
+ System.exit(0);
+ }
+
+ // Parse a comma-separated list of arguments into ArrayLists of the
+ // possible types. Each type is stored in order, but the order
+ // distinction between types is lost.
+ // Note that the format is highly constrained to what the client used
+ // to send to LUA:
+ // Quoted string -> String.
+ // true or false -> Boolean.
+ // %f format number -> Float (no %e allowed)
+ // Sequence of digits -> Integer
+ // Nothing else allowed.
+ private static void parseArguments(String argList,
+ ArrayList<Integer> intList,
+ ArrayList<Float> floatList,
+ ArrayList<String> stringList,
+ ArrayList<Boolean> boolList) {
+ // str is only non-null if an argument starts with a single or double
+ // quote. str is set back to null on completion of the string with a
+ // matching quote. If the string contains a comma then str will stay
+ // non-null across multiple argStr values until a matching closing quote.
+ // Backslash escaped quotes do not count as terminating the string.
+ String str = null;
+ for (String argStr : argList.split(",")) {
+ if (str != null) {
+ // Last string was incomplete. Append argStr to it and restore comma.
+ // Execute str += "," + argStr in Java.
+ int length = str.length() + 1 + argStr.length();
+ StringBuilder appended = new StringBuilder(length);
+ appended.append(str);
+ appended.append(",");
+ appended.append(argStr);
+ str = appended.toString();
+ } else if (argStr.length() == 0) {
+ continue;
+ } else {
+ char quote = argStr.charAt(0);
+ // If it begins with a quote then it is a string, but may not
+ // end this time if it contained a comma.
+ if (quote == '\'' || quote == '"') {
+ str = argStr;
+ }
+ }
+ if (str != null) {
+ // It began with a quote. Check that it still does.
+ assert str.charAt(0) == '\'' || str.charAt(0) == '"';
+ int len = str.length();
+ if (len > 1 && str.charAt(len - 1) == str.charAt(0)) {
+ // We have an ending quote of the right type. Now check that
+ // it is not escaped. Must have an even number of slashes before.
+ int slash = len - 1;
+ while (slash > 0 && str.charAt(slash - 1) == '\\')
+ --slash;
+ if ((len - 1 - slash) % 2 == 0) {
+ // It is now complete. Chop off the quotes and save.
+ // TODO(rays) remove the first backslash of each pair.
+ stringList.add(str.substring(1, len - 1));
+ str = null;
+ }
+ }
+ // If str is not null here, then we have a string with a comma in it.
+ // Append , and the next argument at the next iteration, but check
+ // that str is null after the loop terminates in case it was an
+ // unterminated string.
+ } else if (floatPattern.matcher(argStr).matches()) {
+ // It is a float.
+ floatList.add(Float.parseFloat(argStr));
+ } else if (argStr.equals("true")) {
+ boolList.add(true);
+ } else if (argStr.equals("false")) {
+ boolList.add(false);
+ } else if (intPattern.matcher(argStr).matches()) {
+ // Only contains digits so must be an int.
+ intList.add(Integer.parseInt(argStr));
+ }
+ // else ignore all incompatible arguments for forward compatibility.
+ }
+ // All strings must have been terminated.
+ assert str == null;
+ }
+
+ /** Executes the LUA command parsed as parameter. */
+ private static void processInput(String inputLine) {
+ if (inputLine == null) {
+ return;
+ }
+ // Execute a function encoded as a LUA statement! Yuk!
+ if (inputLine.charAt(0) == 'w') {
+ // This is a method call on a window. Parse it.
+ String noWLine = inputLine.substring(1);
+ String[] idStrs = noWLine.split("[ :]", 2);
+ int windowID = Integer.parseInt(idStrs[0]);
+ // Find the parentheses.
+ int start = inputLine.indexOf('(');
+ int end = inputLine.lastIndexOf(')');
+ // Parse the args.
+ ArrayList<Integer> intList = new ArrayList<Integer>(4);
+ ArrayList<Float> floatList = new ArrayList<Float>(2);
+ ArrayList<String> stringList = new ArrayList<String>(4);
+ ArrayList<Boolean> boolList = new ArrayList<Boolean>(3);
+ parseArguments(inputLine.substring(start + 1, end),
+ intList, floatList, stringList, boolList);
+ int colon = inputLine.indexOf(':');
+ if (colon > 1 && colon < start) {
+ // This is a regular function call. Look for the name and call it.
+ String func = inputLine.substring(colon + 1, start);
+ if (func.equals("drawLine")) {
+ windows.get(windowID).drawLine(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else if (func.equals("createPolyline")) {
+ windows.get(windowID).createPolyline(intList.get(0));
+ } else if (func.equals("drawPolyline")) {
+ windows.get(windowID).drawPolyline();
+ } else if (func.equals("drawRectangle")) {
+ windows.get(windowID).drawRectangle(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else if (func.equals("setVisible")) {
+ windows.get(windowID).setVisible(boolList.get(0));
+ } else if (func.equals("setAlwaysOnTop")) {
+ windows.get(windowID).setAlwaysOnTop(boolList.get(0));
+ } else if (func.equals("addMessage")) {
+ windows.get(windowID).addMessage(stringList.get(0));
+ } else if (func.equals("addMessageBox")) {
+ windows.get(windowID).addMessageBox();
+ } else if (func.equals("clear")) {
+ windows.get(windowID).clear();
+ } else if (func.equals("setStrokeWidth")) {
+ windows.get(windowID).setStrokeWidth(floatList.get(0));
+ } else if (func.equals("drawEllipse")) {
+ windows.get(windowID).drawEllipse(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else if (func.equals("pen")) {
+ if (intList.size() == 4) {
+ windows.get(windowID).pen(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else {
+ windows.get(windowID).pen(intList.get(0), intList.get(1),
+ intList.get(2));
+ }
+ } else if (func.equals("brush")) {
+ if (intList.size() == 4) {
+ windows.get(windowID).brush(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else {
+ windows.get(windowID).brush(intList.get(0), intList.get(1),
+ intList.get(2));
+ }
+ } else if (func.equals("textAttributes")) {
+ windows.get(windowID).textAttributes(stringList.get(0),
+ intList.get(0),
+ boolList.get(0),
+ boolList.get(1),
+ boolList.get(2));
+ } else if (func.equals("drawText")) {
+ windows.get(windowID).drawText(intList.get(0), intList.get(1),
+ stringList.get(0));
+ } else if (func.equals("addMenuBarItem")) {
+ if (boolList.size() > 0) {
+ windows.get(windowID).addMenuBarItem(stringList.get(0),
+ stringList.get(1),
+ intList.get(0),
+ boolList.get(0));
+ } else if (intList.size() > 0) {
+ windows.get(windowID).addMenuBarItem(stringList.get(0),
+ stringList.get(1),
+ intList.get(0));
+ } else {
+ windows.get(windowID).addMenuBarItem(stringList.get(0),
+ stringList.get(1));
+ }
+ } else if (func.equals("addPopupMenuItem")) {
+ if (stringList.size() == 4) {
+ windows.get(windowID).addPopupMenuItem(stringList.get(0),
+ stringList.get(1),
+ intList.get(0),
+ stringList.get(2),
+ stringList.get(3));
+ } else {
+ windows.get(windowID).addPopupMenuItem(stringList.get(0),
+ stringList.get(1));
+ }
+ } else if (func.equals("update")) {
+ windows.get(windowID).update();
+ } else if (func.equals("showInputDialog")) {
+ windows.get(windowID).showInputDialog(stringList.get(0));
+ } else if (func.equals("showYesNoDialog")) {
+ windows.get(windowID).showYesNoDialog(stringList.get(0));
+ } else if (func.equals("zoomRectangle")) {
+ windows.get(windowID).zoomRectangle(intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3));
+ } else if (func.equals("readImage")) {
+ PImage image = SVImageHandler.readImage(intList.get(2), in);
+ windows.get(windowID).drawImage(image, intList.get(0), intList.get(1));
+ } else if (func.equals("drawImage")) {
+ PImage image = new PImage(stringList.get(0));
+ windows.get(windowID).drawImage(image, intList.get(0), intList.get(1));
+ } else if (func.equals("destroy")) {
+ windows.get(windowID).destroy();
+ }
+ // else for forward compatibility purposes, silently ignore any
+ // unrecognized function call.
+ } else {
+ // No colon. Check for create window.
+ if (idStrs[1].startsWith("= luajava.newInstance")) {
+ while (windows.size() <= windowID) {
+ windows.add(null);
+ }
+ windows.set(windowID, new SVWindow(stringList.get(1),
+ intList.get(0), intList.get(1),
+ intList.get(2), intList.get(3),
+ intList.get(4), intList.get(5),
+ intList.get(6)));
+ }
+ // else for forward compatibility purposes, silently ignore any
+ // unrecognized function call.
+ }
+ } else if (inputLine.startsWith("svmain")) {
+ // Startup or end. Startup is a lua bind, which is now a no-op.
+ if (inputLine.startsWith("svmain:exit")) {
+ exit();
+ }
+ // else for forward compatibility purposes, silently ignore any
+ // unrecognized function call.
+ }
+ // else for forward compatibility purposes, silently ignore any
+ // unrecognized function call.
+ }
+
+ /** Called from the client to make the server exit. */
+ public static void exit() {
+ System.exit(0);
+ }
+
+ /**
+ * The main function. Sets up LUA and the server connection and then calls the
+ * IOLoop.
+ */
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ SERVER_PORT = Integer.parseInt(args[0]);
+ }
+ windows = new ArrayList<SVWindow>(100);
+ intPattern = Pattern.compile("[0-9-][0-9]*");
+ floatPattern = Pattern.compile("[0-9-][0-9]*\\.[0-9]*");
+
+ // Open a socket to listen on.
+ try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
+ System.out.println("Socket started on port " + SERVER_PORT);
+
+ // Wait (blocking) for an incoming connection
+ socket = serverSocket.accept();
+ System.out.println("Client connected");
+
+ // Setup the streams
+ out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
+ in =
+ new BufferedReader(new InputStreamReader(socket.getInputStream(),
+ "UTF8"));
+ } catch (IOException e) {
+ // Something went wrong and we were unable to set up a connection. This is
+ // pretty
+ // much a fatal error.
+ // Note: The server does not get restarted automatically if this happens.
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ // Enter the main program loop.
+ IOLoop();
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/events/Makefile.am b/tesseract/java/com/google/scrollview/events/Makefile.am
new file mode 100644
index 00000000..ec3a21ff
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/events/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS =
+
+EXTRA_DIST = \
+ SVEvent.java SVEventHandler.java \
+ SVEventType.java
diff --git a/tesseract/java/com/google/scrollview/events/SVEvent.java b/tesseract/java/com/google/scrollview/events/SVEvent.java
new file mode 100644
index 00000000..18309c2f
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/events/SVEvent.java
@@ -0,0 +1,87 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.events;
+
+import com.google.scrollview.ui.SVWindow;
+
+/**
+ * The SVEvent is a structure which holds the actual values of a message to be
+ * transmitted. It corresponds to the client structure defined in scrollview.h
+ *
+ * @author wanke@google.com
+ */
+public class SVEvent {
+ SVEventType type; // What kind of event.
+ SVWindow window; // Window event relates to.
+ int x; // Coords of click or selection.
+ int y;
+ int xSize; // Size of selection.
+ int ySize;
+ int commandId;
+ String parameter; // Any string that might have been passed as argument.
+
+ /**
+ * A "normal" SVEvent.
+ *
+ * @param t The type of the event as specified in SVEventType (e.g.
+ * SVET_CLICK)
+ * @param w The window the event corresponds to
+ * @param x1 X position of the mouse at the time of the event
+ * @param y1 Y position of the mouse at the time of the event
+ * @param x2 X selection size at the time of the event
+ * @param y2 Y selection size at the time of the event
+ * @param p A parameter associated with the event (e.g. keyboard input)
+ */
+ public SVEvent(SVEventType t, SVWindow w, int x1, int y1, int x2, int y2,
+ String p) {
+ type = t;
+ window = w;
+ x = x1;
+ y = y1;
+ xSize = x2;
+ ySize = y2;
+ commandId = 0;
+ parameter = p;
+ }
+
+ /**
+ * An event which issues a command (like clicking on a item in the menubar).
+ *
+ * @param eventtype The type of the event as specified in SVEventType
+ * (usually SVET_MENU or SVET_POPUP)
+ * @param svWindow The window the event corresponds to
+ * @param commandid The associated id with the command (given by the client
+ * on construction of the item)
+ * @param value A parameter associated with the event (e.g. keyboard input)
+ */
+ public SVEvent(SVEventType eventtype, SVWindow svWindow, int commandid,
+ String value) {
+ type = eventtype;
+ window = svWindow;
+
+ parameter = value;
+ x = 0;
+ y = 0;
+ xSize = 0;
+ ySize = 0;
+ commandId = commandid;
+ }
+
+ /**
+ * This is the string representation of the message, which is what will
+ * actually be transferred over the network.
+ */
+ @Override
+ public String toString() {
+ return (window.hash + "," + type.ordinal() + "," + x + "," + y + ","
+ + xSize + "," + ySize + "," + commandId + "," + parameter);
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/events/SVEventHandler.java b/tesseract/java/com/google/scrollview/events/SVEventHandler.java
new file mode 100644
index 00000000..26a92bdb
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/events/SVEventHandler.java
@@ -0,0 +1,301 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.events;
+
+import com.google.scrollview.ScrollView;
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.events.SVEventType;
+import com.google.scrollview.ui.SVWindow;
+
+import org.piccolo2d.PCamera;
+import org.piccolo2d.PNode;
+import org.piccolo2d.event.PBasicInputEventHandler;
+import org.piccolo2d.event.PInputEvent;
+import org.piccolo2d.nodes.PPath;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.Window;
+
+import javax.swing.Timer;
+
+/**
+ * The ScrollViewEventHandler takes care of any events which might happen on the
+ * canvas and converts them to an according SVEvent, which is (using the
+ * processEvent method) then added to a message queue. All events from the
+ * message queue get sent gradually
+ *
+ * @author wanke@google.com
+ */
+public class SVEventHandler extends PBasicInputEventHandler implements
+ ActionListener, KeyListener, WindowListener {
+
+ /** Necessary to wait for a defined period of time (for SVET_HOVER). */
+ public Timer timer;
+
+ /** The window which the event corresponds to. */
+ private SVWindow svWindow;
+
+ /** These are used to determine a selection size (for SVET_SELECTION). */
+ private int lastX = 0;
+ private int lastY = 0;
+
+ /**
+ * These are used in case we want to transmit our position, but do not get it
+ * because it was no MouseEvent, in particular SVET_HOVER and SVET_INPUT.
+ */
+ private int lastXMove = 0;
+ private int lastYMove = 0;
+
+ /** For Drawing a rubber-band rectangle for selection */
+ private int startX = 0;
+ private int startY = 0;
+ private float rubberBandTransparency = 0.5f;
+ private PNode selection = null;
+
+ /** The string entered since the last enter. Since the client
+ * end eats all newlines, we can't use the newline
+ * character, so use ! for now, as it cannot be entered
+ * directly anyway and therefore can never show up for real. */
+ private String keyStr = "!";
+
+ /** Setup the timer. */
+ public SVEventHandler(SVWindow wdw) {
+ timer = new Timer(1000, this);
+ svWindow = wdw;
+ }
+
+ /**
+ * Store the newest x,y values, add the message to the queue and restart the
+ * timer.
+ */
+ private void processEvent(SVEvent e) {
+ lastXMove = e.x;
+ lastYMove = e.y;
+ ScrollView.addMessage(e);
+ timer.restart();
+ }
+
+ /** Show the associated popup menu at (x,y) (relative position of the window). */
+ private void showPopup(PInputEvent e) {
+ double x = e.getCanvasPosition().getX();
+ double y = e.getCanvasPosition().getY();
+
+ if (svWindow.svPuMenu != null) {
+ svWindow.svPuMenu.show(svWindow, (int) x, (int) y);
+ }
+ }
+
+
+ /** The mouse is clicked - create an SVET_CLICK event. */
+ @Override
+ public void mouseClicked(PInputEvent e) {
+ if (e.isPopupTrigger()) {
+ showPopup(e);
+ } else {
+ processEvent(new SVEvent(SVEventType.SVET_CLICK, svWindow, (int) e
+ .getPosition().getX(), (int) e.getPosition().getY(), 0, 0, null));
+ }
+ }
+
+ /**
+ * The mouse key is pressed (and keeps getting pressed).
+ * Depending on the OS, show a popup menu (if the button pressed is associated
+ * with popup menus, like the RMB under windows&linux) or otherwise save the
+ * position (in case it is a selection).
+ */
+ @Override
+ public void mousePressed(PInputEvent e) {
+ if (e.isPopupTrigger()) {
+ showPopup(e);
+ } else {
+ lastX = (int) e.getPosition().getX();
+ lastY = (int) e.getPosition().getY();
+ timer.restart();
+ }
+ }
+
+ /** The mouse is getting dragged - create an SVET_MOUSE event. */
+ @Override
+ public void mouseDragged(PInputEvent e) {
+ processEvent(new SVEvent(SVEventType.SVET_MOUSE, svWindow, (int) e
+ .getPosition().getX(), (int) e.getPosition().getY(), (int) e
+ .getPosition().getX()
+ - lastX, (int) e.getPosition().getY() - lastY, null));
+
+ // Paint a selection rectangle.
+ if (selection == null) {
+ startX = (int) e.getPosition().getX();
+ startY = (int) e.getPosition().getY();
+ selection = PPath.createRectangle(startX, startY, 1, 1);
+ selection.setTransparency(rubberBandTransparency);
+ svWindow.canvas.getLayer().addChild(selection);
+ } else {
+ int right = Math.max(startX, (int) e.getPosition().getX());
+ int left = Math.min(startX, (int) e.getPosition().getX());
+ int bottom = Math.max(startY, (int) e.getPosition().getY());
+ int top = Math.min(startY, (int) e.getPosition().getY());
+ svWindow.canvas.getLayer().removeChild(selection);
+ selection = PPath.createRectangle(left, top, right - left, bottom - top);
+ selection.setPaint(Color.YELLOW);
+ selection.setTransparency(rubberBandTransparency);
+ svWindow.canvas.getLayer().addChild(selection);
+ }
+ }
+
+ /**
+ * The mouse was released.
+ * Depending on the OS, show a popup menu (if the button pressed is associated
+ * with popup menus, like the RMB under windows&linux) or otherwise create an
+ * SVET_SELECTION event.
+ */
+ @Override
+ public void mouseReleased(PInputEvent e) {
+ if (e.isPopupTrigger()) {
+ showPopup(e);
+ } else {
+ processEvent(new SVEvent(SVEventType.SVET_SELECTION, svWindow, (int) e
+ .getPosition().getX(), (int) e.getPosition().getY(), (int) e
+ .getPosition().getX()
+ - lastX, (int) e.getPosition().getY() - lastY, null));
+ }
+ if (selection != null) {
+ svWindow.canvas.getLayer().removeChild(selection);
+ selection = null;
+ }
+ }
+
+ /**
+ * The mouse wheel is used to zoom in and out of the viewport and center on
+ * the (x,y) position the mouse is currently on.
+ */
+ @Override
+ public void mouseWheelRotated(PInputEvent e) {
+ PCamera lc = svWindow.canvas.getCamera();
+ double sf = SVWindow.SCALING_FACTOR;
+
+ if (e.getWheelRotation() < 0) {
+ sf = 1 / sf;
+ }
+ lc.scaleViewAboutPoint(lc.getScale() / sf, e.getPosition().getX(), e
+ .getPosition().getY());
+ }
+
+ /**
+ * The mouse was moved - create an SVET_MOTION event. NOTE: This obviously
+ * creates a lot of traffic and, depending on the type of application, could
+ * quite possibly be disabled.
+ */
+ @Override
+ public void mouseMoved(PInputEvent e) {
+ processEvent(new SVEvent(SVEventType.SVET_MOTION, svWindow, (int) e
+ .getPosition().getX(), (int) e.getPosition().getY(), 0, 0, null));
+ }
+
+ /**
+ * The mouse entered the window.
+ * Start the timer, which will then emit SVET_HOVER events every X ms. */
+ @Override
+ public void mouseEntered(PInputEvent e) {
+ timer.restart();
+ }
+
+ /**
+ * The mouse exited the window
+ * Stop the timer, so no more SVET_HOVER events will emit. */
+ @Override
+ public void mouseExited(PInputEvent e) {
+ timer.stop();
+ }
+
+ /**
+ * The only associated object with this is the timer, so we use it to send a
+ * SVET_HOVER event.
+ */
+ public void actionPerformed(ActionEvent e) {
+ processEvent(new SVEvent(SVEventType.SVET_HOVER, svWindow, lastXMove,
+ lastYMove, 0, 0, null));
+ }
+
+ /**
+ * A key was pressed - create an SVET_INPUT event.
+ *
+ * NOTE: Might be useful to specify hotkeys.
+ *
+ * Implementation note: The keyListener provided by Piccolo seems to be
+ * broken, so we use the AWT listener directly.
+ * There are never any keyTyped events received either so we are
+ * stuck with physical keys, which is very ugly.
+ */
+ public void keyPressed(KeyEvent e) {
+ char keyCh = e.getKeyChar();
+ if (keyCh == '\r' || keyCh == '\n' || keyCh == '\0' || keyCh == '?') {
+ processEvent(new SVEvent(SVEventType.SVET_INPUT, svWindow, lastXMove,
+ lastYMove, 0, 0, keyStr));
+ // Send newline characters as '!' as '!' can never be a keypressed
+ // and the client eats all newline characters.
+ keyStr = "!";
+ } else {
+ processEvent(new SVEvent(SVEventType.SVET_INPUT, svWindow, lastXMove,
+ lastYMove, 0, 0, String.valueOf(keyCh)));
+ keyStr += keyCh;
+ }
+ }
+
+ /**
+ * A window is closed (by the 'x') - create an SVET_DESTROY event. If it was
+ * the last open Window, also send an SVET_EXIT event (but do not exit unless
+ * the client says so).
+ */
+ public void windowClosing(WindowEvent e) {
+ processEvent(new SVEvent(SVEventType.SVET_DESTROY, svWindow, lastXMove,
+ lastYMove, 0, 0, null));
+ Window w = e.getWindow();
+ if (w != null) {
+ w.dispose();
+ }
+ SVWindow.nrWindows--;
+ if (SVWindow.nrWindows == 0) {
+ processEvent(new SVEvent(SVEventType.SVET_EXIT, svWindow, lastXMove,
+ lastYMove, 0, 0, null));
+ }
+ }
+
+ /** These are all events we do not care about and throw away */
+ public void keyReleased(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent e) {
+ }
+
+ public void windowActivated(WindowEvent e) {
+ }
+
+ public void windowClosed(WindowEvent e) {
+ }
+
+ public void windowDeactivated(WindowEvent e) {
+ }
+
+ public void windowDeiconified(WindowEvent e) {
+ }
+
+ public void windowIconified(WindowEvent e) {
+ }
+
+ public void windowOpened(WindowEvent e) {
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/events/SVEventType.java b/tesseract/java/com/google/scrollview/events/SVEventType.java
new file mode 100644
index 00000000..b15f37e2
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/events/SVEventType.java
@@ -0,0 +1,31 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.events;
+
+/**
+ * These are the defined events which can happen in ScrollView and be
+ * transferred to the client. They are same events as on the client side part of
+ * ScrollView (defined in ScrollView.h).
+ *
+ * @author wanke@google.com
+ */
+public enum SVEventType {
+ SVET_DESTROY, // Window has been destroyed by user.
+ SVET_EXIT, // User has destroyed the last window by clicking on the 'X'
+ SVET_CLICK, // Any button pressed that is not a popup trigger.
+ SVET_SELECTION, // Left button selection.
+ SVET_INPUT, // Any kind of input
+ SVET_MOUSE, // The mouse has moved with a button pressed.
+ SVET_MOTION, // The mouse has moved with no button pressed.
+ SVET_HOVER, // The mouse has stayed still for a second.
+ SVET_POPUP, // A command selected through a popup menu
+ SVET_MENU; // A command selected through the menubar
+}
diff --git a/tesseract/java/com/google/scrollview/ui/Makefile.am b/tesseract/java/com/google/scrollview/ui/Makefile.am
new file mode 100644
index 00000000..556d8015
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS =
+
+EXTRA_DIST = \
+ SVAbstractMenuItem.java \
+ SVCheckboxMenuItem.java SVEmptyMenuItem.java \
+ SVImageHandler.java SVMenuBar.java \
+ SVMenuItem.java SVPopupMenu.java SVSubMenuItem.java SVWindow.java
diff --git a/tesseract/java/com/google/scrollview/ui/SVAbstractMenuItem.java b/tesseract/java/com/google/scrollview/ui/SVAbstractMenuItem.java
new file mode 100644
index 00000000..a5c9c92d
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVAbstractMenuItem.java
@@ -0,0 +1,57 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+/**
+ * A MenuListItem is any sort of menu entry. This can either be within a popup
+ * menu or within a menubar. It can either be a submenu (only name and
+ * command-id) or a name with an associated value and possibly description. They
+ * can also have new entries added (if they are submenus).
+ *
+ * @author wanke@google.com
+ */
+
+import com.google.scrollview.events.SVEventType;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+abstract class SVAbstractMenuItem {
+ JMenuItem mi;
+ public String name;
+ public int id;
+
+ /**
+ * Sets the basic attributes for name, id and the corresponding swing item
+ */
+ SVAbstractMenuItem(int id, String name, JMenuItem jmi) {
+ this.mi = jmi;
+ this.name = name;
+ this.id = id;
+ }
+
+ /** Returns the actual value of the MenuListItem. */
+ public String getValue() { return null; }
+
+ /** Adds a child entry to the submenu. */
+ public void add(SVAbstractMenuItem mli) { }
+
+ /** Adds a child menu to the submenu (or root node). */
+ public void add(JMenu jli) { }
+
+ /**
+ * What to do when user clicks on this item.
+ * @param window The window the event happened.
+ * @param eventType What kind of event will be associated
+ * (usually SVET_POPUP or SVET_MENU).
+ */
+ public void performAction(SVWindow window, SVEventType eventType) {}
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVCheckboxMenuItem.java b/tesseract/java/com/google/scrollview/ui/SVCheckboxMenuItem.java
new file mode 100644
index 00000000..fa2d8323
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVCheckboxMenuItem.java
@@ -0,0 +1,57 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+/**
+ * A MenuListItem is any sort of menu entry. This can either be within a popup
+ * menu or within a menubar. It can either be a submenu (only name and
+ * command-id) or a name with an associated value and possibly description. They
+ * can also have new entries added (if they are submenus).
+ *
+ * @author wanke@google.com
+ */
+
+import com.google.scrollview.ScrollView;
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.events.SVEventType;
+
+import javax.swing.JCheckBoxMenuItem;
+
+/**
+ * Constructs a new menulistitem which possesses a flag that can be toggled.
+ */
+class SVCheckboxMenuItem extends SVAbstractMenuItem {
+ public boolean bvalue;
+
+ SVCheckboxMenuItem(int id, String name, boolean val) {
+ super(id, name, new JCheckBoxMenuItem(name, val));
+ bvalue = val;
+ }
+
+ /** What to do when user clicks on this item. */
+ @Override
+ public void performAction(SVWindow window, SVEventType eventType) {
+ // Checkbox entry - trigger and send event.
+ if (bvalue) {
+ bvalue = false;
+ } else {
+ bvalue = true;
+ }
+ SVEvent svme = new SVEvent(eventType, window, id, getValue());
+ ScrollView.addMessage(svme);
+ }
+
+ /** Returns the actual value of the MenuListItem. */
+ @Override
+ public String getValue() {
+ return Boolean.toString(bvalue);
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVEmptyMenuItem.java b/tesseract/java/com/google/scrollview/ui/SVEmptyMenuItem.java
new file mode 100644
index 00000000..950bcb5b
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVEmptyMenuItem.java
@@ -0,0 +1,46 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+/**
+ * A MenuListItem is any sort of menu entry. This can either be within a popup
+ * menu or within a menubar. It can either be a submenu (only name and
+ * command-id) or a name with an associated value and possibly description. They
+ * can also have new entries added (if they are submenus).
+ *
+ * @author wanke@google.com
+ */
+
+import com.google.scrollview.ScrollView;
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.events.SVEventType;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Constructs a new menulistitem which just has an ID and a name attached to
+ * it. In this case, we will have to ask for the value of the item and its
+ * description if it gets called.
+ */
+class SVEmptyMenuItem extends SVAbstractMenuItem {
+ SVEmptyMenuItem(int id, String name) {
+ super(id, name, new JMenuItem(name));
+ }
+ /** What to do when user clicks on this item. */
+ @Override
+ public void performAction(SVWindow window, SVEventType eventType) {
+ // Send an event indicating that someone clicked on an entry.
+ // Value will be null here.
+ SVEvent svme =
+ new SVEvent(eventType, window, id, getValue());
+ ScrollView.addMessage(svme);
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVImageHandler.java b/tesseract/java/com/google/scrollview/ui/SVImageHandler.java
new file mode 100644
index 00000000..ed6b7c04
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVImageHandler.java
@@ -0,0 +1,74 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+import org.piccolo2d.nodes.PImage;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * The ScrollViewImageHandler is a helper class which takes care of image
+ * processing. It is used to construct an Image from the message-stream and
+ * basically consists of a number of utility functions to process the input
+ * stream.
+ *
+ * @author wanke@google.com
+ */
+public class SVImageHandler {
+ /* All methods are static, so we forbid to construct SVImageHandler objects */
+ private SVImageHandler() {
+ }
+
+ /**
+ * Reads size bytes from the stream in and interprets it as an image file,
+ * encoded as png, and then text-encoded as base 64, returning the decoded
+ * bitmap.
+ *
+ * @param size The size of the image file.
+ * @param in The input stream from which to read the bytes.
+ */
+ public static PImage readImage(int size, BufferedReader in) {
+ char[] charbuffer = new char[size];
+ int numRead = 0;
+ while (numRead < size) {
+ int newRead = -1;
+ try {
+ newRead = in.read(charbuffer, numRead, size - numRead);
+ } catch (IOException e) {
+ System.out.println("Failed to read image data from socket:" + e.getMessage());
+ return null;
+ }
+ if (newRead < 0) {
+ return null;
+ }
+ numRead += newRead;
+ }
+ if (numRead != size) {
+ System.out.println("Failed to read image data from socket");
+ return null;
+ }
+ // Convert the character data to binary.
+ byte[] binarydata = DatatypeConverter.parseBase64Binary(new String(charbuffer));
+ // Convert the binary data to a byte stream and parse to image.
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(binarydata);
+ try {
+ PImage img = new PImage(ImageIO.read(byteStream));
+ return img;
+ } catch (IOException e) {
+ System.out.println("Failed to decode image data from socket" + e.getMessage());
+ }
+ return null;
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVMenuBar.java b/tesseract/java/com/google/scrollview/ui/SVMenuBar.java
new file mode 100644
index 00000000..9a87524e
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVMenuBar.java
@@ -0,0 +1,130 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+import com.google.scrollview.events.SVEventType;
+import com.google.scrollview.ui.SVWindow;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+
+/**
+ * The SVMenuBar class provides the functionality to add a menubar to
+ * ScrollView. Each menubar item gets associated with a (client-defined)
+ * command-id, which SVMenuBar will return upon clicking it.
+ *
+ * @author wanke@google.com
+ *
+ */
+public class SVMenuBar implements ActionListener {
+ /** The root entry to add items to. */
+ private JMenuBar root;
+ /** Contains a map of item name to its actual entry. */
+ private HashMap<String, SVAbstractMenuItem> items;
+ /** The window the menubar belongs to. */
+ private SVWindow svWindow;
+
+ /**
+ * Create a new SVMenuBar and place it at the top of the ScrollView window.
+ *
+ * @param scrollView The window our menubar belongs to.
+ */
+ public SVMenuBar(SVWindow scrollView) {
+ root = new JMenuBar();
+ svWindow = scrollView;
+ items = new HashMap<String, SVAbstractMenuItem>();
+ svWindow.setJMenuBar(root);
+ }
+
+
+ /**
+ * A click on one of the items in our menubar has occurred. Forward it
+ * to the item itself to let it decide what happens.
+ */
+ public void actionPerformed(ActionEvent e) {
+ // Get the corresponding menuitem.
+ SVAbstractMenuItem svm = items.get(e.getActionCommand());
+
+ svm.performAction(svWindow, SVEventType.SVET_MENU);
+ }
+
+ /**
+ * Add a new entry to the menubar.
+ *
+ * @param parent The menu we add our new entry to (should have been defined
+ * before). If the parent is "", we will add the entry to the root
+ * (top-level)
+ * @param name The caption of the new entry.
+ * @param id The Id of the new entry. If it is -1, the entry will be treated
+ * as a menu.
+ */
+ public void add(String parent, String name, int id) {
+ // A duplicate entry - we just throw it away, since its already in.
+ if (items.get(name) != null) { return; }
+ // A new submenu at the top-level
+ if (parent.equals("")) {
+ JMenu jli = new JMenu(name);
+ SVAbstractMenuItem mli = new SVSubMenuItem(name, jli);
+ items.put(name, mli);
+ root.add(jli);
+ }
+ // A new sub-submenu
+ else if (id == -1) {
+ SVAbstractMenuItem jmi = items.get(parent);
+ JMenu jli = new JMenu(name);
+ SVAbstractMenuItem mli = new SVSubMenuItem(name, jli);
+ items.put(name, mli);
+ jmi.add(jli);
+ }
+ // A new child entry. Add to appropriate parent.
+ else {
+ SVAbstractMenuItem jmi = items.get(parent);
+ if (jmi == null) {
+ System.out.println("ERROR: Unknown parent " + parent);
+ System.exit(1);
+ }
+ SVAbstractMenuItem mli = new SVEmptyMenuItem(id, name);
+ mli.mi.addActionListener(this);
+ items.put(name, mli);
+ jmi.add(mli);
+ }
+ }
+
+ /**
+ * Add a new checkbox entry to the menubar.
+ *
+ * @param parent The menu we add our new entry to (should have been defined
+ * before). If the parent is "", we will add the entry to the root
+ * (top-level)
+ * @param name The caption of the new entry.
+ * @param id The Id of the new entry. If it is -1, the entry will be treated
+ * as a menu.
+ * @param b Whether the entry is initially flagged.
+ *
+ */
+
+ public void add(String parent, String name, int id, boolean b) {
+ SVAbstractMenuItem jmi = items.get(parent);
+ if (jmi == null) {
+ System.out.println("ERROR: Unknown parent " + parent);
+ System.exit(1);
+ }
+ SVAbstractMenuItem mli = new SVCheckboxMenuItem(id, name, b);
+ mli.mi.addActionListener(this);
+ items.put(name, mli);
+ jmi.add(mli);
+ }
+
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVMenuItem.java b/tesseract/java/com/google/scrollview/ui/SVMenuItem.java
new file mode 100644
index 00000000..932ab7cc
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVMenuItem.java
@@ -0,0 +1,60 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+/**
+ * A MenuListItem is any sort of menu entry. This can either be within a popup
+ * menu or within a menubar. It can either be a submenu (only name and
+ * command-id) or a name with an associated value and possibly description. They
+ * can also have new entries added (if they are submenus).
+ *
+ * @author wanke@google.com
+ */
+
+import com.google.scrollview.events.SVEventType;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Constructs a new menulistitem which also has a value and a description. For
+ * these, we will not have to ask the server what the value is when the user
+ * wants to change it, but can just call the client with the new value.
+ */
+class SVMenuItem extends SVAbstractMenuItem {
+ public String value = null;
+ public String desc = null;
+
+ SVMenuItem(int id, String name, String v, String d) {
+ super(id, name, new JMenuItem(name));
+ value = v;
+ desc = d;
+ }
+
+ /**
+ * Ask the user for new input for a variable and send it.
+ * Depending on whether there is a description given for the entry, show
+ * the description in the dialog or just show the name.
+ */
+ @Override
+ public void performAction(SVWindow window, SVEventType eventType) {
+ if (desc != null) {
+ window.showInputDialog(desc, value, id, eventType);
+ } else {
+ window.showInputDialog(name, value, id, eventType);
+ }
+ }
+
+ /** Returns the actual value of the MenuListItem. */
+ @Override
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVPopupMenu.java b/tesseract/java/com/google/scrollview/ui/SVPopupMenu.java
new file mode 100644
index 00000000..14c8b3ac
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVPopupMenu.java
@@ -0,0 +1,144 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+import com.google.scrollview.events.SVEventType;
+import com.google.scrollview.ui.SVMenuItem;
+import com.google.scrollview.ui.SVWindow;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+
+import javax.swing.JMenu;
+import javax.swing.JPopupMenu;
+
+/**
+ * The SVPopupMenu class provides the functionality to add a popup menu to
+ * ScrollView. Each popup menu item gets associated with a (client-defined)
+ * command-id, which SVPopupMenu will return upon clicking it.
+ *
+ * @author wanke@google.com
+ *
+ */
+
+public class SVPopupMenu implements ActionListener {
+ /** The root entry to add items to. */
+ private JPopupMenu root;
+ /** Contains a map of item name to its actual entry. */
+ private HashMap<String, SVAbstractMenuItem> items;
+ /** The window the menubar belongs to. */
+ private SVWindow svWindow;
+
+ /**
+ * Create a new SVPopupMenu and associate it with a ScrollView window.
+ *
+ * @param sv The window our popup menu belongs to.
+ */
+ SVPopupMenu(SVWindow sv) {
+ root = new JPopupMenu();
+ svWindow = sv;
+ items = new HashMap<String, SVAbstractMenuItem>();
+ }
+
+ /**
+ * Add a new entry to the menubar. For these items, the server will poll the
+ * client to ask what to do.
+ *
+ * @param parent The menu we add our new entry to (should have been defined
+ * before). If the parent is "", we will add the entry to the root
+ * (top-level)
+ * @param name The caption of the new entry.
+ * @param id The Id of the new entry. If it is -1, the entry will be treated
+ * as a menu.
+ */
+ public void add(String parent, String name, int id) {
+ // A duplicate entry - we just throw it away, since its already in.
+ if (items.get(name) != null) { return; }
+ // A new submenu at the top-level
+ if (parent.equals("")) {
+ JMenu jli = new JMenu(name);
+ SVAbstractMenuItem mli = new SVSubMenuItem(name, jli);
+ items.put(name, mli);
+ root.add(jli);
+ }
+ // A new sub-submenu
+ else if (id == -1) {
+ SVAbstractMenuItem jmi = items.get(parent);
+ JMenu jli = new JMenu(name);
+ SVAbstractMenuItem mli = new SVSubMenuItem(name, jli);
+ items.put(name, mli);
+ jmi.add(jli);
+ }
+ // A new child entry. Add to appropriate parent.
+ else {
+ SVAbstractMenuItem jmi = items.get(parent);
+ if (jmi == null) {
+ System.out.println("ERROR: Unknown parent " + parent);
+ System.exit(1);
+ }
+ SVAbstractMenuItem mli = new SVEmptyMenuItem(id, name);
+ mli.mi.addActionListener(this);
+ items.put(name, mli);
+ jmi.add(mli);
+ }
+ }
+
+ /**
+ * Add a new entry to the menubar. In this case, we also know its value and
+ * possibly even have a description. For these items, the server will not poll
+ * the client to ask what to do, but just show an input dialog and send a
+ * message with the new value.
+ *
+ * @param parent The menu we add our new entry to (should have been defined
+ * before). If the parent is "", we will add the entry to the root
+ * (top-level)
+ * @param name The caption of the new entry.
+ * @param id The Id of the new entry. If it is -1, the entry will be treated
+ * as a menu.
+ * @param value The value of the new entry.
+ * @param desc The description of the new entry.
+ */
+ public void add(String parent, String name, int id, String value, String desc) {
+ SVAbstractMenuItem jmi = items.get(parent);
+ SVMenuItem mli = new SVMenuItem(id, name, value, desc);
+ mli.mi.addActionListener(this);
+ items.put(name, mli);
+ if (jmi == null) { // add to root
+ root.add(mli.mi);
+ } else { // add to parent
+ jmi.add(mli);
+ }
+ }
+
+
+
+ /**
+ * A click on one of the items in our menubar has occurred. Forward it
+ * to the item itself to let it decide what happens.
+ */
+ public void actionPerformed(ActionEvent e) {
+
+ // Get the corresponding menuitem
+ SVAbstractMenuItem svm = items.get(e.getActionCommand());
+
+ svm.performAction(svWindow, SVEventType.SVET_POPUP);
+ }
+
+ /**
+ * Gets called by the SVEventHandler of the window to actually show the
+ * content of the popup menu.
+ */
+ public void show(Component Invoker, int x, int y) {
+ root.show(Invoker, x, y);
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVSubMenuItem.java b/tesseract/java/com/google/scrollview/ui/SVSubMenuItem.java
new file mode 100644
index 00000000..ebcf36cb
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVSubMenuItem.java
@@ -0,0 +1,39 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+/**
+ * A MenuListItem is any sort of menu entry. This can either be within a popup
+ * menu or within a menubar. It can either be a submenu (only name and
+ * command-id) or a name with an associated value and possibly description. They
+ * can also have new entries added (if they are submenus).
+ *
+ * @author wanke@google.com
+ */
+
+import javax.swing.JMenu;
+
+/** Constructs a new submenu which can hold other entries. */
+class SVSubMenuItem extends SVAbstractMenuItem {
+ public SVSubMenuItem(String name, JMenu jli) {
+ super(-1, name, jli);
+ }
+ /** Adds a child entry to the submenu. */
+ @Override
+ public void add(SVAbstractMenuItem mli) {
+ mi.add(mli.mi);
+ }
+ /** Adds a child menu to the submenu (or root node). */
+ @Override
+ public void add(JMenu jli) {
+ mi.add(jli);
+ }
+}
diff --git a/tesseract/java/com/google/scrollview/ui/SVWindow.java b/tesseract/java/com/google/scrollview/ui/SVWindow.java
new file mode 100644
index 00000000..3b5e7cd8
--- /dev/null
+++ b/tesseract/java/com/google/scrollview/ui/SVWindow.java
@@ -0,0 +1,648 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); You may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+// applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, either express or implied. See the License for the specific
+// language governing permissions and limitations under the License.
+
+package com.google.scrollview.ui;
+
+import com.google.scrollview.ScrollView;
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.events.SVEventHandler;
+import com.google.scrollview.events.SVEventType;
+import com.google.scrollview.ui.SVMenuBar;
+import com.google.scrollview.ui.SVPopupMenu;
+
+import org.piccolo2d.PCamera;
+import org.piccolo2d.PCanvas;
+import org.piccolo2d.PLayer;
+import org.piccolo2d.extras.swing.PScrollPane;
+import org.piccolo2d.nodes.PImage;
+import org.piccolo2d.nodes.PPath;
+import org.piccolo2d.nodes.PText;
+import org.piccolo2d.util.PPaintContext;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.TextArea;
+import java.awt.geom.IllegalPathStateException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+
+/**
+ * The SVWindow is the top-level ui class. It should get instantiated whenever
+ * the user intends to create a new window. It contains helper functions to draw
+ * on the canvas, add new menu items, show modal dialogs etc.
+ *
+ * @author wanke@google.com
+ */
+public class SVWindow extends JFrame {
+ /**
+ * Constants defining the maximum initial size of the window.
+ */
+ private static final int MAX_WINDOW_X = 1000;
+ private static final int MAX_WINDOW_Y = 800;
+
+ /* Constant defining the (approx) height of the default message box*/
+ private static final int DEF_MESSAGEBOX_HEIGHT = 200;
+
+ /** Constant defining the "speed" at which to zoom in and out. */
+ public static final double SCALING_FACTOR = 2;
+
+ /** The top level layer we add our PNodes to (root node). */
+ PLayer layer;
+
+ /** The current color of the pen. It is used to draw edges, text, etc. */
+ Color currentPenColor;
+
+ /**
+ * The current color of the brush. It is used to draw the interior of
+ * primitives.
+ */
+ Color currentBrushColor;
+
+ /** The system name of the current font we are using (e.g.
+ * "Times New Roman"). */
+ Font currentFont;
+
+ /** The stroke width to be used. */
+ // This really needs to be a fixed width stroke as the basic stroke is
+ // anti-aliased and gets too faint, but the piccolo fixed width stroke
+ // is too buggy and generates missing initial moveto in path definition
+ // errors with a IllegalPathStateException that cannot be caught because
+ // it is in the automatic repaint function. If we can fix the exceptions
+ // in piccolo, then we can use the following instead of BasicStroke:
+ // import edu.umd.cs.piccolox.util.PFixedWidthStroke;
+ // PFixedWidthStroke stroke = new PFixedWidthStroke(0.5f);
+ // Instead we use the BasicStroke and turn off anti-aliasing.
+ BasicStroke stroke = new BasicStroke(0.5f);
+
+ /**
+ * A unique representation for the window, also known by the client. It is
+ * used when sending messages from server to client to identify him.
+ */
+ public int hash;
+
+ /**
+ * The total number of created Windows. If this ever reaches 0 (apart from the
+ * beginning), quit the server.
+ */
+ public static int nrWindows = 0;
+
+ /**
+ * The Canvas, MessageBox, EventHandler, Menubar and Popupmenu associated with
+ * this window.
+ */
+ private SVEventHandler svEventHandler = null;
+ private SVMenuBar svMenuBar = null;
+ private TextArea ta = null;
+ public SVPopupMenu svPuMenu = null;
+ public PCanvas canvas;
+ private int winSizeX;
+ private int winSizeY;
+
+ /** Set the brush to an RGB color */
+ public void brush(int red, int green, int blue) {
+ brush(red, green, blue, 255);
+ }
+
+ /** Set the brush to an RGBA color */
+ public void brush(int red, int green, int blue, int alpha) {
+ // If alpha is zero, use a null brush to save rendering time.
+ if (alpha == 0) {
+ currentBrushColor = null;
+ } else {
+ currentBrushColor = new Color(red, green, blue, alpha);
+ }
+ }
+
+ /** Erase all content from the window, but do not destroy it. */
+ public void clear() {
+ // Manipulation of Piccolo's scene graph should be done from Swings
+ // event dispatch thread since Piccolo is not thread safe. This code calls
+ // removeAllChildren() from that thread and releases the latch.
+ final java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ layer.removeAllChildren();
+ repaint();
+ latch.countDown();
+ }
+ });
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ /**
+ * Start setting up a new polyline. The server will now expect
+ * polyline data until the polyline is complete.
+ *
+ * @param length number of coordinate pairs
+ */
+ public void createPolyline(int length) {
+ ScrollView.polylineXCoords = new float[length];
+ ScrollView.polylineYCoords = new float[length];
+ ScrollView.polylineSize = length;
+ ScrollView.polylineScanned = 0;
+ }
+
+ /**
+ * Draw the now complete polyline.
+ */
+ public void drawPolyline() {
+ int numCoords = ScrollView.polylineXCoords.length;
+ if (numCoords < 2) {
+ return;
+ }
+ PPath pn = PPath.createLine(ScrollView.polylineXCoords[0],
+ ScrollView.polylineYCoords[0],
+ ScrollView.polylineXCoords[1],
+ ScrollView.polylineYCoords[1]);
+ pn.reset();
+ pn.moveTo(ScrollView.polylineXCoords[0], ScrollView.polylineYCoords[0]);
+ for (int p = 1; p < numCoords; ++p) {
+ pn.lineTo(ScrollView.polylineXCoords[p], ScrollView.polylineYCoords[p]);
+ }
+ pn.closePath();
+ ScrollView.polylineSize = 0;
+ pn.setStrokePaint(currentPenColor);
+ pn.setPaint(null); // Don't fill the polygon - this is just a polyline.
+ pn.setStroke(stroke);
+ layer.addChild(pn);
+ }
+
+ /**
+ * Construct a new SVWindow and set it visible.
+ *
+ * @param name Title of the window.
+ * @param hash Unique internal representation. This has to be the same as
+ * defined by the client, as they use this to refer to the windows.
+ * @param posX X position of where to draw the window (upper left).
+ * @param posY Y position of where to draw the window (upper left).
+ * @param sizeX The width of the window.
+ * @param sizeY The height of the window.
+ * @param canvasSizeX The canvas width of the window.
+ * @param canvasSizeY The canvas height of the window.
+ */
+ public SVWindow(String name, int hash, int posX, int posY, int sizeX,
+ int sizeY, int canvasSizeX, int canvasSizeY) {
+ super(name);
+
+ // Provide defaults for sizes.
+ if (sizeX <= 0) sizeX = canvasSizeX;
+ if (sizeY <= 0) sizeY = canvasSizeY;
+ if (canvasSizeX <= 0) canvasSizeX = sizeX;
+ if (canvasSizeY <= 0) canvasSizeY = sizeY;
+
+ // Avoid later division by zero.
+ if (sizeX <= 0) {
+ sizeX = 1;
+ canvasSizeX = sizeX;
+ }
+ if (sizeY <= 0) {
+ sizeY = 1;
+ canvasSizeY = sizeY;
+ }
+
+ // Initialize variables
+ nrWindows++;
+ this.hash = hash;
+ this.svEventHandler = new SVEventHandler(this);
+ this.currentPenColor = Color.BLACK;
+ this.currentBrushColor = Color.BLACK;
+ this.currentFont = new Font("Times New Roman", Font.PLAIN, 12);
+
+ // Determine the initial size and zoom factor of the window.
+ // If the window is too big, rescale it and zoom out.
+ int shrinkfactor = 1;
+
+ if (sizeX > MAX_WINDOW_X) {
+ shrinkfactor = (sizeX + MAX_WINDOW_X - 1) / MAX_WINDOW_X;
+ }
+ if (sizeY / shrinkfactor > MAX_WINDOW_Y) {
+ shrinkfactor = (sizeY + MAX_WINDOW_Y - 1) / MAX_WINDOW_Y;
+ }
+ winSizeX = sizeX / shrinkfactor;
+ winSizeY = sizeY / shrinkfactor;
+ double initialScalingfactor = 1.0 / shrinkfactor;
+ if (winSizeX > canvasSizeX || winSizeY > canvasSizeY) {
+ initialScalingfactor = Math.min(1.0 * winSizeX / canvasSizeX,
+ 1.0 * winSizeY / canvasSizeY);
+ }
+
+ // Setup the actual window (its size, camera, title, etc.)
+ if (canvas == null) {
+ canvas = new PCanvas();
+ getContentPane().add(canvas, BorderLayout.CENTER);
+ }
+
+ layer = canvas.getLayer();
+ canvas.setBackground(Color.BLACK);
+
+ // Disable antialiasing to make the lines more visible.
+ canvas.setDefaultRenderQuality(PPaintContext.LOW_QUALITY_RENDERING);
+
+ setLayout(new BorderLayout());
+
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ validate();
+ canvas.requestFocus();
+
+ // Manipulation of Piccolo's scene graph should be done from Swings
+ // event dispatch thread since Piccolo is not thread safe. This code calls
+ // initialize() from that thread once the PFrame is initialized, so you are
+ // safe to start working with Piccolo in the initialize() method.
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ repaint();
+ }
+ });
+
+ setSize(winSizeX, winSizeY);
+ setLocation(posX, posY);
+ setTitle(name);
+
+ // Add a Scrollpane to be able to scroll within the canvas
+ PScrollPane scrollPane = new PScrollPane(canvas);
+ getContentPane().add(scrollPane);
+ scrollPane.setWheelScrollingEnabled(false);
+ PCamera lc = canvas.getCamera();
+ lc.scaleViewAboutPoint(initialScalingfactor, 0, 0);
+
+ // Disable the default event handlers and add our own.
+ addWindowListener(svEventHandler);
+ canvas.removeInputEventListener(canvas.getPanEventHandler());
+ canvas.removeInputEventListener(canvas.getZoomEventHandler());
+ canvas.addInputEventListener(svEventHandler);
+ canvas.addKeyListener(svEventHandler);
+
+ // Make the window visible.
+ validate();
+ setVisible(true);
+
+ }
+
+ /**
+ * Convenience function to add a message box to the window which can be used
+ * to output debug information.
+ */
+ public void addMessageBox() {
+ if (ta == null) {
+ ta = new TextArea();
+ ta.setEditable(false);
+ getContentPane().add(ta, BorderLayout.SOUTH);
+ }
+ // We need to make the window bigger to accommodate the message box.
+ winSizeY += DEF_MESSAGEBOX_HEIGHT;
+ setSize(winSizeX, winSizeY);
+ }
+
+ /**
+ * Allows you to specify the thickness with which to draw lines, recantgles
+ * and ellipses.
+ * @param width The new thickness.
+ */
+ public void setStrokeWidth(float width) {
+ // If this worked we wouldn't need the antialiased rendering off.
+ // stroke = new PFixedWidthStroke(width);
+ stroke = new BasicStroke(width);
+ }
+
+ /**
+ * Draw an ellipse at (x,y) with given width and height, using the
+ * current stroke, the current brush color to fill it and the
+ * current pen color for the outline.
+ */
+ public void drawEllipse(int x, int y, int width, int height) {
+ PPath pn = PPath.createEllipse(x, y, width, height);
+ pn.setStrokePaint(currentPenColor);
+ pn.setStroke(stroke);
+ pn.setPaint(currentBrushColor);
+ layer.addChild(pn);
+ }
+
+ /**
+ * Draw the image with the given name at (x,y). Any image loaded stays in
+ * memory, so if you intend to redraw an image, you do not have to use
+ * createImage again.
+ */
+ public void drawImage(PImage img, int xPos, int yPos) {
+ img.setX(xPos);
+ img.setY(yPos);
+ layer.addChild(img);
+ }
+
+ /**
+ * Draw a line from (x1,y1) to (x2,y2) using the current pen color and stroke.
+ */
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ PPath pn = PPath.createLine(x1, y1, x2, y2);
+ pn.setStrokePaint(currentPenColor);
+ pn.setPaint(null); // Null paint may render faster than the default.
+ pn.setStroke(stroke);
+ pn.moveTo(x1, y1);
+ pn.lineTo(x2, y2);
+ layer.addChild(pn);
+ }
+
+ /**
+ * Draw a rectangle given the two points (x1,y1) and (x2,y2) using the current
+ * stroke, pen color for the border and the brush to fill the
+ * interior.
+ */
+ public void drawRectangle(int x1, int y1, int x2, int y2) {
+
+ if (x1 > x2) {
+ int t = x1;
+ x1 = x2;
+ x2 = t;
+ }
+ if (y1 > y2) {
+ int t = y1;
+ y1 = y2;
+ y2 = t;
+ }
+
+ PPath pn = PPath.createRectangle(x1, y1, x2 - x1, y2 - y1);
+ pn.setStrokePaint(currentPenColor);
+ pn.setStroke(stroke);
+ pn.setPaint(currentBrushColor);
+ layer.addChild(pn);
+ }
+
+ /**
+ * Draw some text at (x,y) using the current pen color and text attributes. If
+ * the current font does NOT support at least one character, it tries to find
+ * a font which is capable of displaying it and use that to render the text.
+ * Note: If the font says it can render a glyph, but in reality it turns out
+ * to be crap, there is nothing we can do about it.
+ */
+ public void drawText(int x, int y, String text) {
+ int unreadableCharAt = -1;
+ char[] chars = text.toCharArray();
+ PText pt = new PText(text);
+ pt.setTextPaint(currentPenColor);
+ pt.setFont(currentFont);
+
+ // Check to see if every character can be displayed by the current font.
+ for (int i = 0; i < chars.length; i++) {
+ if (!currentFont.canDisplay(chars[i])) {
+ // Set to the first not displayable character.
+ unreadableCharAt = i;
+ break;
+ }
+ }
+
+ // Have to find some working font and use it for this text entry.
+ if (unreadableCharAt != -1) {
+ Font[] allfonts =
+ GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
+ for (int j = 0; j < allfonts.length; j++) {
+ if (allfonts[j].canDisplay(chars[unreadableCharAt])) {
+ Font tempFont =
+ new Font(allfonts[j].getFontName(), currentFont.getStyle(),
+ currentFont.getSize());
+ pt.setFont(tempFont);
+ break;
+ }
+ }
+ }
+
+ pt.setX(x);
+ pt.setY(y);
+ layer.addChild(pt);
+ }
+
+ /** Set the pen color to an RGB value */
+ public void pen(int red, int green, int blue) {
+ pen(red, green, blue, 255);
+ }
+
+ /** Set the pen color to an RGBA value */
+ public void pen(int red, int green, int blue, int alpha) {
+ currentPenColor = new Color(red, green, blue, alpha);
+ }
+
+ /**
+ * Define how to display text. Note: underlined is not currently not supported
+ */
+ public void textAttributes(String font, int pixelSize, boolean bold,
+ boolean italic, boolean underlined) {
+
+ // For legacy reasons convert "Times" to "Times New Roman"
+ if (font.equals("Times")) {
+ font = "Times New Roman";
+ }
+
+ int style = Font.PLAIN;
+ if (bold) {
+ style += Font.BOLD;
+ }
+ if (italic) {
+ style += Font.ITALIC;
+ }
+ currentFont = new Font(font, style, pixelSize);
+ }
+
+ /**
+ * Zoom the window to the rectangle given the two points (x1,y1)
+ * and (x2,y2), which must be greater than (x1,y1).
+ */
+ public void zoomRectangle(int x1, int y1, int x2, int y2) {
+ if (x2 > x1 && y2 > y1) {
+ winSizeX = getWidth();
+ winSizeY = getHeight();
+ int width = x2 - x1;
+ int height = y2 - y1;
+ // Since piccolo doesn't do this well either, pad with a margin
+ // all the way around.
+ int wmargin = width / 2;
+ int hmargin = height / 2;
+ double scalefactor = Math.min(winSizeX / (2.0 * wmargin + width),
+ winSizeY / (2.0 * hmargin + height));
+ PCamera lc = canvas.getCamera();
+ lc.scaleView(scalefactor / lc.getViewScale());
+ lc.animateViewToPanToBounds(new Rectangle(x1 - hmargin, y1 - hmargin,
+ 2 * wmargin + width,
+ 2 * hmargin + height), 0);
+ }
+ }
+
+ /**
+ * Flush buffers and update display.
+ *
+ * Only actually reacts if there are no more messages in the stack, to prevent
+ * the canvas from flickering.
+ */
+ public void update() {
+ // TODO(rays) fix bugs in piccolo or use something else.
+ // The repaint function generates many
+ // exceptions for no good reason. We catch and ignore as many as we
+ // can here, but most of them are generated by the system repaints
+ // caused by resizing/exposing parts of the window etc, and they
+ // generate unwanted stack traces that have to be piped to /dev/null
+ // (on linux).
+ try {
+ repaint();
+ } catch (NullPointerException e) {
+ // Do nothing so the output isn't full of stack traces.
+ } catch (IllegalPathStateException e) {
+ // Do nothing so the output isn't full of stack traces.
+ }
+ }
+
+ /** Adds a checkbox entry to the menubar, c.f. SVMenubar.add(...) */
+ public void addMenuBarItem(String parent, String name, int id,
+ boolean checked) {
+ svMenuBar.add(parent, name, id, checked);
+ }
+
+ /** Adds a submenu to the menubar, c.f. SVMenubar.add(...) */
+ public void addMenuBarItem(String parent, String name) {
+ addMenuBarItem(parent, name, -1);
+ }
+
+ /** Adds a new entry to the menubar, c.f. SVMenubar.add(...) */
+ public void addMenuBarItem(String parent, String name, int id) {
+ if (svMenuBar == null) {
+ svMenuBar = new SVMenuBar(this);
+
+ }
+ svMenuBar.add(parent, name, id);
+ }
+
+ /** Add a message to the message box. */
+ public void addMessage(String message) {
+ if (ta != null) {
+ ta.append(message + "\n");
+ } else {
+ System.out.println(message + "\n");
+ }
+ }
+
+ /**
+ * This method converts a string which might contain hexadecimal values to a
+ * string which contains the respective unicode counterparts.
+ *
+ * For example, Hall0x0094chen returns Hall<o umlaut>chen
+ * encoded as utf8.
+ *
+ * @param input The original string, containing 0x values
+ * @return The converted string which has the replaced unicode symbols
+ */
+ private static String convertIntegerStringToUnicodeString(String input) {
+ StringBuffer sb = new StringBuffer(input);
+ Pattern numbers = Pattern.compile("0x[0-9a-fA-F]{4}");
+ Matcher matcher = numbers.matcher(sb);
+
+ while (matcher.find()) {
+ // Find the next match which resembles a hexadecimal value and convert it
+ // to
+ // its char value
+ char a = (char) (Integer.decode(matcher.group()).intValue());
+
+ // Replace the original with the new character
+ sb.replace(matcher.start(), matcher.end(), String.valueOf(a));
+
+ // Start again, since our positions have switched
+ matcher.reset();
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Show a modal input dialog. The answer by the dialog is then send to the
+ * client, together with the associated menu id, as SVET_POPUP
+ *
+ * @param msg The text that is displayed in the dialog.
+ * @param def The default value of the dialog.
+ * @param id The associated commandId
+ * @param evtype The event this is associated with (usually SVET_MENU
+ * or SVET_POPUP)
+ */
+ public void showInputDialog(String msg, String def, int id,
+ SVEventType evtype) {
+ svEventHandler.timer.stop();
+ String tmp =
+ (String) JOptionPane.showInputDialog(this, msg, "",
+ JOptionPane.QUESTION_MESSAGE, null, null, def);
+
+ if (tmp != null) {
+ tmp = convertIntegerStringToUnicodeString(tmp);
+ SVEvent res = new SVEvent(evtype, this, id, tmp);
+ ScrollView.addMessage(res);
+ }
+ svEventHandler.timer.restart();
+ }
+
+
+ /**
+ * Shows a modal input dialog to the user. The return value is automatically
+ * sent to the client as SVET_INPUT event (with command id -1).
+ *
+ * @param msg The text of the dialog.
+ */
+ public void showInputDialog(String msg) {
+ showInputDialog(msg, null, -1, SVEventType.SVET_INPUT);
+ }
+
+ /**
+ * Shows a dialog presenting "Yes" and "No" as answers and returns either a
+ * "y" or "n" to the client.
+ *
+ * Closing the dialog without answering is handled like "No".
+ *
+ * @param msg The text that is displayed in the dialog.
+ */
+ public void showYesNoDialog(String msg) {
+ // res returns 0 on yes, 1 on no. Seems to be a bit counterintuitive
+ int res =
+ JOptionPane.showOptionDialog(this, msg, "", JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, null, null);
+
+ SVEvent e = new SVEvent(SVEventType.SVET_INPUT, this, 0, 0, 0, 0,
+ res == 0 ? "y" : "n");
+ ScrollView.addMessage(e);
+ }
+
+ /** Adds a submenu to the popup menu, c.f. SVPopupMenu.add(...) */
+ public void addPopupMenuItem(String parent, String name) {
+ if (svPuMenu == null) {
+ svPuMenu = new SVPopupMenu(this);
+ }
+ svPuMenu.add(parent, name, -1);
+ }
+
+ /** Adds a new menu entry to the popup menu, c.f. SVPopupMenu.add(...) */
+ public void addPopupMenuItem(String parent, String name, int cmdEvent,
+ String value, String desc) {
+ if (svPuMenu == null) {
+ svPuMenu = new SVPopupMenu(this);
+ }
+ svPuMenu.add(parent, name, cmdEvent, value, desc);
+ }
+
+ /** Destroys a window. */
+ public void destroy() {
+ ScrollView.addMessage(new SVEvent(SVEventType.SVET_DESTROY, this, 0,
+ "SVET_DESTROY"));
+ setVisible(false);
+ // dispose();
+ }
+}