ScrollView.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. // Copyright 2007 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"); You may not
  4. // use this file except in compliance with the License. You may obtain a copy of
  5. // the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
  6. // applicable law or agreed to in writing, software distributed under the
  7. // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
  8. // OF ANY KIND, either express or implied. See the License for the specific
  9. // language governing permissions and limitations under the License.
  10. package com.google.scrollview;
  11. import com.google.scrollview.events.SVEvent;
  12. import com.google.scrollview.ui.SVImageHandler;
  13. import com.google.scrollview.ui.SVWindow;
  14. import org.piccolo2d.nodes.PImage;
  15. import java.io.BufferedReader;
  16. import java.io.IOException;
  17. import java.io.InputStreamReader;
  18. import java.io.PrintStream;
  19. import java.net.ServerSocket;
  20. import java.net.Socket;
  21. import java.util.ArrayList;
  22. import java.util.regex.Pattern;
  23. /**
  24. * The ScrollView class is the main class which gets started from the command
  25. * line. It sets up LUA and handles the network processing.
  26. * @author wanke@google.com
  27. */
  28. public class ScrollView {
  29. /** The port our server listens at. */
  30. public static int SERVER_PORT = 8461;
  31. /**
  32. * All SVWindow objects share the same connection stream. The socket is needed
  33. * to detect when the connection got closed, in/out are used to send and
  34. * receive messages.
  35. */
  36. private static Socket socket;
  37. private static PrintStream out;
  38. public static BufferedReader in;
  39. public static float polylineXCoords[]; // The coords being received.
  40. public static float polylineYCoords[]; // The coords being received.
  41. public static int polylineSize; // The size of the coords arrays.
  42. public static int polylineScanned; // The size read so far.
  43. private static ArrayList<SVWindow> windows; // The id to SVWindow map.
  44. private static Pattern intPattern; // For checking integer arguments.
  45. private static Pattern floatPattern; // For checking float arguments.
  46. /** Keeps track of the number of messages received. */
  47. static int nrInputLines = 0;
  48. /** Prints all received messages to the console if true. */
  49. static boolean debugViewNetworkTraffic = false;
  50. /** Add a new message to the outgoing queue. */
  51. public static void addMessage(SVEvent e) {
  52. if (debugViewNetworkTraffic) {
  53. System.out.println("(S->c) " + e.toString());
  54. }
  55. String str = e.toString();
  56. // Send the whole thing as UTF8.
  57. try {
  58. byte [] utf8 = str.getBytes("UTF8");
  59. out.write(utf8, 0, utf8.length);
  60. } catch (java.io.UnsupportedEncodingException ex) {
  61. System.out.println("Oops... can't encode to UTF8... Exiting");
  62. System.exit(0);
  63. }
  64. out.println();
  65. // Flush the output and check for errors.
  66. boolean error = out.checkError();
  67. if (error) {
  68. System.out.println("Connection error. Quitting ScrollView Server...");
  69. System.exit(0);
  70. }
  71. }
  72. /** Read one message from client (assuming there are any). */
  73. public static String receiveMessage() throws IOException {
  74. return in.readLine();
  75. }
  76. /**
  77. * The main program loop. Basically loops through receiving messages and
  78. * processing them and then sending messages (if there are any).
  79. */
  80. private static void IOLoop() {
  81. String inputLine;
  82. try {
  83. while (!socket.isClosed() && !socket.isInputShutdown() &&
  84. !socket.isOutputShutdown() &&
  85. socket.isConnected() && socket.isBound()) {
  86. inputLine = receiveMessage();
  87. if (inputLine == null) {
  88. // End of stream reached.
  89. break;
  90. }
  91. nrInputLines++;
  92. if (debugViewNetworkTraffic) {
  93. System.out.println("(c->S," + nrInputLines + ")" + inputLine);
  94. }
  95. if (polylineSize > polylineScanned) {
  96. // We are processing a polyline.
  97. // Read pairs of coordinates separated by commas.
  98. boolean first = true;
  99. for (String coordStr : inputLine.split(",")) {
  100. int coord = Integer.parseInt(coordStr);
  101. if (first) {
  102. polylineXCoords[polylineScanned] = coord;
  103. } else {
  104. polylineYCoords[polylineScanned++] = coord;
  105. }
  106. first = !first;
  107. }
  108. assert first;
  109. } else {
  110. // Process this normally.
  111. processInput(inputLine);
  112. }
  113. }
  114. }
  115. // Some connection error
  116. catch (IOException e) {
  117. System.out.println("Connection error. Quitting ScrollView Server...");
  118. }
  119. System.exit(0);
  120. }
  121. // Parse a comma-separated list of arguments into ArrayLists of the
  122. // possible types. Each type is stored in order, but the order
  123. // distinction between types is lost.
  124. // Note that the format is highly constrained to what the client used
  125. // to send to LUA:
  126. // Quoted string -> String.
  127. // true or false -> Boolean.
  128. // %f format number -> Float (no %e allowed)
  129. // Sequence of digits -> Integer
  130. // Nothing else allowed.
  131. private static void parseArguments(String argList,
  132. ArrayList<Integer> intList,
  133. ArrayList<Float> floatList,
  134. ArrayList<String> stringList,
  135. ArrayList<Boolean> boolList) {
  136. // str is only non-null if an argument starts with a single or double
  137. // quote. str is set back to null on completion of the string with a
  138. // matching quote. If the string contains a comma then str will stay
  139. // non-null across multiple argStr values until a matching closing quote.
  140. // Backslash escaped quotes do not count as terminating the string.
  141. String str = null;
  142. for (String argStr : argList.split(",")) {
  143. if (str != null) {
  144. // Last string was incomplete. Append argStr to it and restore comma.
  145. // Execute str += "," + argStr in Java.
  146. int length = str.length() + 1 + argStr.length();
  147. StringBuilder appended = new StringBuilder(length);
  148. appended.append(str);
  149. appended.append(",");
  150. appended.append(argStr);
  151. str = appended.toString();
  152. } else if (argStr.length() == 0) {
  153. continue;
  154. } else {
  155. char quote = argStr.charAt(0);
  156. // If it begins with a quote then it is a string, but may not
  157. // end this time if it contained a comma.
  158. if (quote == '\'' || quote == '"') {
  159. str = argStr;
  160. }
  161. }
  162. if (str != null) {
  163. // It began with a quote. Check that it still does.
  164. assert str.charAt(0) == '\'' || str.charAt(0) == '"';
  165. int len = str.length();
  166. if (len > 1 && str.charAt(len - 1) == str.charAt(0)) {
  167. // We have an ending quote of the right type. Now check that
  168. // it is not escaped. Must have an even number of slashes before.
  169. int slash = len - 1;
  170. while (slash > 0 && str.charAt(slash - 1) == '\\')
  171. --slash;
  172. if ((len - 1 - slash) % 2 == 0) {
  173. // It is now complete. Chop off the quotes and save.
  174. // TODO(rays) remove the first backslash of each pair.
  175. stringList.add(str.substring(1, len - 1));
  176. str = null;
  177. }
  178. }
  179. // If str is not null here, then we have a string with a comma in it.
  180. // Append, and the next argument at the next iteration, but check
  181. // that str is null after the loop terminates in case it was an
  182. // unterminated string.
  183. } else if (floatPattern.matcher(argStr).matches()) {
  184. // It is a float.
  185. floatList.add(Float.parseFloat(argStr));
  186. } else if (argStr.equals("true")) {
  187. boolList.add(true);
  188. } else if (argStr.equals("false")) {
  189. boolList.add(false);
  190. } else if (intPattern.matcher(argStr).matches()) {
  191. // Only contains digits so must be an int.
  192. intList.add(Integer.parseInt(argStr));
  193. }
  194. // else ignore all incompatible arguments for forward compatibility.
  195. }
  196. // All strings must have been terminated.
  197. assert str == null;
  198. }
  199. /** Executes the LUA command parsed as parameter. */
  200. private static void processInput(String inputLine) {
  201. if (inputLine == null) {
  202. return;
  203. }
  204. // Execute a function encoded as a LUA statement! Yuk!
  205. if (inputLine.charAt(0) == 'w') {
  206. // This is a method call on a window. Parse it.
  207. String noWLine = inputLine.substring(1);
  208. String[] idStrs = noWLine.split("[ :]", 2);
  209. int windowID = Integer.parseInt(idStrs[0]);
  210. // Find the parentheses.
  211. int start = inputLine.indexOf('(');
  212. int end = inputLine.lastIndexOf(')');
  213. // Parse the args.
  214. ArrayList<Integer> intList = new ArrayList<Integer>(4);
  215. ArrayList<Float> floatList = new ArrayList<Float>(2);
  216. ArrayList<String> stringList = new ArrayList<String>(4);
  217. ArrayList<Boolean> boolList = new ArrayList<Boolean>(3);
  218. parseArguments(inputLine.substring(start + 1, end),
  219. intList, floatList, stringList, boolList);
  220. int colon = inputLine.indexOf(':');
  221. if (colon > 1 && colon < start) {
  222. // This is a regular function call. Look for the name and call it.
  223. String func = inputLine.substring(colon + 1, start);
  224. if (func.equals("drawLine")) {
  225. windows.get(windowID).drawLine(intList.get(0), intList.get(1),
  226. intList.get(2), intList.get(3));
  227. } else if (func.equals("createPolyline")) {
  228. windows.get(windowID).createPolyline(intList.get(0));
  229. } else if (func.equals("drawPolyline")) {
  230. windows.get(windowID).drawPolyline();
  231. } else if (func.equals("drawRectangle")) {
  232. windows.get(windowID).drawRectangle(intList.get(0), intList.get(1),
  233. intList.get(2), intList.get(3));
  234. } else if (func.equals("setVisible")) {
  235. windows.get(windowID).setVisible(boolList.get(0));
  236. } else if (func.equals("setAlwaysOnTop")) {
  237. windows.get(windowID).setAlwaysOnTop(boolList.get(0));
  238. } else if (func.equals("addMessage")) {
  239. windows.get(windowID).addMessage(stringList.get(0));
  240. } else if (func.equals("addMessageBox")) {
  241. windows.get(windowID).addMessageBox();
  242. } else if (func.equals("clear")) {
  243. windows.get(windowID).clear();
  244. } else if (func.equals("setStrokeWidth")) {
  245. windows.get(windowID).setStrokeWidth(floatList.get(0));
  246. } else if (func.equals("drawEllipse")) {
  247. windows.get(windowID).drawEllipse(intList.get(0), intList.get(1),
  248. intList.get(2), intList.get(3));
  249. } else if (func.equals("pen")) {
  250. if (intList.size() == 4) {
  251. windows.get(windowID).pen(intList.get(0), intList.get(1),
  252. intList.get(2), intList.get(3));
  253. } else {
  254. windows.get(windowID).pen(intList.get(0), intList.get(1),
  255. intList.get(2));
  256. }
  257. } else if (func.equals("brush")) {
  258. if (intList.size() == 4) {
  259. windows.get(windowID).brush(intList.get(0), intList.get(1),
  260. intList.get(2), intList.get(3));
  261. } else {
  262. windows.get(windowID).brush(intList.get(0), intList.get(1),
  263. intList.get(2));
  264. }
  265. } else if (func.equals("textAttributes")) {
  266. windows.get(windowID).textAttributes(stringList.get(0),
  267. intList.get(0),
  268. boolList.get(0),
  269. boolList.get(1),
  270. boolList.get(2));
  271. } else if (func.equals("drawText")) {
  272. windows.get(windowID).drawText(intList.get(0), intList.get(1),
  273. stringList.get(0));
  274. } else if (func.equals("addMenuBarItem")) {
  275. if (boolList.size() > 0) {
  276. windows.get(windowID).addMenuBarItem(stringList.get(0),
  277. stringList.get(1),
  278. intList.get(0),
  279. boolList.get(0));
  280. } else if (intList.size() > 0) {
  281. windows.get(windowID).addMenuBarItem(stringList.get(0),
  282. stringList.get(1),
  283. intList.get(0));
  284. } else {
  285. windows.get(windowID).addMenuBarItem(stringList.get(0),
  286. stringList.get(1));
  287. }
  288. } else if (func.equals("addPopupMenuItem")) {
  289. if (stringList.size() == 4) {
  290. windows.get(windowID).addPopupMenuItem(stringList.get(0),
  291. stringList.get(1),
  292. intList.get(0),
  293. stringList.get(2),
  294. stringList.get(3));
  295. } else {
  296. windows.get(windowID).addPopupMenuItem(stringList.get(0),
  297. stringList.get(1));
  298. }
  299. } else if (func.equals("update")) {
  300. windows.get(windowID).update();
  301. } else if (func.equals("showInputDialog")) {
  302. windows.get(windowID).showInputDialog(stringList.get(0));
  303. } else if (func.equals("showYesNoDialog")) {
  304. windows.get(windowID).showYesNoDialog(stringList.get(0));
  305. } else if (func.equals("zoomRectangle")) {
  306. windows.get(windowID).zoomRectangle(intList.get(0), intList.get(1),
  307. intList.get(2), intList.get(3));
  308. } else if (func.equals("readImage")) {
  309. PImage image = SVImageHandler.readImage(intList.get(2), in);
  310. windows.get(windowID).drawImage(image, intList.get(0), intList.get(1));
  311. } else if (func.equals("drawImage")) {
  312. PImage image = new PImage(stringList.get(0));
  313. windows.get(windowID).drawImage(image, intList.get(0), intList.get(1));
  314. } else if (func.equals("destroy")) {
  315. windows.get(windowID).destroy();
  316. }
  317. // else for forward compatibility purposes, silently ignore any
  318. // unrecognized function call.
  319. } else {
  320. // No colon. Check for create window.
  321. if (idStrs[1].startsWith("= luajava.newInstance")) {
  322. while (windows.size() <= windowID) {
  323. windows.add(null);
  324. }
  325. windows.set(windowID, new SVWindow(stringList.get(1),
  326. intList.get(0), intList.get(1),
  327. intList.get(2), intList.get(3),
  328. intList.get(4), intList.get(5),
  329. intList.get(6)));
  330. }
  331. // else for forward compatibility purposes, silently ignore any
  332. // unrecognized function call.
  333. }
  334. } else if (inputLine.startsWith("svmain")) {
  335. // Startup or end. Startup is a lua bind, which is now a no-op.
  336. if (inputLine.startsWith("svmain:exit")) {
  337. exit();
  338. }
  339. // else for forward compatibility purposes, silently ignore any
  340. // unrecognized function call.
  341. }
  342. // else for forward compatibility purposes, silently ignore any
  343. // unrecognized function call.
  344. }
  345. /** Called from the client to make the server exit. */
  346. public static void exit() {
  347. System.exit(0);
  348. }
  349. /**
  350. * The main function. Sets up LUA and the server connection and then calls the
  351. * IOLoop.
  352. */
  353. public static void main(String[] args) {
  354. if (args.length > 0) {
  355. SERVER_PORT = Integer.parseInt(args[0]);
  356. }
  357. windows = new ArrayList<SVWindow>(100);
  358. intPattern = Pattern.compile("[0-9-][0-9]*");
  359. floatPattern = Pattern.compile("[0-9-][0-9]*\\.[0-9]*");
  360. // Open a socket to listen on.
  361. try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
  362. System.out.println("Socket started on port " + SERVER_PORT);
  363. // Wait (blocking) for an incoming connection
  364. socket = serverSocket.accept();
  365. System.out.println("Client connected");
  366. // Setup the streams
  367. out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
  368. in =
  369. new BufferedReader(new InputStreamReader(socket.getInputStream(),
  370. "UTF8"));
  371. } catch (IOException e) {
  372. // Something went wrong and we were unable to set up a connection. This is
  373. // pretty much a fatal error.
  374. // Note: The server does not get restarted automatically if this happens.
  375. e.printStackTrace();
  376. System.exit(1);
  377. }
  378. // Enter the main program loop.
  379. IOLoop();
  380. }
  381. }