source: main/trunk/greenstone3/src/java/org/greenstone/applet/phind/JPhind.java@ 38943

Last change on this file since 38943 was 38943, checked in by anupama, 4 weeks ago

JPhind is still being run as an application, but: 1. I've now changed the Java code to have a shutdown handler for if run as a webswing applet and to have a windowClosing handler for when run as a (webswing) application. It didn't appear to need a windowClosing handler before as it doesn't have any internal threads and didn't need thread cleanup, but having a skeleton windowClosing handler is good for if a GS3 developer needs to in future add some cleanup. 2. The mostly static parameters to Phind are now moved back from webswing.config.in to pages/webswing-phind.xsl (which only sets the webswing parameter to indicate it's being run through webswing). We don't actually have an applet element (yet) for Phind webswing: it was being pushed out by the GS3 Java code service PhindPhraseBrowse.java, but that is a different URL that we have replaced with the webswing-phind url. More incremental modifications to come. Next up will be running JPhind as a webswing applet (with or without a shim applet HTML element existing on the page).

File size: 45.4 KB
Line 
1/**********************************************************************
2 * 2024 - rewriting as JApplet to work with webswing (which won't work with AWT Applet)
3 * http://fizyka.umk.pl/~jacek/docs/javatutorial/uiswing/converting/how.html
4 *
5 * Phind.java -- the Phind java applet - modified to work with gsdl3 kjdon
6 *
7 * Copyright 1997-2000 Gordon W. Paynter
8 * Copyright 2000 The New Zealand Digital Library Project
9 *
10 * A component of the Greenstone digital library software
11 * from the New Zealand Digital Library Project at the
12 * University of Waikato, New Zealand.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 *
28 *********************************************************************/
29
30
31/*********************************************************************
32
33JPhind is a JApplet swing port of the awt Phind Applet and can be run
34as an application too due to the inclusion of the main() method.
35
36To run as an applet, ensure PhindPhraseBrowse.java refers to JPhind.class
37(not the old Phind.class Applet), then run in commandline:
38
39appletviewer "http://localhost:8383/greenstone3/gs2-library?a=a&rt=d&s=PhindApplet&c=tudor"
40
41To run this applet as an application, for which purpose this class has a main() method, run as:
42
43java -cp ./web/WEB-INF/lib/gsdl3.jar:./web/WEB-INF/lib/gutil.jar:./web/applet/phind.jar:./web/applet/xercesImpl.jar:./web/applet/xml-apis.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes org.greenstone.applet.phind.JPhind "http://localhost:8383/greenstone3/library?a=a&rt=d&s=PhindApplet&c=tudor" --collection tudor --classifier 1 --phindcgi "?a=a&rt=r&s=PhindApplet&o=xml&ro=1" --library "library" --backdrop "interfaces/default/images/phindbg1.jpg" --xtraParams "orientation=vertical&depth=2&resultorder=L,l,E,e,D,d&fontsize=10&blocksize=10"
44
45Basic version:
46java -cp ./web/WEB-INF/lib/gsdl3.jar:./web/WEB-INF/lib/gutil.jar:./web/applet/phind.jar:./web/applet/xercesImpl.jar:./web/applet/xml-apis.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes org.greenstone.applet.phind.JPhind "http://localhost:8383/greenstone3/gs2-library" --collection tudor --classifier 1 --phindcgi "?a=a&rt=r&s=PhindApplet&o=xml&ro=1" --library "gs2-library" --backdrop "interfaces/default/images/phindbg1.jpg"
47
48To print very basic usage, run this as an application so:
49java -cp ./web/WEB-INF/lib/gsdl3.jar:./web/WEB-INF/lib/gutil.jar:./web/applet/phind.jar:./web/WEB-INF/lib/log4j-1.2.8.jar:./web/WEB-INF/classes org.greenstone.applet.phind.JPhind
50
51
52To use the applet, you'll need to embed it in a web page like this:
53
54<APPLET CODE="Phind.class" WIDTH=500 HEIGHT=500>
55
56 <PARAM NAME=collection VALUE="fao.org">
57 <PARAM NAME=classifier VALUE="1">
58 <PARAM NAME=phindcgi VALUE="http://kowhai/cgi-bin/phindcgi">
59 <PARAM NAME=library VALUE="http://kowhai/cgi-bin/library">
60 <PARAM NAME=backdrop VALUE="http://kowhai/~paynter/transfer/phindtest/green1.jpg">
61 The Phind java applet.
62</APPLET>
63
64There are a bunch of other parameters; these are described in the
65getParameters method below. It is all done for you in Greenstone
66in the document.dm macro file (the _phindapplet_ macro).
67
68You may have problems with Java applet security. Java applet's can only
69open socket connections (including the HTTP connections the applet uses
70to get data) to the same server the applet was loaded from. This means
71that your phindcgi, library, and (optional) backdrop URLs must be on the
72same machine that your web page was loaded from.
73
74**********************************************************************
75
76The applet comprises several classes:
77
781. Phind (this file) is the applet class, loaded by the browser.
79 It also handles network connections.
802. ResultDisplay is a Panel that sits in the applet and displays
81 things; displays are connected in a doubly-linked list.
823. ResultBox holds the results of a query. Result boxes are shown
83 to the user through ResultDisplays. Another doubly-linked list.
844. ResultTitle acts as a caption to a ResultBox describing its contents.
855. ResultCanvas is what the ResultBox data is drawn on. It draws the
86 words on the screen, and handles user input from the mouse.
876. ResultItem represents a single result object (a phrase or document).
887. PhindTitle is for drawing backdrops in ResultDisplays.
89
90**********************************************************************/
91
92package org.greenstone.applet.phind;
93
94import org.webswing.toolkit.api.WebswingUtil;
95
96import org.webswing.toolkit.api.lifecycle.WebswingShutdownListener;
97import org.webswing.toolkit.api.lifecycle.OnBeforeShutdownEvent;
98
99import javax.swing.JApplet;
100import javax.swing.JComponent;
101import javax.swing.JFrame;
102import javax.swing.JLabel;
103import javax.swing.JPanel;
104import javax.swing.JTextField;
105import javax.swing.JButton;
106
107//import java.awt.Choice;
108import java.awt.Color;
109import java.awt.Dimension;
110import java.awt.Font;
111import java.awt.event.ActionEvent;
112import java.awt.event.ActionListener;
113import java.awt.event.WindowAdapter;
114import java.awt.event.WindowEvent;
115
116import java.awt.BorderLayout;
117import java.awt.FlowLayout;
118import java.awt.GridLayout;
119import java.awt.Image;
120
121import java.net.URL;
122import java.net.MalformedURLException;
123import java.io.DataInputStream;
124
125import java.net.Socket;
126import java.net.InetAddress;
127import java.net.UnknownHostException;
128import java.io.IOException;
129
130import java.util.Vector;
131import java.util.Date;
132
133import org.w3c.dom.Element;
134import org.w3c.dom.Document;
135//import javax.xml.parsers.*;
136import org.xml.sax.InputSource;
137import org.apache.xerces.parsers.DOMParser;
138
139import java.util.HashMap;
140import java.util.Map;
141
142public class JPhind extends JApplet
143 implements ActionListener, WebswingShutdownListener {
144
145 int verbosity_ = 3;
146
147 // if run as webswing vs either commandline application or as applet through appletviewer
148 boolean isWebswing = false;
149 // if run as applet vs application
150 boolean isRunAsApplet = true;
151
152 // set only if JPhind object is run as an application
153 URL docBaseURL = null;
154 JLabel statusBar = null;
155 Map<String,String> appParams;
156
157 // What is the collection called?
158 public String collection;
159
160 // Which phind classifier are we using? (There may be more than one.)
161 public String classifier;
162
163 // Internet address of phind resources
164 public String library_address, phindcgi_address;
165
166 // Initial search term
167 public String initialSearch;
168
169 // Number of phrases to retrieve at any one time
170 public int phraseBlockSize;
171
172 // Appearance parameters
173 public boolean vertical;
174 public int depth;
175
176 // Font
177 public int fontSize;
178 public String fontName;
179 public Font plainFont, boldFont;
180
181 // Do we want a background image in the applet?
182 public boolean showImage;
183 public String backdrop_address;
184 public Image backgroundImage;
185 public boolean showBorder;
186
187 // Colours
188 public Color panel_fg, panel_bg,
189 column_1_fg, column_1_bg,
190 column_2_fg, column_2_bg,
191 highlight_fg, highlight_bg,
192 thesaurus_fg, thesaurus_bg, thesaurus_bar_fg, thesaurus_bar_bg,
193 expansion_fg, expansion_bg, expansion_bar_fg, expansion_bar_bg,
194 document_fg, document_bg, document_bar_fg, document_bar_bg,
195 message_fg, message_bg;
196
197 // Column dimensions
198 int column_1_width, column_2_width;
199
200 // Where do we open new windows
201 String searchWindowName, documentWindowName;
202
203 // the mode of operation
204 int mode;
205 final int initMode = 0;
206 final int idleMode = 1;
207 final int searchMode = 2;
208
209 // Elements of the control panel
210 boolean showControlPanel;
211 JLabel titleLabel;
212 JTextField wordField;
213 JButton searchButton, prevButton, nextButton;
214
215 // Holders for the downloaded data
216 JPanel resultPanel;
217 JResultDisplay firstDisplay, lastDisplay;
218
219 // The time at which the last query finished
220 Date lastQueryEndTime;
221
222 // lastQueryEndTime is stored to ensure a 1 second gap between a query
223 // returning and a new one beginning. It is needed because the FAO
224 // folks in Rome have a huge lag, and frquently click several times
225 // while they wait; these clicks are turned into new queries, which
226 // they await again. It is no elegant solution, but it seems like the
227 // easiest, given that I don't know threads.
228 // 1. The search button is easy to disable, and is disabled when a
229 // socket connection is in progress.
230 // 2. ResutCanvas widgets can't be similarly disabled because the
231 // browser hides or wipes them, which looks very bad.
232 // 3. I cannot just ignore clicks on canvasses because the browser
233 // caches the clicks while the socket connection is going on, and then
234 // sends them through afterwards, when the canvas is accepting clicks
235 // again.
236 // 4. Current sequence of events is to record the time the last query
237 // ends, then whenever a click happens make sure a second has past. if
238 // you double-click the the first query is sent, returns, end-tie is
239 // set, and the second (and any others made during query time) is
240 // *immediately* processed, but since 1 second hasn't past it is
241 // ignored.
242
243
244 public JPhind() { super(); /*super.init();*/ }
245
246 public JPhind(String[] args) {
247
248 this.isRunAsApplet = false;
249 try {
250 this.docBaseURL = new URL(args[0]);
251 } catch(MalformedURLException mue) {
252 mue.printStackTrace();
253 System.err.println("*** Unable to instantiate URL from parameter: " + args[0]);
254 System.exit(-1);
255 }
256
257 appParams = new HashMap<String,String>((args.length+1)/2);
258
259 String key = null;
260 String value = null;
261 for(int i = 1; i < args.length; i++) { // after arg0, have key-value pairs (params)
262 if(i%2==1) {
263 key = args[i].substring(2); // remove -- prefix of paramname
264 //System.err.println("got key: " + key);
265 } else {
266 value = args[i];
267 appParams.put(key, value);
268 //System.err.println("got value: " + value);
269
270
271 // HttpUtils.parseQueryString() deprecated, so hacking decode xtra key-value pairs
272 // https://stackoverflow.com/questions/13592236/parse-a-uri-string-into-name-value-collection?page=1&tab=scoredesc#tab-top
273 if(key.equals("xtraParams")) {
274 value = value.replace("&amp;", "&"); // just in case we have html entities
275 parseXtraParams(value, "=", "&", appParams);
276 }
277 key = value = null;
278 }
279 }
280
281 isWebswing = appParams.getOrDefault("webswing", "0").equals("1") ? true : false;
282
283 // manually calling (J)Applet method init()
284 init();
285 }
286
287 /**
288 * Overriding (J)Applet method getParameter to first check appParams map
289 * if Phind was run as an application.
290 * If run as an applet, we still check the appParams first for if the param-name
291 * exists in any xtraParams manually parsed into appParams, before finally
292 * checking the Applet method getParameter().
293 * https://stackoverflow.com/questions/15905127/overridden-methods-in-javadoc
294 */
295 @Override
296 public String getParameter(String name) {
297 if(!isRunAsApplet) {
298 return appParams.get(name);
299 }
300 else {
301 if(appParams != null) {
302 String value = appParams.get(name);
303 if(value != null) {
304 return value;
305 }
306 }
307 return super.getParameter(name);
308 }
309 }
310
311 @Override
312 public void stop() {
313 if(!isRunAsApplet) {
314 System.exit(-1);
315 }
316 else {
317 super.stop(); // or put this applet's stop() method's contents here if there was one
318 }
319 }
320
321 public String getAppletInfo() {
322 return "Phind by Gordon Paynter ([email protected]). Copyright 1997-2000.";
323 }
324
325 // Given a string xtraParams of key-value pairs separatod by pairSeparators,
326 // parses out each (key, value) and puts them into the given map, allocating it if
327 // necessary, and returns this map.
328 Map parseXtraParams(String xtraParams, String kvSeparator, String kvPairSeparator, Map map) {
329
330 // String.split() is preferred over Tokenizer but 2nd parameter behaves differently
331 // than I expected.
332 // https://docs.oracle.com/javase/6/docs/api/java/lang/String.html#split%28java.lang.String,%20int%29
333 String[] param_list = xtraParams.split(kvPairSeparator, 0);// 0 means for all occurrences
334
335 if(map == null) {
336 map = new HashMap<String,String>(param_list.length);
337 }
338
339 for(String key_val : param_list) {
340 String[] paramPair = key_val.split(kvSeparator, 2); // get first 2 strings, key and val
341 //System.err.println("key_val: " + key_val);
342 if(paramPair.length == 2) {
343 String xtraParamsKey = paramPair[0];
344 String xtraParamsVal = paramPair[1];
345 // Let's remove any bookending quotes from value, this is necessary for some
346 // values when run as a webswing applet
347 if(xtraParamsVal.startsWith("\"") && xtraParamsVal.endsWith("\"")) {
348 xtraParamsVal = xtraParamsVal.substring(1, xtraParamsVal.length()-1);
349 }
350
351 if (verbosity_ >= 4) {
352 System.err.println("*** xtraParams key - val: " + xtraParamsKey + " - " + xtraParamsVal);
353 }
354 map.put(xtraParamsKey, xtraParamsVal);
355 }
356 }
357
358 return map;
359 }
360
361 public void init() {
362
363 mode = initMode;
364
365 // Read applet parameters
366 getParameters();
367
368 // if running as a *webswing applet*, need to do extra work when shutdown is called
369 // It will be triggered from a JavaScript call to webswing's kill(). By default that
370 // generates a windowClosing event, which is only detected if we're running as a
371 // webswing application. For a webswing applet, we add a shutdownlistener to do more
372 // when JS calls webswing's kill(): Phind has no threads that we need to implement
373 // stop() and destroy() (we'll call them in sequence anyway to futureproof the shutdown)
374 // but especially, we call System.exit(0) on shutdown, which we do NOT want to do
375 // if we're running as a regular applet instead of webswing applet.
376 if(isWebswing && isRunAsApplet) {
377 WebswingUtil.getWebswingApi().addShutdownListener(this);
378 }
379
380
381 // Initialise the user interface
382 setBackground(panel_bg);
383 lastQueryEndTime = new Date();
384
385 // fonts used to output text
386 plainFont = new Font(fontName, Font.PLAIN, fontSize);
387 boldFont = new Font(fontName, Font.BOLD, fontSize);
388
389 // The phind applet layout manager
390 setLayout(new BorderLayout());
391
392 // Panel containing the displays is in the center of the display
393 resultPanel = new JPanel();
394 if (vertical) {
395 resultPanel.setLayout(new GridLayout(depth,1,0,2));
396 } else {
397 System.out.println("horizontal");
398 resultPanel.setLayout(new GridLayout(1,depth,2,0));
399 }
400 add(resultPanel, BorderLayout.CENTER);
401
402 // Manual status bar to mimic applet's default one, in case we
403 // want one if we run Collage as application And in fact, when
404 // webswing runs our applet, we never get a status bar. So we
405 // can create a custom status bar now even if we're running as
406 // an applet. When this applet is run through the
407 // appletviewer we might end up with 2 status bars.
408
409 //if(!isRunAsApplet) {
410 statusBar = new JLabel();
411 this.add(statusBar, BorderLayout.SOUTH);
412 Dimension d = statusBar.getSize();
413 d.height = fontSize + 10;
414 statusBar.setPreferredSize(d);
415 //}
416
417 // Create ResultDisplays and place into the interface
418 JResultDisplay d1, d2 = null;
419 firstDisplay = new JResultDisplay(this, null);
420 resultPanel.add(firstDisplay);
421
422 if (depth == 1) {
423 lastDisplay = firstDisplay;
424 } else {
425 d1 = firstDisplay;
426 for (int i = 2; i <= depth; i++) {
427 d2 = new JResultDisplay(this, d1);
428 resultPanel.add(d2);
429 d1 = d2;
430 }
431 lastDisplay = d2;
432 }
433
434 // The control panel
435 initialiseControlPanel();
436
437 // lets get started then
438 setStatus("Welcome to Phind.");
439 mode = idleMode;
440
441 // Perform initial search, if requested
442 if (initialSearch.length() > 0) {
443 searchForWord(initialSearch);
444 }
445
446 }
447
448
449 // Display a message in the status bar
450 void setStatus(String status) {
451 showStatus(status);
452 }
453
454 // The user performs an action in the interface
455 /* public boolean action(Event evt, Object arg) {
456
457 if (evt.getSource() == searchButton) {
458 System.out.println("evt source ==searchButton");
459 searchForWord(getSearchTerm());
460 } else if (evt.getSource() == wordField) {
461 System.out.println("evt source ==wordField");
462 searchForWord(getSearchTerm());
463 } else if (evt.getSource() == prevButton) {
464 shiftPrevious();
465 } else if (evt.getSource() == nextButton) {
466 shiftNext();
467 } else {
468 System.out.println("unknown action: " + evt.toString()
469 + ", object: " + arg.toString());
470 }
471 return true;
472 }
473
474 */
475
476 public void actionPerformed(ActionEvent evt) {
477
478 JComponent target = (JComponent)evt.getSource();
479 if (target==searchButton) {
480 System.out.println("search button pressed");
481 searchForWord(getSearchTerm());
482 } else if (target == wordField) {
483 System.out.println("word field entered");
484 searchForWord(getSearchTerm());
485 } else if (target == prevButton) {
486 System.out.println("prev button pressed");
487 shiftPrevious();
488 }else if (target == nextButton) {
489 System.out.println("prev button pressed");
490 shiftNext();
491 } else {
492 System.out.println("unknown action: " + evt.toString() );
493
494 }
495 }
496
497 // implementing WebswingShutdownListener
498 /**
499 * Invoked before Webswing requests application to exit.
500 * Do not execute long operations in this listener - listener execution will be interrupted if blocking for &gt; 3 seconds and the application will exit without delay.
501 * Connection to server is still open when this callback is triggered.
502 * This method can delay the shutdown. Calling {@link WebswingApi#resetInactivityTimeout()} within the delay period will cancel the shutdown sequence.
503 *
504 * This method is not called on the event dispatch thread.
505 *
506 * @param event Event contains the reason of this shutdown - either triggered from Admin console's rest interface or by inactivity
507 * @return number of seconds to delay the shutdown, returning 0 will cause shutdown without delay (even if {@link WebswingApi#resetInactivityTimeout()} has been called)
508 */
509 public int onBeforeShutdown(OnBeforeShutdownEvent event) {
510 return 0; // seconds to delay before starting shutdown procedure
511 }
512
513 // If you add more cleanup code here for when run as a webswing applet,
514 // think if it should be added in the windowClosing handler in main() as well.
515 /**
516 * Invoked when Webswing requests swing application to exit.
517 * This method should cause this process to exit (not necessarily in the same thread).
518 * When this method is called, connection to server is already closed
519 *
520 * This method is not called on the event dispatch thread.
521 */
522 public void onShutdown() {
523 // https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/applet/Applet.html#destroy()
524 // destroy(): "Called by the browser or applet viewer to inform this applet that
525 // it is being reclaimed and that it should destroy any resources that it has
526 // allocated. The stop method will always be called before destroy."
527 // Running in webswing code here now, we're in the position of the browser at this point,
528 // and must keep to the above contract.
529 if (verbosity_ >= 3) {
530 System.err.println("------- JPhind: WebswingShutdownListener.onShutdown() - shutting down");
531 }
532 stop();
533 destroy();
534
535 // System.exit(0) is not done for regular applets, but this shutdown handler is *only*
536 // registered if Phind is run as a *webswing applet*. Webswing running applets isn't
537 // entirely the same as a browser running applets, but more like a framework that
538 // launches an applet semi like an application (perhaps in a container).
539 // We found we could call System.exit(0) on a webswing applet here and it doesn't have
540 // the side-effect of shutting down the webswing running it or anything, so it seems
541 // fine to do, and we feel we *need* to do it as we'd like to clean up resources onShutdown
542 // Note that onShutdown is a custom handler we register if we run as a webswing applet,
543 // so that our webswing applet responds as we intend to the JavaScript kill() call that
544 // comes in when a user navigates away from the page running the webswing applet.
545 // Without implememnting WebswingShutdownListener, the webswing applet would not have
546 // terminated, but still linger on. Phind is not a multi-threaded applet, so the calls to
547 // stop() and destroy() above do not stop any internal threads launched, but the Phind
548 // program would still have allocated memory and we'd like them deallocated when the
549 // user navigates away.
550 System.exit(0);
551
552 }
553
554 // Search for a word
555 //
556 // Called on two occasions:
557 // when the "Search" Button is pressed, or
558 // to perform an "initial search"
559 void searchForWord(String searchWord) {
560
561 System.err.println("in searchforword!!");
562 if (mode == idleMode) {
563
564 setSearchTerm(searchWord);
565
566 // Convert the String from UTF8 charaters into
567 // an encoding that is okay for a URL.
568 searchWord = URLUTF8Encoder.encode(searchWord);
569
570 // Look up the word
571 if (searchWord.length() > 1) {
572 setStatus("searching for \"" + searchWord + "\"");
573 firstDisplay.emptyContents();
574 JResultBox result = lookupPhraseOnServer(null, false, searchWord, searchWord, 2);
575
576 // if there is an error, return
577 if (result == null) {
578 setStatus("No results for \"" + searchWord + "\"");
579 return;
580 }
581
582 // display the result
583 result.display = firstDisplay.display(result);
584 result.setSize(result.display.getSize());
585 result.paintAll(result.getGraphics());
586 }
587
588 enablePreviousAndNext();
589 }
590 }
591
592
593 // Search for a phrase
594 //
595 // If querymode is 2, the user has clicked on a phrase.
596 // If querymode is 3, the user has requested more phrases.
597 // If querymode is 4, the user has requested more documents.
598 void searchForPhrase(JResultBox source, String key, String phrase, int queryMode) {
599
600 // System.out.println("searchForPhrase: " + key + " " + phrase + " " + queryMode);
601
602 if (mode == idleMode) {
603
604 // If we are going to replace the first ResultDisplay, then empty it
605 if (queryMode <= 2) {
606 if (source.display.next != null) source.display.next.emptyContents();
607 }
608
609 // look up the word
610 setStatus("Searching for \"" + phrase + "\"");
611 JResultBox result = lookupPhraseOnServer(source, true, key, phrase, queryMode);
612 if (result == null) {
613 setStatus("No result for \"" + phrase + "\"");
614 return;
615 }
616
617 // If this is not already displayed, display it in the last free spot
618 if (queryMode <= 2) {
619 result.display = lastDisplay.display(result);
620 result.setSize(result.display.getSize());
621 result.paintAll(result.getGraphics());
622 }
623
624 enablePreviousAndNext();
625 }
626 }
627
628
629 // Look up a phrase (or symbol) on the server
630 //
631 // Arguments are the source of the query (a ResultBox, or null if the
632 // query comes from hitting the search button), the key to search for
633 // (the text of a phrase or a symbol number), the phrase as a string,
634 // and the query mode.
635 // Query modes are:
636 // 0 = obsolete
637 // 1 = obsolete
638 // 2 = get first N phrases and URLs,
639 // 3 = get another N phrases into same window
640 // 4 = get another N documents into same window
641 // 5 = get another N thesaurus links into same window
642
643 JResultBox lookupPhraseOnServer(JResultBox source,
644 boolean keyKnown, String key, String phrase,
645 int queryMode) {
646 disableSearchButton();
647 mode = searchMode;
648 JResultBox r = null;
649
650 if (queryMode <= 2) {
651 r = new JResultBox(this, collection, key, phrase, source);
652 } else if ((queryMode == 3) || (queryMode == 4) || (queryMode == 5)) {
653 r = source;
654 }
655
656 try {
657 queryServer(keyKnown, key, queryMode, r);
658 } catch (Exception e) {
659 System.out.println("Phind query error: " + e.toString());
660 setStatus("Query error: " + e.toString());
661 mode = idleMode;
662 enableSearchButton();
663 return null;
664 }
665
666 // The query is finished
667 setStatus(r.c.numberOfItems + " results for \"" + phrase + "\"");
668 mode = idleMode;
669 enableSearchButton();
670 lastQueryEndTime = new Date();
671
672 return r;
673 }
674
675
676 // Query the phindcgi program
677 //
678 // Send a query (a word or symbol number) to the server
679 // and pass the response to a ResultBox.
680
681 void queryServer(boolean keyKnown, String word, int queryMode, JResultBox area)
682 throws IOException {
683
684 // Build the query
685 String query = phindcgi_address + "c=" + collection + "&pc=" + classifier;
686
687 if (keyKnown) {
688 query = query + "&ppnum=" + word;
689 } else {
690 query = query + "&ppnum=0" + "&pptext=" + word;
691 }
692
693
694 // Specify the set of results to return
695 int first_e = 0;
696 int last_e = 0;
697 int first_d = 0;
698 int last_d = 0;
699 int first_l = 0;
700 int last_l = 0;
701
702 // the initial query
703 if (queryMode <= 2) {
704 last_e = phraseBlockSize;
705 last_d = phraseBlockSize;
706 last_l = phraseBlockSize;
707 }
708
709 // add phrases to an existing result set
710 else if (queryMode == 3) {
711 first_e = area.nextPhraseBlock * phraseBlockSize;
712 area.nextPhraseBlock++;
713 last_e = area.nextPhraseBlock * phraseBlockSize;
714 }
715
716 // add documents to existing result set
717 else if (queryMode == 4) {
718 first_d = area.nextDocumentBlock * phraseBlockSize;
719 area.nextDocumentBlock++;
720 last_d = area.nextDocumentBlock * phraseBlockSize;
721 }
722
723 // add thesaurus links to existing result set
724 else if (queryMode == 5) {
725 first_l = area.nextThesaurusLinkBlock * phraseBlockSize;
726 area.nextThesaurusLinkBlock++;
727 last_l = area.nextThesaurusLinkBlock * phraseBlockSize;
728 }
729
730 query = query + "&pfe=" + first_e + "&ple=" + last_e
731 + "&pfd=" + first_d + "&pld=" + last_d
732 + "&pfl=" + first_l + "&pll=" + last_l;
733
734 // Send the query to the phindcgi program
735 System.out.println("1:sending query: " + query);
736 try {
737 URL phindcgi = new URL(query);
738 DataInputStream in = new DataInputStream(phindcgi.openStream());
739 DOMParser parser = new DOMParser();
740 parser.parse(new InputSource(in));
741 Document data_doc = parser.getDocument();
742 Element data_elem = data_doc.getDocumentElement();
743 area.parseXML(data_elem);
744 in.close();
745 } catch (Exception e) {
746 System.err.println( "Error sending query to phindcgi: " + e);
747 e.printStackTrace();
748 }
749 area.repaint();
750 }
751
752 @Override
753 public void showStatus(String msg) {
754 // Either firefox doesn't provide a status bar window for applets any more or webswing
755 // doesn't provide a status window, so we don't see Applet.showStatus() output appear
756 // in webswing-phind and in fact don't even see any Applet statusBar in webswing.
757 // However, since we print useful and interesting information to the status bar,
758 // we'll now always show and print to our manually added statusBar now
759 // not only if(!isRunAsApplet) when we needed to manually create a statusBar.
760 this.statusBar.setText(msg);
761 if(isRunAsApplet) {
762 super.showStatus(msg);
763 }
764 }
765
766 @Override
767 public URL getDocumentBase() {
768 if(!isRunAsApplet) { // launched as application
769 //System.err.println("*** docBaseURL: " + docBaseURL);
770 return this.docBaseURL;
771 } else {
772 return super.getDocumentBase();
773 }
774 }
775
776 // Tidy up URLs
777 //
778 // Ensure a URL address (as string) has a protocol, host, and file.
779 //
780 // If the URL is a CGI script URL, it should be tidied up so that it is
781 // appropriate to tage attrib=value pairs on the end. This means it
782 // must either end with a "?" or (if it contains a question-mark
783 // internally) end with a "&".
784 String tidy_URL(String address, boolean isCGI) {
785
786 System.err.println("tidy URL: " + address);
787
788 // make sure the URL has protocol, host, and file
789 if (address.startsWith("http") || address.startsWith("https")) {
790 // the address has all the necessary components
791 } else if (address.startsWith("/")) {
792 // there is not protocol and host
793 URL document = getDocumentBase();
794 //if(document == null) {
795 // document = getDocumentBase(address);
796 //}
797 String port = "";
798 if (document.getPort()!=-1) {
799 port = ":" + document.getPort();
800 }
801 address = "http://" + document.getHost() + port + address;
802 } else {
803 // this URL is relative to the directory the document is in
804 URL document = getDocumentBase();
805 //if(document == null) {
806 // document = getDocumentBase(address);
807 //}
808 String directory = document.getFile();
809 int end = directory.lastIndexOf('/');
810 String port = "";
811 if (document.getPort()!=-1) {
812 port = ":" + document.getPort();
813 }
814 directory = directory.substring(0,end + 1);
815 address = "http://" + document.getHost() + port + directory + address;
816
817 }
818
819 // if the URL is a cgi script, make sure it has a "?" in ti,
820 // and that it ends with a "?" or "&"
821 if (isCGI) {
822 if (address.indexOf((int) '?') == -1) {
823 address = address + "?";
824 } else if (!address.endsWith("?")) {
825 address = address + "&";
826 }
827 }
828
829 return address;
830 }
831
832
833
834 // Open an arbitrary web page
835 void displayWebPage(String address, String window) {
836 try {
837 URL url= new URL(address);
838 if(isRunAsApplet) {
839 if (window.length() > 0) {
840 getAppletContext().showDocument(url, window);
841 } else {
842 getAppletContext().showDocument(url);
843 }
844 } else if(isWebswing) { // webswing and not applet but application
845 if (window.length() > 0) {
846 WebswingUtil.getWebswingApi().sendActionEvent("openURL",
847 url.toString() + " - " +window,
848 null);
849 } else {
850 WebswingUtil.getWebswingApi().sendActionEvent("openURL", url.toString(), null);
851 }
852 } else { // else application, but not webswing, TODO: open browser at the URL
853 System.err.println("Phind.displayWebPage() for non-applet and non-webswing application is not yet implemented.");
854 }
855 } catch (Exception e) {
856 System.out.println("Cannot open web page: " + e.toString());
857 }
858 }
859
860
861 // Get the applet parameters
862 void getParameters() {
863
864 // To get the webswing applet version of JPhind to work when
865 // specifiying custom parameters outside the webswing.config.in file,
866 // we have xtraParams, a string of key1::value1;;key2::value2 pairs
867 // that we first need to extract and add to final list of applet parameters
868 // we're working with.
869 String xtraParams = getParameter("xtraParams");
870 if(xtraParams != null) {
871 // will optionally create appParams and add the key,value pairs in xtraParams into it
872 // after splitting key::value;;k2::v2 etc
873 appParams = parseXtraParams(xtraParams, "::", ";;", appParams);
874 }
875
876 verbosity_ = parameterValue("verbosity", verbosity_);
877
878 String webswing = parameterValue("webswing", "0");
879 isWebswing = webswing.equals("1") ? true : false;
880
881 // What is this collection called?
882 collection = parameterValue("collection");
883
884 String docBase = getDocumentBase().toString();
885 int index = -1;
886 // Fallback for webswing, as customising applet param "collection" didn't work
887 // and the ideally-customisable collection and library applet params remain null
888 // This assumes webswing running the applet has a docBase URL of the form:
889 //docBase = "http://localhost:8383/greenstone3/library/collection/tudor/page/phind";
890 if(collection == null) {
891 //System.err.println("*** docBase: " + docBase);
892 index = docBase.indexOf("collection/");
893 if(index != -1) {
894 index += "collection/".length();
895 int endIndex = docBase.indexOf("/", index);
896 collection = docBase.substring(index, endIndex);
897 //System.err.println("*** collection: " + docBase.substring(index, endIndex) );
898 }
899 }
900
901 System.out.println("Phind collection: " + collection);
902
903 // Which of the collection's classifiers are we using?
904 classifier = parameterValue("classifier", "1");
905 System.out.println("Phind classifier: " + classifier);
906
907 // Where is the Greenstone library
908 library_address = parameterValue("library");
909
910 // Fallback for webswing, as customising applet param "library" didn't work
911 if(library_address == null) {
912 index = docBase.indexOf("/collection");
913 if(index != -1) {
914 String docBasePrefix = docBase.substring(0, index);
915 index = docBasePrefix.lastIndexOf("/");
916 if(index != -1) {
917
918 library_address = docBasePrefix.substring(index+1);
919 //System.err.println("*** library: " + docBasePrefix.substring(index+1) );
920 }
921 }
922 }
923 library_address = tidy_URL(library_address, true);
924 System.out.println("Phind library: " + library_address);
925
926 // Where is the phind CGI script
927 // we assume this is relative to the greenstone library
928 phindcgi_address = parameterValue("library")+parameterValue("phindcgi");
929 phindcgi_address = phindcgi_address.replace("&amp;", "&");
930 phindcgi_address = tidy_URL(phindcgi_address, true);
931 System.out.println("Phind phindcgi: " + phindcgi_address);
932
933
934 // Is there a default search term?
935 initialSearch = parameterValue("initial_search", "");
936
937 // Should we display the control panel
938 showControlPanel = true;
939 if (parameterValue("control_panel", "show").toLowerCase().equals("hide")) {
940 showControlPanel = false;
941 }
942
943 // Should we show a background image?
944 backdrop_address = parameterValue("backdrop", "");
945 if (backdrop_address.length() > 0) {
946 backdrop_address = tidy_URL(backdrop_address, false);
947 System.out.println("Phind backdrop URL: " + backdrop_address);
948
949 try {
950 URL backdrop_url = new URL(backdrop_address);
951 backgroundImage = getImage(backdrop_url);
952 showImage = true;
953 } catch (Exception e) {
954 System.out.println("Phind could not load " + backdrop_address);
955 showImage = false;
956 }
957 }
958
959 // Should we draw a border?
960 showBorder = parameterValue("border", "on").equals("off");
961
962 // Are the windows arranged vertically or horizontally
963 if (parameterValue("orientation", "vertical").toLowerCase().startsWith("hori")) {
964 vertical = false;
965 } else {
966 vertical = true;
967 }
968
969 // How many phind windows are there?
970 depth = parameterValue("depth", 3);
971
972 // Result sort order
973 // Standard is "LlEeDd", expansion-first is "EeLlDd"
974 String order = parameterValue("resultorder", "standard");
975 if (!order.equals("standard")) {
976 int next = 20;
977 ResultItem.sortMessage = next;
978 for (int x = 0; x < order.length(); x++) {
979 if (order.charAt(x) == ',') {
980 next--;
981 } else if (order.charAt(x) == 'L') {
982 ResultItem.sortLinkItem = next;
983 } else if (order.charAt(x) == 'l') {
984 ResultItem.sortMoreLinks = next;
985 } else if (order.charAt(x) == 'E') {
986 ResultItem.sortPhraseItem = next;
987 } else if (order.charAt(x) == 'e') {
988 ResultItem.sortMorePhrases = next;
989 } else if (order.charAt(x) == 'D') {
990 ResultItem.sortDocumentItem = next;
991 } else if (order.charAt(x) == 'd') {
992 ResultItem.sortMoreDocuments = next;
993 }
994 }
995 System.out.println("link: " + ResultItem.sortLinkItem);
996 System.out.println("exps: " + ResultItem.sortPhraseItem);
997 System.out.println("docs: " + ResultItem.sortDocumentItem);
998
999 }
1000
1001 // How many phrases should we fetch at any given time?
1002 phraseBlockSize = parameterValue("blocksize", 10);
1003
1004 // What font should we use?
1005 fontSize = parameterValue("fontsize", 10);
1006 fontName = parameterValue("fontname", "Helvetica");
1007
1008 // Column dimensions
1009 column_1_width = parameterValue("first_column_width", 6);
1010 column_2_width = parameterValue("second_column_width", column_1_width);
1011
1012 // Where do we open new windows
1013 searchWindowName = parameterValue("search_window", "phindsearch");
1014 documentWindowName = parameterValue("document_window", "phinddoc");
1015
1016 // Colours
1017 panel_fg = parameterValue("panel_fg", Color.black);
1018 panel_bg = parameterValue("panel_bg", Color.white);
1019
1020 highlight_bg = parameterValue("highlight_bg", Color.yellow);
1021
1022 expansion_fg = parameterValue("expansion_fg", Color.black);
1023 thesaurus_fg = parameterValue("thesaurus_fg", new Color(0, 100, 0));
1024 document_fg = parameterValue("document_fg", Color.blue);
1025
1026 thesaurus_bar_fg = parameterValue("thesaurus_bar_fg", Color.black);
1027 expansion_bar_fg = parameterValue("expansion_bar_fg", Color.black);
1028 document_bar_fg = parameterValue("document_bar_fg", Color.black);
1029
1030 thesaurus_bar_bg = parameterValue("thesaurus_bar_bg", new Color(160, 160, 190));
1031 expansion_bar_bg = parameterValue("expansion_bar_bg", new Color(255, 200, 200));
1032 document_bar_bg = parameterValue("document_bar_bg", new Color(150, 193, 156));
1033
1034 column_1_fg = parameterValue("first_column_fg", Color.black);
1035 column_1_bg = parameterValue("first_column_bg", new Color(235, 245, 235));
1036 column_2_fg = parameterValue("second_column_fg", Color.black);
1037 column_2_bg = parameterValue("second_column_bg", new Color(200, 220, 200));
1038
1039 message_fg = parameterValue("message_fg", Color.black);
1040 message_bg = parameterValue("message_bg", Color.white);
1041
1042 // Colours I don't use, yet
1043 // thesaurus_bg = parameterValue("thesaurus_bg", Color.white);
1044 // expansion_bg = parameterValue("expansion_bg", Color.white);
1045 // document_bg = parameterValue("document_bg", Color.white);
1046 }
1047
1048 // Get the value of a parameter given its name.
1049 // There are many types of parameters, hence the variety of functions.
1050
1051 // Get a REQUIRED string. Stop the applet if we cannot.
1052 String parameterValue(String name) {
1053 try {
1054 return getParameter(name);
1055 } catch (Exception e) {
1056 System.err.println("Phind: you must give a parameter for \""
1057 + name + "\". Stopping.");
1058 stop();
1059 }
1060 return "";
1061 }
1062
1063 // Get an optional parameter. Return a default if we cannot.
1064 String parameterValue(String name, String defaultValue) {
1065 String text = getParameter(name);
1066 if (text == null) {
1067 return defaultValue;
1068 }
1069 System.out.println("Phind " + name + ": " + text);
1070 return text;
1071 }
1072
1073 int parameterValue(String name, int defaultValue) {
1074 int value;
1075 try {
1076 value = Integer.parseInt(getParameter(name));
1077 } catch (Exception e) {
1078 return defaultValue;
1079 }
1080 System.out.println("Phind " + name + ": " + value);
1081 return value;
1082 }
1083
1084 Color parameterValue(String name, Color defaultValue) {
1085
1086 String text = getParameter(name);
1087 if (text == null) {
1088 return defaultValue;
1089 }
1090 text = text.toLowerCase();
1091
1092 // a number of the form "#ddffee"
1093 if (text.startsWith("#") && (text.length() == 7)) {
1094 text = text.substring(1);
1095 int r, g, b;
1096 try {
1097 r = Integer.parseInt(text.substring(0,2), 16);
1098 g = Integer.parseInt(text.substring(2,4), 16);
1099 b = Integer.parseInt(text.substring(4), 16);
1100 return new Color(r, g, b);
1101 } catch (Exception e) {
1102 return defaultValue;
1103 }
1104 }
1105
1106 // a known Java colour string
1107 else if (text.equals("black")) { return Color.black; }
1108 else if (text.equals("blue")) { return Color.blue; }
1109 else if (text.equals("cyan")) { return Color.cyan; }
1110 else if (text.equals("darkgray")) { return Color.darkGray; }
1111 else if (text.equals("gray")) { return Color.gray; }
1112 else if (text.equals("green")) { return Color.green; }
1113 else if (text.equals("lightgray")) { return Color.lightGray; }
1114 else if (text.equals("magenta")) { return Color.magenta; }
1115 else if (text.equals("orange")) { return Color.orange; }
1116 else if (text.equals("pink")) { return Color.pink; }
1117 else if (text.equals("red")) { return Color.red; }
1118 else if (text.equals("white")) { return Color.white; }
1119 else if (text.equals("yellow")) { return Color.yellow; }
1120
1121 return defaultValue;
1122 }
1123
1124
1125 // Control panel operations
1126
1127 // Initialise the control panel
1128 void initialiseControlPanel() {
1129
1130 if (showControlPanel) {
1131 JPanel p1 = new JPanel();
1132 this.add(p1, BorderLayout.NORTH);
1133 p1.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
1134
1135 searchButton = new JButton("Search");
1136 searchButton.setFont(boldFont);
1137 //searchButton.setEnabled(true);
1138 searchButton.addActionListener(this);
1139 p1.add(searchButton);
1140
1141 JLabel tempLabel = new JLabel(" for");
1142 tempLabel.setFont(boldFont);
1143 p1.add(tempLabel);
1144
1145 wordField = new JTextField(12);
1146 wordField.setFont(boldFont);
1147 wordField.addActionListener(this);
1148 p1.add(wordField);
1149
1150 JLabel temp2 = new JLabel(" ");
1151 p1.add(temp2);
1152
1153 prevButton = new JButton("Previous");
1154 prevButton.setFont(boldFont);
1155 prevButton.addActionListener(this);
1156 prevButton.setEnabled(false);
1157
1158 p1.add(prevButton);
1159
1160 nextButton = new JButton(" Next ");
1161 nextButton.setFont(boldFont);
1162 nextButton.addActionListener(this);
1163 nextButton.setEnabled(false);
1164 p1.add(nextButton);
1165
1166 }
1167 }
1168
1169 // Button and field functionality
1170
1171 // Enable and disable the word field
1172 void enableSearchButton() {
1173 if (showControlPanel) {
1174 searchButton.setEnabled(true);
1175 }
1176 }
1177 void disableSearchButton() {
1178 if (showControlPanel) {
1179 searchButton.setEnabled(false);
1180 }
1181 }
1182
1183 // Get and set the search text in the wordField
1184 String getSearchTerm() {
1185 if (showControlPanel) {
1186 return wordField.getText();
1187 } else {
1188 return initialSearch;
1189 }
1190 }
1191 void setSearchTerm(String word) {
1192 if (showControlPanel) {
1193 wordField.setText(word);
1194 }
1195 }
1196
1197 // Enable or disable the "Previous" and "Next" buttons
1198 void enablePreviousAndNext() {
1199 if (showControlPanel) {
1200 JComponent c = firstDisplay.current;
1201 if (c.getClass().getName().endsWith("JResultBox")) {
1202 if (((JResultBox) c).prevBoxExists()) {
1203 prevButton.setEnabled(true);
1204 } else {
1205 prevButton.setEnabled(false);
1206 }
1207 }
1208
1209 c = lastDisplay.current;
1210 if (c.getClass().getName().endsWith("JResultBox")) {
1211 if (((JResultBox) c).nextBoxExists()) {
1212 nextButton.setEnabled(true);
1213 } else {
1214 nextButton.setEnabled(false);
1215 }
1216 }
1217 }
1218 }
1219
1220 // Shift to previous box
1221 //
1222 // If the user clicks "Previous" then scroll up.
1223 void shiftPrevious() {
1224
1225 JComponent c = firstDisplay.current;
1226 if (c.getClass().getName().endsWith("JResultBox")) {
1227
1228 JResultBox b = (JResultBox) c;
1229 if (b.prevBoxExists()) {
1230 b = b.prev;
1231
1232 // empty all the displays
1233 firstDisplay.emptyContents();
1234
1235 // add as many result boxes as there are displays
1236 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
1237 lastDisplay.display(b);
1238 b.setSize(b.display.getSize());
1239 b.paintAll(b.getGraphics());
1240 b = b.next;
1241 }
1242 }
1243 }
1244 enablePreviousAndNext();
1245 }
1246
1247 // Shift to next box
1248 //
1249 // If the user clicks "Next" then scroll down if possible
1250 void shiftNext() {
1251
1252 JComponent c = lastDisplay.current;
1253 if (c.getClass().getName().endsWith("JResultBox")) {
1254
1255 JResultBox b = (JResultBox) c;
1256 if (b.nextBoxExists()) {
1257
1258 // find the new "first" displayed box
1259 c = firstDisplay.current;
1260 b = (JResultBox) c;
1261 b = b.next;
1262
1263 // empty all the displays
1264 firstDisplay.emptyContents();
1265
1266 // add as many result boxes as there are displays
1267 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
1268 lastDisplay.display(b);
1269 b.setSize(b.display.getSize());
1270 b.paintAll(b.getGraphics());
1271 b = b.next;
1272 }
1273 }
1274 }
1275 enablePreviousAndNext();
1276 }
1277
1278 // adding a main method to allow this applet to function as an application also
1279 public static void main(String args[]) {
1280 if(args.length < 9) {
1281 System.err.println("Need minimum --params: <baseURL> --collection <collection> --classifier <classifier> --phindcgi <URL> --library <libURL> [--webswing <1/0>] [--backdrop <ImgURL>] [--xtraParams <key1=value1&key2=value2&...]");
1282 }
1283 else { // collection fao.org, classifier 1, phindcgi url, library url, backdrop imgurl
1284 JPhind phind = new JPhind(args);
1285 JFrame frame = new JFrame("Phind Applet as Application");
1286 frame.getContentPane().add(phind, BorderLayout.CENTER);
1287 frame.setSize(500,500);
1288 // https://stackoverflow.com/questions/19433358/difference-between-dispose-and-exit-on-close-in-java
1289 // default: https://docs.oracle.com/javase/8/docs/api/javax/swing/JFrame.html#EXIT_ON_CLOSE
1290 // don't do EXIT_ON_CLOSE in Applets. But being in main() means we're being run
1291 // as an application.
1292 // When the windowClosing event is received, EXIT_ON_CLOSE will call windowClosing handlers
1293 // and then I think do a System.exit(0).
1294 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
1295 frame.setVisible(true);
1296
1297
1298 // There's no real cleanup to do now, but adding in the skeleton for application shutdown
1299 // if there's ever any need for cleanup code. You'll then want to add cleanup code here
1300 // iff JPhind is run as a application or as a webswing application. Then you'll want to
1301 // add cleanup code to stop()/destroy() too which get called if running as a regular
1302 // applet and also get called by our onShutDown() handler of WebswingShutdownListener
1303 // for the webswing applet case.
1304 frame.addWindowListener(new WindowAdapter() {
1305 public void windowClosing(WindowEvent e) {
1306 // Calling phind.showStatus() here doesn't work, maybe it's too late at this pt
1307 // and the JFrame is already being dismantled?
1308
1309
1310 if(phind.isWebswing) {
1311 // This call will only work if run as *webswing application* and the user
1312 // manually clicked on the JPhind frame's window close button. Not if
1313 // the user navigated away: at that point the webpage has already
1314 // unloaded and its webswing listeners too, so the javascript
1315 // can't echo this message to console:
1316 WebswingUtil.getWebswingApi().sendActionEvent("javaToWebswingJSConsoleLog", "JPhind run as application - quitting now", null);
1317 }
1318
1319 // Print stmts go to the terminal if run as a regular application. If run as
1320 // webswing application/applet, then into packages/tomcat/bin/logs/webswing.log
1321 System.err.println("\n\n*** Exitting the JPhind application...");
1322 }
1323 });
1324 }
1325 }
1326
1327
1328}
Note: See TracBrowser for help on using the repository browser.