source: trunk/gsdl/src/phind/client/Phind.java@ 1636

Last change on this file since 1636 was 1636, checked in by paynter, 24 years ago

Some documentation explaining how to use the applet.

  • Property svn:keywords set to Author Date Id Revision
File size: 18.2 KB
Line 
1/**********************************************************************
2 *
3 * Phind.java -- the Phind java applet
4 *
5 * Copyright 1997-2000 Gordon W. Paynter
6 * Copyright 2000 The New Zealand Digital Library Project
7 *
8 * A component of the Greenstone digital library software
9 * from the New Zealand Digital Library Project at the
10 * University of Waikato, New Zealand.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *
26 *********************************************************************/
27
28
29/*********************************************************************
30
31To use the applet, you'll need to embed it in a web page as follows:
32
33<APPLET CODE="Phind.class" WIDTH=500 HEIGHT=500>
34 <PARAM NAME=phindcgi VALUE="http://kowhai/cgi-bin/phindcgi">
35 <PARAM NAME=library VALUE="http://kowhai/cgi-bin/library">
36 <PARAM NAME=backdrop VALUE="http://kowhai/~paynter/transfer/phindtest/green1.jpg">
37 <PARAM NAME=collection VALUE="fao.org">
38 <PARAM NAME=orientation VALUE="vertical">
39 <PARAM NAME=depth VALUE="2">
40 <PARAM NAME=fontsize VALUE="12">
41 <PARAM NAME=blocksize VALUE="10">
42 The Phind java applet.
43</APPLET>
44
45You may have problems with Java applet security. Java applet's can only
46open socket connections (including the HTTP connections the applet uses
47to get data) to the same server the applet was loaded from. This means
48that your phindcgi, library, and (optional) backdrop URLs must be on the
49same machine that your web page was loaded from.
50
51**********************************************************************
52
53The applet comprises several classes:
54
551. Phind (this file) is the applet class, loaded by the browser.
56 It also handles network connections.
572. ResultDisplay is a Panel that sits in the applet and displays
58 things; displays are connected in a doubly-linked list.
593. ResultBox holds the results of a query. Result boxes are shown
60 to the user through ResultDisplays. Another doubly-linked list.
614. ResultTitle acts as a caption to a ResultBox describing its contents.
625. ResultCanvas is what the ResultBox data is drawn on. It draws the
63 words on the screen, and handles user input from the mouse.
646. ResultItem represents a single result object (a phrase or document).
657. PhindTitle is for drawing backdrops in ResultDisplays.
66
67**********************************************************************/
68
69
70import java.awt.BorderLayout;
71import java.awt.Button;
72import java.awt.Choice;
73import java.awt.Color;
74import java.awt.Component;
75import java.awt.Dimension;
76import java.awt.Event;
77import java.awt.FlowLayout;
78import java.awt.Font;
79import java.awt.GridLayout;
80import java.awt.Image;
81import java.awt.Label;
82import java.awt.Panel;
83import java.awt.TextField;
84
85import java.net.URL;
86import java.io.DataInputStream;
87
88import java.net.Socket;
89import java.net.InetAddress;
90import java.net.UnknownHostException;
91import java.io.IOException;
92
93import java.util.Vector;
94import java.util.Date;
95
96public class Phind extends java.applet.Applet {
97
98 // elements of the page
99 Label titleLabel;
100 TextField wordField;
101 Button searchButton, prevButton, nextButton;
102
103 // Appearance parameters
104 public boolean vertical;
105 public int depth;
106 public int fontSize;
107 public Font plainFont, boldFont;
108
109 // Do we want a background image in the applet?
110 String backdrop_address;
111 public boolean showImage;
112 public Image backgroundImage;
113
114 // Holders for the downloaded data
115 Panel resultPanel;
116 ResultDisplay firstDisplay, lastDisplay;
117
118 // the mode of operation
119 int mode;
120 final int initMode = 0;
121 final int idleMode = 1;
122 final int searchMode = 2;
123
124 // what to search for
125 String collection;
126 int phraseBlockSize;
127
128 //int stopwordLevel = 1;
129 //int lowFrequencyCutoff = 0;
130 //int stemming = 0;
131
132 // where to search
133 Socket connection;
134 String library_address, phindcgi_address;
135 URL library, phindcgi;
136
137 String search_url;
138
139 // browser details
140 String localHost;
141
142 // The time at which the last query finished
143 Date lastQueryEndTime;
144
145 // lastQueryEndTime is stored to ensure a 1 second gap between a query
146 // returning and a new one beginning. It is needed because the FAO
147 // folks in Rome have a huge lag, and frquently click several times
148 // while they wait; these clicks are turned into new queries, which
149 // they await again. It is no elegant solution, but it seems like the
150 // easiest, given that I don't know threads.
151 // 1. The search button is easy to disable, and is disabled when a
152 // socket connection is in progress.
153 // 2. ResutCanvas widgets can'r be similarly disabled because the
154 // browser hides or wipes them, which looks very bad.
155 // 3. I cannot just ignore clicks on canvasses because the browser
156 // caches the clicks while the socket connection is going on, and then
157 // sends them through afterwards, when the canvas is accepting clicks
158 // again.
159 // 4. Current sequence of events is to record the time the last query
160 // ends, then whenever a click happens make sure a second has past. if
161 // you double-click the the first query is sent, returns, end-tie is
162 // set, and the second (and any others made during query time) is
163 // *immediately* processed, but since 1 second hasn't past it is
164 // ignored.
165
166
167 public String getAppletInfo() {
168 return "Phind by Gordon Paynter ([email protected]). Copyright 1997-2000.";
169 }
170
171 public void init() {
172
173 // default search variable values
174 mode = initMode;
175 lastQueryEndTime = new Date();
176
177 // What is this collection called?
178 try {
179 collection = new String(getParameter("collection"));
180 } catch (Exception e) {
181 System.err.println("Phind: You must specify a collection.");
182 stop();
183 }
184 System.out.println("Phind collection: " + collection);
185
186 // Where do we get the phind data
187 try {
188 phindcgi_address = new String(getParameter("phindcgi"));
189 } catch (Exception e) {
190 System.err.println("Phind: You must specify a URL for phindcgi.");
191 stop();
192 }
193 System.out.println("Phind phindcgi: " + phindcgi_address);
194
195 // Where is the collection served from
196 try {
197 library_address = new String(getParameter("library"));
198 } catch (Exception e) {
199 System.err.println("Phind: You must specify a URL for library.");
200 stop();
201 }
202 System.out.println("Phind library: " + library_address);
203
204 // Should we display a background image?
205 showImage = false;
206 try {
207 backdrop_address = new String(getParameter("backdrop"));
208 URL backdrop_url = new URL(backdrop_address);
209 backgroundImage = getImage(backdrop_url);
210 showImage = true;
211 System.out.println("Phind backdrop: " + backdrop_address);
212 } catch (Exception e) {
213 System.out.println("Phind backdrop not set");
214 }
215
216 // Is there a search URL for this collection?
217 try {
218 search_url = new String(getParameter("search_url"));
219 System.out.println("Phind search URL: " + search_url);
220 } catch (Exception e) {
221 System.out.println("Phind search_url not set.");
222 }
223
224 // Are the windows arranged vertically or horizontally
225 vertical = true;
226 try {
227 if (getParameter("orientation").toLowerCase().startsWith("h")) {
228 vertical = false;
229 System.out.println("Phind orientation: horizontal");
230 }
231 } catch (Exception e) {
232 System.out.println("Phind orientation: vertical");
233 }
234
235 // How many phind windows are there?
236 Integer temp;
237 try {
238 temp = Integer.valueOf(getParameter("depth"));
239 depth = temp.intValue();
240 } catch (Exception e) {
241 temp = null;
242 depth = 3;
243 }
244 System.out.println("Phind depth: " + depth);
245
246 // How many phrases should we fetch at any given time?
247 try {
248 temp = Integer.valueOf(getParameter("blocksize"));
249 phraseBlockSize = temp.intValue();
250 } catch (Exception e) {
251 temp = null;
252 phraseBlockSize = 10;
253 }
254 System.out.println("Phind phrase block size: " + phraseBlockSize);
255
256 // How large should the font be?
257 try {
258 temp = Integer.valueOf(getParameter("fontsize"));
259 fontSize = temp.intValue();
260 } catch (Exception e) {
261 temp = null;
262 fontSize = 12;
263 }
264 System.out.println("Phind font size: " + fontSize);
265
266 // browser details
267 try {
268 localHost = InetAddress.getLocalHost().toString();
269 } catch (Exception e) {
270 localHost = "Can't get client host.";
271 }
272 System.out.println("Phind client: " + localHost);
273 System.out.println("");
274
275 // The phind applet layout manager
276 setLayout(new BorderLayout());
277
278 // initialise the user interface
279 setBackground(Color.white);
280 plainFont = new Font("Helvetica", Font.PLAIN, fontSize);
281 boldFont = new Font("Helvetica", Font.BOLD, fontSize);
282
283 // the main Result area
284
285 // the Panel containing the displays is in the center of the display
286 resultPanel = new Panel();
287 if (vertical) {
288 resultPanel.setLayout(new GridLayout(depth,1,0,2));
289 } else {
290 System.out.println("horizontal");
291 resultPanel.setLayout(new GridLayout(1,depth,2,0));
292 }
293 add("Center", resultPanel);
294
295 // Create ResultDisplays and place in the interface
296 ResultDisplay d1, d2 = null;
297 firstDisplay = new ResultDisplay(this, null);
298 resultPanel.add(firstDisplay);
299
300 d1 = firstDisplay;
301 for (int i = 2; i <= depth; i++) {
302 d2 = new ResultDisplay(this, d1);
303 resultPanel.add(d2);
304 d1 = d2;
305 }
306
307 lastDisplay = d2;
308
309
310 // The "control panel"
311 Panel p1 = new Panel();
312 add("North", p1);
313 p1.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
314
315 searchButton = new Button("Search");
316 searchButton.setFont(boldFont);
317 p1.add(searchButton);
318
319 Label tempLabel = new Label(" for");
320 tempLabel.setFont(boldFont);
321 p1.add(tempLabel);
322
323 wordField = new TextField(12);
324 wordField.setFont(boldFont);
325 p1.add(wordField);
326
327 Label temp2 = new Label(" ");
328 p1.add(temp2);
329
330 prevButton = new Button("Previous");
331 prevButton.setFont(boldFont);
332 prevButton.disable();
333 p1.add(prevButton);
334
335 nextButton = new Button(" Next ");
336 nextButton.setFont(boldFont);
337 nextButton.disable();
338 p1.add(nextButton);
339
340 // lets get started then
341 setStatus("Welcome to Phind.");
342 mode = idleMode;
343 }
344
345
346 void setStatus(String status) {
347 showStatus(status);
348 }
349
350 public boolean action(Event evt, Object arg) {
351
352 if (evt.target == searchButton) {
353 searchForWord();
354 } else if (evt.target == wordField) {
355 searchForWord();
356 } else if (evt.target == prevButton) {
357 shiftPrevious();
358 } else if (evt.target == nextButton) {
359 shiftNext();
360 } else {
361 System.out.println("unknown action: " + evt.toString() + ", object: " + arg.toString());
362 }
363 return true;
364 }
365
366
367 // Search for a word
368 //
369 // called when the "Search" Button is pressed
370 void searchForWord() {
371
372 if (mode == idleMode) {
373
374 // transform the word to an appropriate search key
375 String searchWord = wordField.getText().trim().toLowerCase();
376 for (int i = 0; i < searchWord.length(); i++) {
377 if ((searchWord.charAt(i) < 'a') || (searchWord.charAt(i) > 'z')) {
378 searchWord = searchWord.substring(0, i);
379 }
380 }
381 wordField.setText(searchWord);
382
383 // look up the word
384 if (searchWord.length() > 1) {
385 setStatus("searching for \"" + searchWord + "\"");
386 firstDisplay.emptyContents();
387 ResultBox result = lookupPhraseOnServer(null, false, searchWord, searchWord, 2);
388
389 // if there is an error, return
390 if (result == null) {
391 setStatus("No results for \"" + searchWord + "\"");
392 return;
393 }
394
395 // display the result
396 result.display = firstDisplay.display(result);
397 result.resize(result.display.size());
398 result.paintAll(result.getGraphics());
399 }
400
401 enablePreviousAndNext();
402 }
403 }
404
405
406 // Search for a phrase
407 //
408 // If querymode is 2, the user has clicked on a phrase.
409 // If querymode is 3, the user has requested more phrases.
410 // If querymode is 4, the user has requested more documents.
411 void searchForPhrase(ResultBox source, String key, String phrase, int queryMode) {
412
413 // System.out.println("searchForPhrase: " + key + " " + phrase + " " + queryMode);
414
415 if (mode == idleMode) {
416
417 // If we are going to replace the next ResultDisplay, then empty it
418 if (queryMode <= 2) {
419 if (source.display.next != null) source.display.next.emptyContents();
420 }
421
422 // look up the word
423 setStatus("Searching for \"" + phrase + "\"");
424 ResultBox result = lookupPhraseOnServer(source, true, key, phrase, queryMode);
425 if (result == null) {
426 setStatus("No result for \"" + phrase + "\"");
427 return;
428 }
429
430 // If this is not already displayed, display it in the last free spot
431 if (queryMode <= 2) {
432 result.display = lastDisplay.display(result);
433 result.resize(result.display.size());
434 result.paintAll(result.getGraphics());
435 }
436
437 enablePreviousAndNext();
438 }
439 }
440
441
442 // Look up a phrase (or symbol) on the server
443 //
444 // Arguments are the source of the query (a ResultBox, or null if the
445 // query comes from hitting the search button), the key to search for
446 // (the text of a phrase or a symbol number), the phrase as a string,
447 // and the query mode.
448 // Query modes are:
449 // 0 = obsolete
450 // 1 = obsolete
451 // 2 = get first N phrases and URLs,
452 // 3 = get another N phrases into same window
453 // 4 = get another N documents into same window
454
455 ResultBox lookupPhraseOnServer(ResultBox source,
456 boolean keyKnown, String key, String phrase,
457 int queryMode) {
458 searchButton.disable();
459 mode = searchMode;
460 ResultBox r = null;
461
462 if (queryMode <= 2) {
463 r = new ResultBox(this, collection, key, phrase, source);
464 } else if ((queryMode == 3) || (queryMode == 4)) {
465 r = source;
466 }
467
468 try {
469 queryServer(keyKnown, key, queryMode, r);
470 } catch (Exception e) {
471 System.out.println("Phind query error: " + e.toString());
472 setStatus("Query error: " + e.toString());
473 mode = idleMode;
474 searchButton.enable();
475 return null;
476 }
477
478 // The query is finished
479 setStatus(r.c.numberOfPhrases + " results containing \"" + phrase + "\"");
480 mode = idleMode;
481 searchButton.enable();
482 lastQueryEndTime = new Date();
483
484 return r;
485 }
486
487
488 // Query the phindcgi program
489 //
490 // Send a query (a word or symbol number) to the server
491 // Open a socket connection to the server, send it a query,
492 // and pass the result to a ResultBox.
493
494 void queryServer(boolean keyKnown, String word, int queryMode, ResultBox area)
495 throws IOException {
496
497 // Build the query
498 String query = phindcgi_address + "?x=1&c=" + collection;
499
500 if (keyKnown) {
501 query = query + "&n=" + word;
502 } else {
503 query = query + "&p=" + word;
504 }
505
506
507 // Specify the set of results to return
508 int first_e = 0;
509 int last_e = 0;
510 int first_d = 0;
511 int last_d = 0;
512
513 // the initial query
514 if (queryMode <= 2) {
515 last_e = phraseBlockSize;
516 last_d = phraseBlockSize;
517 }
518
519 // add phrases to an existing result set
520 else if (queryMode == 3) {
521 first_e = area.nextPhraseBlock * phraseBlockSize;
522 area.nextPhraseBlock++;
523 last_e = area.nextPhraseBlock * phraseBlockSize;
524 }
525
526 // add documents to existing result set
527 else if (queryMode == 4) {
528 first_d = area.nextDocumentBlock * phraseBlockSize;
529 area.nextDocumentBlock++;
530 last_d = area.nextDocumentBlock * phraseBlockSize;
531 }
532
533 query = query + "&f=" + first_d + "&d=" + last_d
534 + "&g=" + first_e + "&e=" + last_e;
535
536 // Send the query to the phindcgi program
537 System.out.println("sending query: " + query);
538 try {
539 phindcgi = new URL(query);
540 DataInputStream in = new DataInputStream(phindcgi.openStream());
541 byte[] buffer;
542 int availableBytes = 0;
543
544 while (!area.finished) {
545 availableBytes = in.available();
546 if (availableBytes == 0) {
547 // if i had threads i'd do a wait here for 1 second
548 } else {
549 buffer = new byte[availableBytes];
550 in.read(buffer);
551 area.parseBytes(buffer);
552 }
553 }
554
555 in.close();
556 } catch (Exception e) {
557 System.err.println( "Error sending query to phindcgi: " + e);
558 }
559 area.repaint();
560 }
561
562
563 // Previous and Next button functionality
564
565 // enable or disable the "Previous" and "Next" buttons
566 void enablePreviousAndNext() {
567
568 Component c = firstDisplay.current;
569 if (c.getClass().getName().equals("ResultBox")) {
570 if (((ResultBox) c).prevBoxExists()) {
571 prevButton.enable();
572 } else {
573 prevButton.disable();
574 }
575 }
576
577 c = lastDisplay.current;
578 if (c.getClass().getName().equals("ResultBox")) {
579 if (((ResultBox) c).nextBoxExists()) {
580 nextButton.enable();
581 } else {
582 nextButton.disable();
583 }
584 }
585 }
586
587 // Shift to previous box
588 //
589 // If the user clicks "Previous" then scroll up.
590 void shiftPrevious() {
591
592 Component c = firstDisplay.current;
593 if (c.getClass().getName().equals("ResultBox")) {
594
595 ResultBox b = (ResultBox) c;
596 if (b.prevBoxExists()) {
597 b = b.prev;
598
599 // empty all the displays
600 firstDisplay.emptyContents();
601
602 // add as many result boxes as there are displays
603 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
604 lastDisplay.display(b);
605 b.resize(b.display.size());
606 b.paintAll(b.getGraphics());
607 b = b.next;
608 }
609 }
610 }
611 enablePreviousAndNext();
612 }
613
614
615 // Shift to next box
616 //
617 // If the user clicks "Next" then scroll down if possible
618 void shiftNext() {
619
620 Component c = lastDisplay.current;
621 if (c.getClass().getName().equals("ResultBox")) {
622
623 ResultBox b = (ResultBox) c;
624 if (b.nextBoxExists()) {
625
626 // find the new "first" displayed box
627 c = firstDisplay.current;
628 b = (ResultBox) c;
629 b = b.next;
630
631 // empty all the displays
632 firstDisplay.emptyContents();
633
634 // add as many result boxes as there are displays
635 for (int i = 1 ; ((i <= depth) && (b != null)); i++) {
636 lastDisplay.display(b);
637 b.resize(b.display.size());
638 b.paintAll(b.getGraphics());
639 b = b.next;
640 }
641 }
642 }
643 enablePreviousAndNext();
644 }
645
646}
Note: See TracBrowser for help on using the repository browser.