Looking for cheap tickets? Choose JDBC Airlines! The JDBC Airlines applet illustrates remote access to a database through a browser user interface. This example runs as an applet within World Wide Web browsers that support Java. It uses JDBC to connect and retrieve flight schedules from a database. No other middleware is involved. The JDBC driver used for this example is a 100 percent Java driver that directly connects to the database server.
Figure 11-4 shows the JDBC Airlines applet, which is from Connect Software, Inc.
Figure 11-4: Connect Software’s JDBC Airlines applet.
Thanks to its 100 percent Java drivers, this applet is able to run within any Java-compatible WWW browser. Again, no specific classes must be preinstalled on the client machine. The JDBC driver downloads from the Web server along with the applet classes.
The following is the HTML file for this example; it contains the tag to load the applet as well as parameters that provide connection information to the applet. Such connection information includes the driver to use to connect to the database and the database’s URL.
<html> <head> <title> Airplet, the Airline Applet by Connect Software </title> </head> <body bgcolor="#FFFFFF"> <center> <img src="/books/3/397/1/html/2/images/banner.gif"> <p> <applet code=airplet.Airplet width=500 height=600> <param name=driver value=connect.sybase.SybaseDriver> <param name=connection value=’jdbc:sybase://db.mydomain.com:8192/airline;user=guest;password=guest’> </center> </applet> </html>
A Type 4 driver for Sybase was used in this example. If a Type 4 driver is available for your database, refer to its documentation for the format of the URL and possible restrictions when using it over the Internet. If the driver and database listener doesn’t support HTTP-tuneling, firewalls and Web proxies won’t be supported.
Airplet.java is the main applet class. Initialization establishes a connection with the database server. As soon as the connection is established, various panels are prepared and displayed. These panels include a panel with name and preset choices for departure and arrival airports, a panel with search and roundtrip buttons, and a panel with maps and routes between airports.
User events are handled when selecting airport choices and clicking search or roundtrip buttons. A search for flights from the origin to the destination is performed. A method is also provided to load map images from the server.
On the Web | The structure of the database tables plus some sample data is available on this book’s companion Web site at www.hungryminds.com/extras/. Please see the Preface for details on accessing files on the Web site. |
Listing 11-7 shows the source code for the main part of the applet, Airplet. java.
Listing 11-7: Airplet.java
// // Airplet.java - Connect Software’s Airline Applet, a.k.a. jdbc airlines // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // // Connect Software, Inc. // 81 Lansing Street, Suite #411 // San Francisco, CA 94105 // (415) 710-1544 (phone) - (415) 543-6695 (fax) // // email: info@connectsw.com - www: http://www.connectsw.com package airplet; // airplet’s package import java.applet.*; // import a number of java libraries import java.awt.*; import java.io.*; import java.net.*; import java.util.*; import java.sql.*; // import modified jdbc libraries public class Airplet extends java.applet.Applet // main applet class { private TextField nameTo = null; // names of departure and arrival airports private TextField nameFrom = null; private AirportChoice choiceTo = null; // preset lists of departure and arrival airports private AirportChoice choiceFrom = null; private FlightsPanel panelFlights = null; // panel containing maps and flight listings /** Initialize the applet, opens the connection with the database and add user interface items in the applet. */ synchronized public void init() { airplet = this; // static reference to this airplet setBackground(Color.white); // white background for this applet LayoutManager columnLayout = new ColumnLayout(5,5); // all panels go in a single column setLayout(columnLayout); showStatus("Connecting..."); // let user know we’re connecting to the database try { String driver = getParameter("driver"); // use sql driver specified in ‘driver’ parameter if(driver != null) Class.forName(driver).newInstance(); // register driver with DriverManager String url = getParameter("connection"); // get connection’s url connection = DriverManager.getConnection(url); // establish connection with server } catch(Exception sqlEx) { System.out.println("Connection failed because " + sqlEx + "\n"); } showStatus("Preparing..."); try { // create panel with name and preset choices // for departure’s airport Panel panelFrom = new Panel(); ImageCanvas imageFrom = new ImageCanvas("images/airFrom.gif"); panelFrom.add(imageFrom); nameFrom = new TextField(25); panelFrom.add(nameFrom); choiceFrom = new AirportChoice(); panelFrom.add(choiceFrom); // create panel with name and preset choices // for arrival’s airport Panel panelTo = new Panel(); ImageCanvas imageTo = new ImageCanvas("images/airTo.gif"); panelTo.add(imageTo); nameTo = new TextField(25); panelTo.add(nameTo); choiceTo = new AirportChoice(); panelTo.add(choiceTo); Panel panelButtons = new Panel(); // panel with search and roundtrip buttons Button button1 = new Button("Search"); panelButtons.add(button1); Button button2 = new Button("Roundtrip"); panelButtons.add(button2); panelFlights = new FlightsPanel(); // panel with maps and routes add(panelFrom); // add all panels to the applet add(panelTo); add(panelButtons); add(panelFlights); showStatus("Connect Software, 1996."); // here we are! } catch(SQLException sqlEx) { showStatus("Sorry, could not initialize, e-mail support@connectsw.com"); } } /** Responds to user selecting an airport in the choice menus or clicking search or roundtrip. */ public boolean action(Event iEvent,Object iArgument) { if(iEvent.target == choiceFrom) // user picked an origin from the choices { Airport air = choiceFrom.getSelectedAirport(); nameFrom.setText(air.getName()); // copy origin’s name to origin’s text field return true; } if(iEvent.target == choiceTo) // user picked a destination from the choices { Airport air = choiceTo.getSelectedAirport(); nameTo.setText(air.getName()); // copy destination’s name to destination’s text field } // search for flights from origin to destination if(iArgument.equals("Search") || iArgument.equals("Roundtrip")) { String airFrom = nameFrom.getText(); if(airFrom.length() < 1) airFrom = choiceFrom.getSelectedItem(); String airTo = nameTo.getText(); if(airTo.length() < 1) airTo = choiceTo.getSelectedItem(); searchFlights(airFrom,airTo,iArgument.equals("Roundtrip")); return true; } return super.action(iEvent,iArgument); // event was handled } private void searchFlights(String departingFrom,String arrivingTo, boolean roundTrip) { try { Airport airFrom = Airport.getAirport(departingFrom); // find out more about departing airport Airport airTo = Airport.getAirport(arrivingTo); // find out more about arriving airport // if both airports were found (and they are different) if(airFrom != null && airTo != null) { // show complete name and code for departure airport nameFrom.setText(airFrom.getName()); choiceFrom.select(airFrom.getCode()); // show complete name and code for arrival airport nameTo.setText(airTo.getName()); choiceTo.select(airTo.getCode()); if(roundTrip) // if user requested return trip { // show inverse route panelFlights.setAirports(airTo,airFrom); } // show normal route else panelFlights.setAirports(airFrom, airTo); layout(); } } catch(SQLException sqlEx) { panelFlights.setText(sqlEx.toString()); } } static Statement createStatement() throws SQLException { return connection.createStatement(); } private static Connection connection = null; // connection to the airline database /** Loads given image from the network, or file system, and returns it. */ static Image loadImage(String iName) { if(images == null) // if there’s no hash table for images yet { images = new Hashtable(); // create an empty hash table } // try to get image from the hash table (hash is image’s name) Image image = (Image) images.get(iName); if(image == null) // if this image hasn’t been loaded yet { try // catch all loading problems { // create url of image on Web server or // local file system URL url = new URL(airplet.getDocumentBase(), iName); image = airplet.getImage(url); // try to load image airplet.prepareImage(image,airplet); } catch(Exception e) { } if(image != null) // if image was loaded { // add it to the hash table so next time // we don’t have to load it images.put(iName,image); } } return image; // return the image } static private Hashtable images = null; // a hash table of loaded images // a static reference to this applet // (there’s only one instance of it running at any time) static private Airplet airplet = null; }
Airport.java holds the information regarding an airport. The constructor executes a query returning airport details such as its code, its name, its description, and its geographical coordinates in terms of x and y positions on different maps.
A hash table of airports is also created in Listing 11-8, which shows the source code of Airport.java.
Listing 11-8: Airport.java
// // Airport.java - this object holds information regarding an airport // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.sql.*; // import sql server access classes import java.awt.*; // java’s windowing toolkit and other ui classes import java.util.*; // utility classes /** Information regarding an airport. */ class Airport { public Airport(String iAirport) throws SQLException { iAirport = iAirport.trim(); // remove leading and trailing spaces // use normal statement to query the Airports table ResultSet r = null; Statement s = Airplet.createStatement(); if(iAirport.length() == 3) // if this is likely to be an airport code { r = s.executeQuery("select * from airports where code = ‘" + iAirport + "‘"); // move over to the first (and only) row in the result set if(r.next() == false) { r = null; // there are no entries with given airport code } } // search for airports whose name contain given airport // string (like %string%) if(r == null) { r = s.executeQuery("select * from airports where name like ‘%" + iAirport + "%’"); // move to first (and probably only) row in result set if(r.next() == false) { // case insensitive name, eg. [mM][iI][lL][aA][nN] // instead of Milan String nocase = ""; // scan all characters in the string for(int i = 0 ; i < iAirport.length() ; i++) { // extract character then convert to // lowercase and uppercase String single = iAirport.substring(i,i+1); String lower = single.toLowerCase(), upper = single.toUpperCase(); // if lowercase is different from uppercase // (that is, if this character is alpha) if(lower.equals(upper) == false) { // regular expression for both lower // or uppercase // (for example, [aA]) nocase += "[" + lower + upper + "]"; } else nocase += single; } r = s.executeQuery("select * from airports where name like ‘%" + nocase + "%’"); // if this one didn’t work either, there’s // no such airport if(r.next() == false) r = null; } } if(r != null) { code = r.getString("code"); // airport code, eg. ‘SFO’ name = r.getString("name"); // airport name, // eg. ‘San Francisco, CA’ description = r.getString("description"); // description // of this airport StringTokenizer sTokenizer = new StringTokenizer (r.getString("maps"),";"); // scan each token, maps entry looks something like // "california(45,60);usa(123,3);world(56,78)" while(sTokenizer.hasMoreTokens()) { // create an object containing information regarding // this airport on a single map MapInfo info = new MapInfo(sTokenizer.nextToken()); if(maps != null) // if there are other maps already { // add this map to the linked list of maps maps.append(info); } else maps = info; // this is the first map in the list // System.out.println(code + " map " + info); } } else throw new SQLException("Can’t find ‘" + iAirport + "‘ in the airports database."); } // airport code, name and description, eg. ‘SFO’, // ‘San Francisco, CA’, ‘International Airport, ...’ private String code, name, description; // linked list of maps available for this airport (and coordinates // on each map) in preferred order (eg. ‘california’, ‘usa’, ‘world’) private MapInfo maps; public String getCode() { return code; // return airport code } public String getName() { return name; } public MapInfo getMaps() { return maps; } /** Returns airport with the given name or code. */ static public Airport getAirport(String iName) { try { if(airports = null) // if there’s no hash table for airports { // create an empty hash table airports = new Hashtable(); } // try getting the airport from the hash table Airport airport = (Airport) airports.get(iName); if(airport = null) // if airport was not found { // create a new airport from that name // (will query the database) airport = new Airport(iName); // add airport to the hash table (by name) airports.put(airport.getName(),airport); // add also by code airports.put(airport.getCode(),airport); // } return airport; // return the airport } catch(SQLException sqlException) { return null; } // airport could // not be found } // hash table of airports (used to minimize database access) static private Hashtable airports = null; public String toString() { return "Airport[" + code + "," + name + "]"; // convert object to string } }
The AirportChoice.java class queries the database to build a list of airport codes such as SFO, LAX, and JFK. The list is used within the user interface to enable the user to choose the departure and arrival airports. Listing 11-9 shows the source code for AirPortChoice.java.
Listing 11-9: AirportChoice.java
// // AirportChoice.java - user interface widget showing a choice of airports // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.sql.*; // import jdbc and other sql libraries import java.awt.*; // java windowing toolkit /** A choice user interface widget showing a list of available airport codes. */ class AirportChoice extends Choice { /** * Initialize the choice user interface widget with a list of airports * available in the database. The method will query the airports table * of the database, listing all available airports by code. */ public AirportChoice() throws SQLException { // use sql to select all airport codes from the // airports table then add them to the widget // scan all the airports in the table Statement s = Airplet.createStatement(); for(ResultSet r = s.executeQuery("select code from airports order by code") ; r.next() ; ) { String name = r.getString(1); // name of this airport addItem(name); // add airport to the choices } s.close(); // close statement } /** Returns the Airport corresponding to the entry with the given index. */ public Airport getAirport(int index) { return Airport.getAirport(getItem(index)); // return Airport object } /** Returns the currently selected Airport. */ public Airport getSelectedAirport() { return getAirport(getSelectedIndex()); } }
The ColumnLayout.java class arranges a set of components in a single column. The source code for this class is in Listing 11-10.
Listing 11-10: ColumnLayout.java
// // ColumnLayout.java - layout that arranges all components in a column // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo. // package airplet; import java.awt.*; // import java windowing classes /** A layout that arranges all components in a single column. */ public class ColumnLayout implements LayoutManager // just another layout manager { public ColumnLayout() { hgap = vgap = 0; // no gap between components } public ColumnLayout(int hgap,int vgap) { this.hgap = hgap; this.vgap = vgap; // use this spacing // between components } private int hgap, vgap; // horizontal and vertical spacing between components /** Arrange components contained in iParent in a single column using their preferred size. */ public void layoutContainer(Container iParent) { Insets insets = iParent.insets(); // insets (borders around // the container) Dimension dimension = iParent.size(); // size of parent container dimension.width -= insets.left + insets.right; // net width of container for(int i = 0, v = vgap ; i < iParent.countComponents() ; i++) { // scan each component in the container Component component = iParent.getComponent(i); // get component’s preferred size and then reshape it Dimension size = component.preferredSize(); component.reshape(insets.left,v,dimension.width - insets.left - insets.right,size.height); component.repaint(); // redraw the component // update vertical origin for next component v += size.height + vgap; } } /** Returns the minimum layout size calculated using each component’s preferred size. */ public Dimension minimumLayoutSize(Container iParent) { Dimension dimension = new Dimension(0,0); for(int i = 0 ; i < iParent.countComponents() ; i++) // scan components { Component component = iParent.getComponent(i); // get i-th component’s size Dimension size = component.preferredSize(); dimension.width = Math.max(dimension.width,size.width); // update height including this component dimension.height += size.height + vgap; } Insets insets = iParent.insets(); // add insets (border) dimension.width += insets.left + insets.right + 2 * hgap; dimension.height += insets.top + insets.bottom + vgap; return dimension; } /** Preferred size is just like minimum size but can be as wide as the parent component. */ public Dimension preferredLayoutSize(Container iParent) { Dimension dimension = minimumLayoutSize(iParent); dimension.width = Math.max(iParent.size().width,dimension. width); return dimension; } public void addLayoutComponent(String iName,Component iComponent) { } public void removeLayoutComponent(Component iComponent) { } }
Flight.java contains a constructor that initializes the class members (attributes) using flight information. Flight.java extracts the flight number, departure and arrival, flight frequency, and plane identification. Listing 11-11 shows the source code for this class.
Listing 11-11: Flight.java
// // Flight.java - holds information regarding a flight // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.sql.*; // import jdbc and other sql libraries class Flight { /** * Initialize flight from the information contained in the current * row of this result set. The result set is a subset of rows from * the flights table in the airline database. This method will read * information on current row (it will not call next). * * @param iFlight is a result set whose current row is a flight */ public Flight(ResultSet iFlight) throws SQLException { code = iFlight.getString("code"); // get flight number from = iFlight.getString("from_city"); to = iFlight.getString("to_city"); departure = iFlight.getTime("departure"); arrival = iFlight.getTime("arrival"); // flight frequency (eg. which days this flight operates) frequency = iFlight.getString("frequency"); plane = iFlight.getString("plane"); // airplane used } String code, from, to; // the flight code/number and city of // departure/arrival, e.g., ‘TWA800’ Time departure, arrival; // departure and arrival time String frequency; // days when the flight is available, // e.g., 123 for Mon, Tue, Wed String plane; // airplane used, e.g., "Boeing 767" public String toString() { return "Flight[" + code + "," + from + " " + departure + "," + to + " " + arrival + "," + frequency + "," + plane + "]"; } }
The FlightsPanel.java class is a panel containing a graphical map. Which map is displayed depends on the departure and destination airport locations. This panel also displays the routes. Listing 11-12 contains the source code for this class.
Listing 11-12: FlightsPanel.java
// // FlightsPanel.java - a panel showing flight information and routes // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; // airplet package import java.sql.*; // import connect’s jdbc libraries import java.awt.*; // import java windowing library class FlightsPanel extends Panel { public FlightsPanel() { // column layout with 10 pixels between components LayoutManager layout = new ColumnLayout(0,10); setLayout(layout); // use this layout for the panel map = new MapCanvas(); add(map); // add a map to the panel // label that can display multiple lines of text (draw with subtle // good looking shadow) label = new MultilineLabel(Label.CENTER); setText("Welcome to jdbc airlines!\n \nPlease pick an origin and a destination\n" + "then click Search or RoundTrip."); add(label); // add label to panel } /** Converts a time object into a string in the form hh:mm am/pm */ String time2string(Time time) { int hour = time.getHours(); // get hours (0..23) and minutes (0..59) // format the string as hh:mm then append am or pm int minute = time.getMinutes(); return (hour % 12 < 10 ? "0" : "") + Integer.toString(hour % 12) + ":" + (minute < 10 ? "0" : "") + Integer.toString(minute) + (hour < 12 ? " AM" : " PM"); } void setAirports(Airport iFrom,Airport iTo) throws SQLException { String str = null; try { // show best map for these two airports // (and a route between them) map.setAirports(iFrom,iTo); // if the two airports are not the same if(iFrom.getCode().equals(iTo.getCode()) = false) { // create a vector containing all the // flights between the two airports FlightsVector flights = new FlightsVector(iFrom,iTo); int numFlights = flights.size(); // number of flights // found if(numFlights > 0) // if there are flights { str = "Flights from " + iFrom.getName() + " to " + iTo.getName() + "\n \n"; // scan flights between these two airports for(int i = 0 ; i < numFlights ; i++) { // retrieve i-th flight Flight flight = (Flight) flights.elementAt(i); str += flight.code + " leaves at " + time2string(flight.departure) + " arrives at " + time2string(flight.arrival) + " (frequency " + flight.frequency + ").\n"; } } else str = "There are no flights between " + iFrom.getName() + " and " + iTo.getName() + "."; } // if there are no flights or airports are the same, // show an error message else str = "Please pick two different airports, then retry."; } // some sql exception was raised, notify the user catch(SQLException sqlEx) { str = "Sorry, your request didn’t go through,\n" + "the server is probably down or busy,\nplease try again later.\n \n" + sqlEx; } setText(str); // show the string with the flights or the warning // we may need to redo this panel’s layout // (the label may have changed its size) layout(); } public void setText(String text) { label.setText(text); } private MapCanvas map = null; // map and route canvas private MultilineLabel label = null; // label with flights or error message }
FlightsVector.java is a vector containing all flights between the departure and arrival airports. A query is sent to the database server to get information about flights with the given airport codes for departure and arrival. Listing 11-13 shows the source code for this class.
Listing 11-13: FlightsVector.java
// // FlightsVector.java - a vector containing a bunch of flights // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.sql.*; // import jdbc and other sql libraries import java.util.*; // java utility classes class FlightsVector extends Vector // this is just a vector of Flight objects { /** * Initialize this vector with all flights between two given airports. * The method will select all rows in the flights table having the given * airport codes in the from_city and to_city fields. An entry in the vector * will then be created for each flight and each entry will be added to the * vector. * * @param iConnection connection to the database * @param iFrom the airport we’re leaving from * @param iTo the airport we’re arriving to */ public FlightsVector(Airport iFrom,Airport iTo) throws SQLException { // executes something like: select * from flights where // from_city = ‘SFO’ and to_city = ‘JFK’ String sql = "select * from flights where from_city = ‘" + iFrom.getCode() + "‘ and to_city = ‘" + iTo.getCode() + "‘ order by departure"; Statement s = Airplet.createStatement(); // create normal sql statement // scan all flights between given airports for(ResultSet r = s.executeQuery(sql) ; r.next() ; ) { // create a new flight from current row Flight flight = new Flight(r); addElement(flight); // add this flight to the vector } s.close(); // we don’t have to do this // (but it could help jdbc optimize access) } }
The ImageCanvas.java class is a canvas containing an image. An update method is provided to draw the image using double buffering, if possible. Listing 11-14 shows the source code for this class.
Listing 11-14: ImageCanvas.java
// // ImageCanvas.java - a canvas that shows an image // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.applet.*; import java.awt.*; // java windowing classes /** A canvas used to display an image. */ public class ImageCanvas extends Canvas // shows a canvas containing an image { /** Initialize canvas showing the image with the given name. */ public ImageCanvas(String name) { if(name != null && name.length() > 0) // if a name was specified { setImage(name); // load image } } protected Image image = null; // image shown by this canvas /** Display image with given name in the canvas. */ public void setImage(String iName) { Image newimage = Airplet.loadImage(iName); // load new image if(image != newimage) // if image changed { image = newimage; repaint(); // refresh the canvas } } /** Update the canvas using double buffering (if enough memory’s available). */ synchronized public void update(Graphics iGraphics) { Dimension d = size(); if(d.width < 1 || d.height < 1) return; // don’t update if empty Image buf = null; try // catch memory full and other problem { buf = createImage(d.width,d.height); // create temporary buffer } catch(Exception e) { } if(buf != null) // if buffer was created { // get buffer’s graphic context Graphics bufGr = buf.getGraphics(); bufGr.clearRect(0,0,d.width,d.height); // erase content of buffer paint(bufGr); // paint into the offscreen buffer // copy the offscreen buffer to the panel iGraphics.drawImage(buf,0,0,this); buf.flush(); // dispose buffer’s resources } // if there’s not enough memory for double buffering, // let the superclass update as usual else super.update(iGraphics); } /** Draw the image centered in the canvas. */ public void paint(Graphics iGraphics) { if(image != null) // if there is an image { Dimension d = size(); // calculate image’s origin d.width -= image.getWidth(this); // then draw the image centered in the canvas d.height -= image.getHeight(this); iGraphics.drawImage(image,d.width,d.height,this); } } /** Preferred size for this canvas is the size of the image that it is showing, if any. */ public Dimension preferredSize() { if(image != null) // if an image was selected, return its size { return new Dimension(image.getWidth(this),image. getHeight(this)); } // otherwise 1 pixel will do (0 would be too little, // ‘cause paint would never be called) return new Dimension(1,1); } }
MapCanvas.java contains the methods used to display the most appropriate map for the departure and arrival selections. A route is drawn between the two airports. Listing 11-15 shows the source code for this class.
Listing 11-15: MapCanvas.java
// // MapCanvas.java - a view that shows a map with airports and a route // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.awt.*; // import java windowing toolkit import java.io.*; // I/O streams, exceptions, etc. import java.applet.*; // applet class /** A canvas that shows a map and a flight’s route. */ class MapCanvas extends ImageCanvas // map class extends canvas (drawable view) { public MapCanvas() { // display world map until airports are selected super("images/world.gif"); // load origin and destination icons iconFrom = Airplet.loadImage("images/iconFrom.gif"); iconTo = Airplet.loadImage("images/iconTo.gif"); } private Airport airFrom = null; // arrival and departure airports private Airport airTo = null; private MapInfo mapFrom = null; // information regarding the airports on the map private MapInfo mapTo = null; private Image iconFrom = null; // icons for arrival and departure points on the map private Image iconTo = null; /** Draw a route going from x1,y1 to x2,y2 */ private void drawRoute(Graphics iGraphics,int x1,int y1,int x2,int y2) { int xp = x1; int yp = y1; double arc = Math.min(Math.abs(x1 - x2) * .20 + Math.abs(y1 - y2) * .20,30.0); // draw a slanted arc as 20 connected lines for(double p = .1 ; p <= 1.0 ; p += .1) { // calculate parametric position in the line // connecting origin with arrival int xc = (int) (x1 + (double) (x2 - x1) * p); int yc = (int) (y1 + (double) (y2 - y1) * p); double pslanted = p; // (p < .75) ? (p * .50 / .75) : (.50 + (p - .75) * .50 / .25); // add variable y value to form an arc yc -= (int) (Math.sin(Math.PI * pslanted) * arc); iGraphics.drawLine(xp,yp,xc,yc); // draw current segment xp = xc; // current position becomes previous position yp = yc; } } /** Draw the map of the region containing both airports and a route. */ public void paint(Graphics iGraphics) { super.paint(iGraphics); // draws the map if(mapFrom != null && mapTo != null) { Dimension d = size(); // size of this canvas // origin of the map in the canvas int w = image.getWidth(this), hofs = (d.width - w) / 2; int h = image.getHeight(this), vofs = (d.height - h) / 2; iGraphics.setColor(Color.lightGray); drawRoute(iGraphics,hofs + mapFrom.x + 1,vofs + mapFrom.y + 1,hofs + mapTo.x + 1,vofs + mapTo.y + 1); iGraphics.setColor(Color.black); drawRoute(iGraphics,hofs + mapFrom.x,vofs + mapFrom.y,hofs + mapTo.x,vofs + mapTo.y); int xFrom = hofs + mapFrom.x - iconFrom.getWidth(this) / 2; int yFrom = vofs + mapFrom.y - iconFrom.getHeight(this) / 2; // calculate origin and destination icon’s position int xTo = hofs + mapTo.x - iconTo.getWidth(this) / 2; int yTo = vofs + mapTo.y - iconTo.getHeight(this) / 2; // draw origin and destination icons iGraphics.drawImage(iconFrom,xFrom,yFrom,this); iGraphics.drawImage(iconTo,xTo,yTo,this); } } /** Sets departure and arrival airports, selecting and displaying the most appropriate map. */ void setAirports(Airport iFrom,Airport iTo) { String name = null; airFrom = iFrom; airTo = iTo; // set departure and arrival airports if(airFrom != null && airTo != null) // if departure and arrival airports were specified { for(mapFrom = airFrom.getMaps() ; name == null && mapFrom != null ; ) { for(mapTo = airTo.getMaps() ; name == null && mapTo != null ; ) { if(mapFrom.name.equals(mapTo.name)) { name = mapFrom.name; } else mapTo = mapTo.next; } if(name == null) mapFrom = mapFrom.next; } } // use world’s map if there’s no better one name = "images/" + (name != null ? name : "world") + ".gif"; setImage(name); // display new image } public Dimension preferredSize() { return new Dimension(500,300); // size of the maps is fixed } }
The MapInfo.java class extracts the x,y coordinates from the flight’s string for a particular graphic map. Listing 11-16 shows its source code.
Listing 11-16: MapInfo.java
// // MapInfo.java - informations regarding airport’s position on a map // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; // airplet’s package import java.util.*; // utility classes /** Information about an airport’s position on a map. */ class MapInfo { /** Initialize from a ‘map(x,y)’ string. */ MapInfo(String map) { // name is encoded as name(x,y) so use ( and comma as separators StringTokenizer sTokenizer = new StringTokenizer(map,"(,)"); // name of this map (eg. ‘usa’, ‘europe’, ‘world’) name = sTokenizer.nextToken().toLowerCase(); // coordinate of the airport in this map x = Integer.parseInt(sTokenizer.nextToken()); y = Integer.parseInt(sTokenizer.nextToken()); } String name; // name of the map int x,y; // coordinates of the airport on this map MapInfo next = null; // next map (this is a linked list) void append(MapInfo item) { // appends item at the end of the linked list if(next != null) next.append(item); else next = item; } public String toString() { return "MapInfo[" + name + "," + x + "," + y + "]"; // returns MapInfo[name,x,y] } }
MultilineLabel.java is simply a label that can display multiple lines of text. It also provides text shadow for the drawn string. Listing 11-17 shows the source code for this class.
Listing 11-17: MultilineLabel.java
// // MultilineLabel.java - a label that can draw several lines of text // // Copyright (C) 1996 by Connect Software. All rights reserved. // // Written by Gionata Mettifogo, Peter Ham. // package airplet; import java.awt.*; import java.util.*; public class MultilineLabel extends java.awt.Canvas { public MultilineLabel(int alignment) { align = alignment; } private String text; private int align; // text and alignment (see constants in Label) public void setText(String text) { this.text = text; } /** Draw the multiline label aligned as specified during object’s construction. */ public void paint(Graphics iGraphics) { // get information on the font’s sizes FontMetrics fm = iGraphics.getFontMetrics(); // separate different lines StringTokenizer tokens = new StringTokenizer(text,"\n"); // line height and label’s width int w = size().width, h = fm.getHeight(); // scan all lines in the label for(int y = h ; tokens.hasMoreTokens() ; y += h) { String line = tokens.nextToken(); // retrieve line int x = 0; // if line is centered or right aligned if(align == Label.CENTER || align == Label.RIGHT) { // calculate spacing on left side x = w - fm.stringWidth(line); if(align == Label.CENTER) x /= 2; } shadowString(iGraphics,line,x,y); // draw the line } } public Dimension preferredSize() { // get information on the font’s sizes FontMetrics fm = getGraphics().getFontMetrics(); // separate different lines StringTokenizer tokens = new StringTokenizer(text,"\n"); Dimension dimension = new Dimension(0,fm.getHeight() * tokens.countTokens() + fm.getMaxDescent() + 1); while(tokens.hasMoreTokens()) // scan lines { String line = tokens.nextToken(); // retrieve line // width is the length of the longest line dimension.width = Math.max(fm.stringWidth(line),dimension.width); } return dimension; } /** * Draws the given string at the given position using a * subtle 1 pixel gray shadow. Light comes from the upper * left corner (where the Apple used to be). */ public void shadowString(Graphics iGraphics,String iString,int x,int y) { Color color = iGraphics.getColor(); iGraphics.setColor(Color.lightGray); iGraphics.drawString(iString,x+1,y+1); iGraphics.setColor(color); iGraphics.drawString(iString,x,y); } }