home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 1999 October / pcp156b.iso / handson / files / copyjava.exe / Panorama.java < prev    next >
Encoding:
Java Source  |  1999-07-02  |  16.3 KB  |  659 lines

  1. /*
  2.  * @(#)Panorama.java    1.1 98/04/12
  3.  * 
  4.  * Copyright (c) 1998, David Griffiths. All Rights Reserved.
  5.  * 
  6.  * This software is the proprietary information of David Griffiths.
  7.  * This source code may not be published or redistributed without the 
  8.  * express permission of the author. 
  9.  * 
  10.  * THE AUTHOR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 
  11.  * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
  12.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  13.  * PURPOSE, OR NON-INFRINGEMENT. THE AUTHOR SHALL NOT BE LIABLE FOR ANY DAMAGES
  14.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  15.  * THIS SOFTWARE OR ITS DERIVATIVES.
  16.  * 
  17.  */
  18.  
  19. import java.awt.*;
  20. import java.applet.Applet;
  21. import java.io.*;
  22. import java.net.*;
  23.  
  24. public synchronized class Panorama extends Applet implements Runnable {
  25.     private Graphics screenG;
  26.     private Image screenImage, theImage;
  27.     private int dx[][], dy[][], dw[], cx, cy, w1, h1, w2, h2, mx, my, mxOrig, myOrig;
  28.     private int moveX, moveY;
  29.     private int renderRate, degrees, sw, sh, needDetail, timeSample, rotateAmount;
  30.     private boolean interlace, jdk10, mouseMoving, autoRotate, autoRotateOn;
  31.     private boolean imageLoading = false;
  32.     private int hyperLink = -1;
  33.     private Color tipColour = new Color(255, 255, 225);
  34.     private FontMetrics screenFM;
  35.     private DataInputStream mapIn;
  36.     private final static int HYPER_COUNT = 10;
  37.     private Polygon hyperArea[] = new Polygon[HYPER_COUNT];
  38.     private String tip[] = new String[HYPER_COUNT];
  39.     private String href[] = new String[HYPER_COUNT];
  40.     private int polyCount = 0;
  41.     private int pointx[] = new int[255], pointy[] = new int[255], coordNo;
  42.     private Thread thrMain;
  43.     transient private long totTime = 0L;
  44.     transient private int totPass = 0;
  45.     transient private Graphics gMain;
  46.     private int rotateSpeed = 3;
  47.  
  48.     /**
  49.      * Constructors.
  50.      */
  51.     public Panorama() {
  52.         super();
  53.  
  54.         dx = new int[600][600];
  55.         dy = new int[600][600];
  56.         dw = new int[600];
  57.  
  58.         if (!System.getProperty("java.version").substring(0,3).equals("1.0"))
  59.             try {
  60.                 Class paras[] = {
  61.                     Image.class, 
  62.                     Integer.TYPE, 
  63.                     Integer.TYPE, 
  64.                     Integer.TYPE, 
  65.                     Integer.TYPE, 
  66.                     Integer.TYPE, 
  67.                     Integer.TYPE, 
  68.                     Integer.TYPE, 
  69.                     Integer.TYPE, 
  70.                     java.awt.image.ImageObserver.class
  71.                 };
  72.                 jdk10 = false;
  73.                 java.awt.Graphics.class.getMethod("drawImage", 
  74.                     paras);
  75.             }
  76.             catch (Exception e) {
  77.                 jdk10 = true;
  78.             }
  79.         else
  80.             jdk10 = true;
  81. // jdk10 = true;
  82.     }
  83.  
  84.     public Panorama(Image theImage) {
  85.         this(360, theImage);
  86.     }
  87.  
  88.     public Panorama(int degrees, Image theImage) {
  89.         this(360, theImage, 300, 200);
  90.     }
  91.  
  92.     public Panorama(int degrees, Image theImage, int sw, int sh) {
  93.         this();
  94.         this.degrees = degrees;
  95.         this.theImage = theImage;
  96.         this.sw = sw;
  97.         this.sh = sh;
  98.  
  99.         resize(this.sw, this.sh);
  100.     }
  101.  
  102.     /**
  103.      * Runnable section.
  104.      *
  105.      * The following methods are here to implement the Runnable interface.
  106.      */
  107.     public void start() {
  108.         if (thrMain == null) {
  109.             thrMain = new Thread(this);
  110.             thrMain.start();
  111.         }
  112.     }
  113.  
  114.     public void stop() {
  115.         if (thrMain != null) {
  116.             thrMain.stop();
  117.             thrMain = null;
  118.         }
  119.     }
  120.  
  121.     public void run() {
  122.         gMain = getGraphics();
  123.         while (true) {
  124.             if ((autoRotateOn) && (!mouseMoving)) {
  125.                 needDetail = renderRate;
  126. //                moveX = 10;
  127.                 moveX = rotateSpeed;
  128.             }
  129.             if (!alreadyPainting)
  130.                 repaint();
  131.             try {
  132.                 Thread.sleep(50);
  133.             }
  134.             catch (InterruptedException e2) {
  135.                 stop();
  136.             }
  137.         }
  138.     }
  139.  
  140.     /**
  141.      * preferredSize() method used for when the class is a component.
  142.      */
  143.     public Dimension preferredSize() {
  144.         return new Dimension(300, 200);
  145.     }
  146.  
  147.     public void setAutoRotate(int speed) {
  148.         autoRotate = !(speed == 0);
  149.         autoRotateOn = autoRotate;
  150.         rotateSpeed = speed;
  151.     }
  152.  
  153.     public void init() {
  154.         String string, mapName;
  155.  
  156.         string = getParameter("autorotate");
  157.         if (string != null)
  158. //            setAutoRotate(string.toUpperCase().equals("ON"));
  159.             setAutoRotate(Integer.parseInt(string));
  160.         else
  161. //            setAutoRotate(false);
  162.             setAutoRotate(0);
  163.         string = getParameter("degrees");
  164.         if (string != null)
  165.             degrees = Integer.parseInt(string);
  166.         else
  167.             degrees = 360;
  168.         theImage = getImage(getDocumentBase(), getParameter("image"));
  169.         sw = size().width;
  170.         sh = size().height;
  171.         mouseMoving = false;
  172.         mapName = getParameter("map");
  173.         if (mapName != null)
  174.             openImageMap(mapName);
  175.     }
  176.  
  177.     synchronized private void createScreenImage() {
  178.         if (screenImage != null)
  179.             return;
  180.         MediaTracker mediaTracker = new MediaTracker(this);
  181.         mediaTracker.addImage(this.theImage, 0);
  182.         try {
  183.             mediaTracker.waitForID(0);
  184.         }
  185.         catch (Exception e) {}
  186.         w1 = sw;
  187.         w2 = theImage.getWidth(this);
  188.         h1 = sh;
  189.         h2 = theImage.getHeight(this);
  190.         screenImage = createImage(w2 + sw + (w2 / 4), h2);
  191.         screenG = screenImage.getGraphics();
  192.         screenG.drawImage(theImage, sw, 0, this);
  193.         screenG.drawImage(theImage, sw + w2, 0, this);
  194.         screenG.setFont(new Font("Helvetica", Font.PLAIN, 11));
  195.         screenFM = screenG.getFontMetrics();
  196.         initialise();
  197.     }
  198.     
  199.     private void initialise() {
  200.         int k = (int)((double)w2 / ((double)(degrees / 180) * 3.1415926));
  201.         int oldyd;
  202.  
  203.         resetView();
  204.         if (w1 > dx.length)
  205.             w1 = dx.length;
  206.         if (h2 > dy.length)
  207.             h2 = dy.length;
  208.         for (int i = 0; i < w1; i++) {
  209.             for (int j = 0; j < h2; j++) {
  210.                 double d1 = i - w1 / 2;
  211.                 double d2 = h2 / 2 - j;
  212.                 double d3 = (double)k * Math.atan(d1 / k);
  213.                 double d5 = d1;
  214.                 d5 *= d5;
  215.                 d5 += k * k;
  216.                 d5 = Math.sqrt(d5);
  217.                 double d4 = d2 * k / d5;
  218.                 dx[i][j] = (int)d3;
  219.                 dy[i][j] = (int)d4;
  220.             }
  221.         }
  222.         oldyd = 0;
  223.         for (int i = w1; --i >= 0;) {
  224.             if (dy[i][0] == oldyd) {
  225.                 dw[i] = dw[i + 1] + 1;
  226.             }
  227.             else {
  228.                 oldyd = dy[i][0];
  229.                 dw[i] = 1;
  230.             }
  231.         }
  232.     
  233.         renderRate = 1;
  234.         interlace = true;
  235.         needDetail = renderRate;
  236.     }
  237.  
  238.     public void update(Graphics g) {
  239.         paint(g);
  240.     }
  241.  
  242.     boolean alreadyPainting = false;
  243.  
  244.     public void paint(Graphics g) {
  245.         if (alreadyPainting)
  246.             return;
  247.         alreadyPainting = true;
  248.         if (screenImage == null) {
  249.             g.setFont(new Font("Helvetica", 20, Font.PLAIN));
  250.             String loadCaption = "Panorama Loading...";
  251.             FontMetrics fm = g.getFontMetrics();
  252.             int tw = fm.stringWidth(loadCaption);
  253.             int th = fm.getHeight();
  254.             g.setColor(Color.lightGray);
  255.             g.fillRect(0, 0, sw, sh);
  256.             g.setColor(Color.white);
  257.             g.drawString(loadCaption, (sw - tw) / 2, (sh + th) / 2);
  258.             g.setColor(Color.black);
  259.             g.drawString(loadCaption, ((sw - tw) / 2) + 2, ((sh + th) / 2) + 2);
  260.             g.setColor(Color.lightGray);
  261.             g.drawString(loadCaption, ((sw - tw) / 2) + 1, ((sh + th) / 2) + 1);
  262.             if (!imageLoading) {
  263.                 imageLoading = true;
  264.                 createScreenImage();
  265.                 repaint();
  266.             }
  267.         }
  268.         else {
  269.             if (jdk10) {
  270.                 oldPaint(g);
  271.             }
  272.             else {
  273.                 renderImage(g, 1, 0, interlace);
  274.             }
  275.             if (hyperLink != -1) {
  276.                 drawTip(tip[hyperLink]);
  277.             }
  278.             g.drawImage(screenImage, 0, 0, this);
  279.         }
  280.         if ((moveX != 0) && (w1 > 0))
  281.             moveHoriz(moveX * w2 / (w1 << 4));
  282.         if ((moveY != 0) && (w1 > 0))
  283.             moveVert(moveY * w2 / (w1 << 4));
  284.         if ((moveX != 0) || (moveY != 0)) {
  285.             needDetail = renderRate;
  286.         }
  287.         alreadyPainting = false;
  288.     }
  289.  
  290.     /**
  291.      * Methods for JDK1.0 (or for browsers which don't use full
  292.      * full JDK1.1).
  293.      */
  294.     public void oldPaint(Graphics g) {
  295.         long startTime = 0L;
  296.         long stopTime = 0L;
  297.         long minTime = 10000L;
  298.         startTime = System.currentTimeMillis();
  299.         oldRenderImage(g, renderRate, needDetail, interlace);
  300.         stopTime = System.currentTimeMillis();
  301.         if (minTime > (stopTime - startTime))
  302.             minTime = (stopTime - startTime);
  303.         if ((renderRate < 8) && (minTime > 100L) && (stopTime != startTime)) {
  304.             renderRate++;
  305.         }
  306. //        rotateAmount = 3 * (int)minTime / 50;
  307.         rotateAmount = rotateSpeed * (int)minTime / 50;
  308.     }
  309.  
  310.     private void oldRenderImage(Graphics g, int renderRate, int needDetail, boolean interlace) {
  311.         int yd = renderRate;
  312.         int xd = renderRate;
  313.         int xinc = 1;
  314.  
  315.         yd = needDetail * h2 / (h2 - h1);
  316.         for (int x1 = 0; x1 < w1; x1 = x1 + xinc) {
  317.             if (dw[x1] > needDetail)
  318.                 xinc = dw[x1];
  319.             else
  320.                 xinc = needDetail;
  321.             for (int y1 = 0; y1 < h1; y1 += yd) {
  322.                 int xoff = cx + dx[x1][cy + y1];
  323.                 int yoff = h2 / 2 - dy[x1][cy + y1];
  324.                 if (xoff >= w2) {
  325.                     xoff = xoff - w2;
  326.                 }
  327.                 else if (xoff < 0) {
  328.                     xoff = xoff + w2;
  329.                 }
  330.                 if ((x1 + xinc) < sw)
  331.                     screenG.copyArea(xoff + sw, yoff, 
  332.                         xinc, yd,
  333.                         x1 - xoff - sw, y1 - yoff);
  334.                 else
  335.                     screenG.copyArea(xoff + sw, yoff, 
  336.                         sw - x1, yd,
  337.                         x1 - xoff - sw, y1 - yoff);
  338.             }
  339.         }
  340.     }
  341.  
  342.     /**
  343.      * renderImage used by JDK1.1 browsers.
  344.      */
  345.     public void renderImage(Graphics g, int i1, int j1, boolean interlace) {
  346.         int x1;
  347.         int xd;
  348.         int yd;
  349.         xd = 1;
  350.         yd = 50;
  351.         for (x1 = j1; x1 < w1; x1 += dw[x1]) {
  352.             int xoff = cx + dx[x1][cy + h1];
  353.             int yoff = (h2 / 2) - dy[x1][cy + h1];
  354.             if (xoff >= w2) {
  355.                 xoff = xoff - w2;
  356.             }
  357.             else if (xoff < 0) {
  358.                 xoff = xoff + w2;
  359.             }
  360.             screenG.drawImage(screenImage, x1, 0, 
  361.                 x1 + dw[x1], sh, 
  362.                 xoff + sw, h2 / 2 - dy[x1][cy], 
  363.                 xoff + dw[x1] + sw, yoff,
  364.                 this);
  365.         }
  366.     }
  367.  
  368. //////////////////////////////////////////
  369. //
  370. // INTERACTIVITY SECTION
  371. //
  372. //////////////////////////////////////////
  373.     public boolean mouseDown(Event event, int i, int j) {
  374.         mouseMoving = true;
  375.         mxOrig = mx = i;
  376.         myOrig = my = j;
  377.         moveX = moveY = 0;
  378.         checkCursor(i, j);
  379.         return true;
  380.     }
  381.  
  382.     public boolean mouseUp(Event event, int i, int j) {
  383.         if ((mxOrig == i) && (myOrig == j) && (autoRotate))
  384.             autoRotateOn = !autoRotateOn;
  385.         mouseMoving = false;
  386.         needDetail = 1;
  387.         moveX = moveY = 0;
  388.         checkCursor(i, j);
  389.         if ((mxOrig == i) && (myOrig == j) && (hyperLink != -1))
  390.             openDoc(href[hyperLink]);
  391.         return false; // Return false because we want the message passed up to the parent.
  392.     }
  393.  
  394.     private void openDoc(String s) {
  395.         try {
  396.             URL uRL = new URL(getDocumentBase(), s);
  397.             getAppletContext().showDocument(uRL, "_self");
  398.         }
  399.         catch (MalformedURLException e) {
  400.             getAppletContext().showStatus("Bad URL: " + s);
  401.         }
  402.     }
  403.  
  404.     public boolean mouseDrag(Event event, int i, int j) {
  405.         if (i != mx)
  406.             moveX += (i - mx);
  407.         if (j != my)
  408.             moveY += (j - my);
  409.         mx = i;
  410.         my = j;
  411.         return true;
  412.     }
  413.  
  414.     public boolean mouseMove(Event event, int i, int j) {
  415.         checkCursor(i, j);
  416.         return true;
  417.     }
  418.  
  419. //////////////////////////////////////////
  420. //
  421. // MOVEMENT SECTION
  422. //
  423. //////////////////////////////////////////
  424.     public void resetView() {
  425.         cx = w2 / 2;
  426.         cy = h2 / 2 - h1 / 2;
  427.     }
  428.  
  429.     private void moveVert(int i) {
  430.         if ((cy + i) >= 0 && (cy + i) < (h2 - h1))
  431.             cy = cy + i;
  432.     }
  433.  
  434.     private void moveHoriz(int i) {
  435.         cx += i;
  436.         if (cx < 0)
  437.             cx = cx + w2;
  438.         else if (cx >= w2)
  439.             cx = cx - w2;
  440.     }
  441.  
  442.     public int getHorizontalPos() {
  443.         return cx;
  444.     }
  445.  
  446. //////////////////////////////////////////
  447. //
  448. // HYPERGRAPHICS SECTION
  449. //
  450. //////////////////////////////////////////
  451.     private void openImageMap(String mapName) {
  452.         String line = "", map = "", coords="";
  453.         boolean inMap = false;
  454.         int pos1, pos2;
  455.  
  456.         try {
  457.             InputStream In = getDocumentBase().openStream();
  458.             mapIn = new DataInputStream(new BufferedInputStream(In));
  459.             line = mapIn.readLine();
  460.             while (line != null) {
  461.                 if (line.toUpperCase().indexOf("<MAP") != -1) {
  462.                     if (line.toUpperCase().indexOf("NAME=\"" 
  463.                         + mapName.toUpperCase() + "\"") != -1) {
  464.                         map = line;
  465.                         inMap = true;
  466.                     }
  467.                 }
  468.                 else if (inMap) {
  469.                     map = map + line;
  470.                 }
  471.                 if (line.indexOf("</MAP") != -1) {
  472.                     inMap = false;
  473.                 }
  474.                 line = mapIn.readLine();
  475.             }
  476.         }
  477.         catch (IOException e) {
  478.             System.out.println("IO Error: " + e.getMessage());
  479.         }
  480.         if (map.length() > 0) {
  481.             pos1 = map.toUpperCase().indexOf("<AREA") + "<AREA".length();
  482.             while (pos1 >= "<AREA".length()) {
  483.                 pos2 = map.indexOf(">", pos1);
  484.                 coords = map.substring(pos1, pos2);
  485.                 if (pos2 > pos1) {
  486.                     createArea(coords, polyCount++);
  487.                 }
  488.                 map = map.substring(pos1);
  489.                 pos1 = map.toUpperCase().indexOf("<AREA") + "<AREA".length();
  490.             }
  491.         }
  492.     }
  493.  
  494.     private void createArea(String area, int hyperNo) {
  495.         Polygon poly = null;
  496.         String coords = "", shape = "", alt = "", href = "";
  497.         if (area.length() > 0) {
  498.             coords = getAttr(area, "COORDS");
  499.             shape = getAttr(area, "SHAPE").toUpperCase();
  500.             alt = getAttr(area, "ALT");
  501.             href = getAttr(area, "HREF");
  502.             if (shape.equals("RECT")) {
  503.                 poly = aRect(coords);
  504.             }
  505.             else if (shape.equals("POLYGON")) {
  506.                 poly = aPolygon(coords);
  507.             }
  508.             else if (shape.equals("CIRCLE")) {
  509.                 poly = aCircle(coords);
  510.             }
  511.         }
  512.         hyperArea[hyperNo] = poly;
  513.         tip[hyperNo] = alt;
  514.         this.href[hyperNo] = href;
  515.     }
  516.  
  517.     private String getAttr(String s, String attr) {
  518.         String getAttr;
  519.         int pos1, pos2;
  520.  
  521.         pos1 = s.toUpperCase().indexOf(attr.toUpperCase() + "=\"")
  522.                 + (attr + "=\"").length();
  523.         getAttr = "";
  524.         if (pos1 >= (attr + "=\"").length()) {
  525.             pos2 = s.indexOf("\"", pos1);
  526.             getAttr = s.substring(pos1, pos2);
  527.         }
  528.         return getAttr;
  529.     }
  530.  
  531.     private Polygon aRect(String coords) {
  532.         int px[] = new int[4], py[] = new int[4];
  533.         getPoints(coords);
  534.         px[0] = pointx[0]; px[1] = pointx[1]; px[2] = px[1]; px[3] = px[0];
  535.         py[0] = pointy[0]; py[1] = pointy[0]; py[2] = pointy[1]; py[3] = py[2];
  536.         return new Polygon(px, py, 4);
  537.     }
  538.  
  539.     private Polygon aCircle(String coords) {
  540.         String temp = coords + ",0"; // Dummy value so we can get an even # of coords
  541.         int x, y, radius;
  542.         int px[] = new int[8], py[] = new int[8];
  543.         getPoints(temp);
  544.         x = pointx[0]; y = pointy[0]; radius = pointx[1];
  545.         for (int i = 0; i < px.length; i++) {
  546.             px[i] = x + (int)(radius * Math.cos(2 * 3.1415926 * (double)i / (double)px.length));
  547.             py[i] = y + (int)(radius * Math.sin(2 * 3.1415926 * (double)i / (double)px.length));
  548.         }
  549.         return new Polygon(px, py, px.length);
  550.     }
  551.  
  552.     private Polygon aPolygon(String coords) {
  553.         String temp;
  554.         getPoints(coords);
  555.         return new Polygon(pointx, pointy, coordNo);
  556.     }
  557.  
  558.     private void getPoints(String coords) {
  559.         String temp = coords + ",";
  560.         int pos1 = temp.indexOf(',');
  561.         coordNo = 0;
  562.         while (pos1 != -1) {
  563.             pointx[coordNo] = Integer.parseInt(temp.substring(0, pos1));
  564.             temp = temp.substring(pos1 + 1).trim();
  565.             pos1 = temp.indexOf(',');
  566.             pointy[coordNo++] = Integer.parseInt(temp.substring(0, pos1));
  567.             temp = temp.substring(pos1 + 1).trim();
  568.             pos1 = temp.indexOf(',');
  569.         }
  570.     }
  571.  
  572.     private void checkCursor(int i, int j) {
  573.         if (mouseMoving) {
  574.             if (!jdk10)
  575.                 setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
  576.             hyperLink = -1;
  577.             return;
  578.         }
  579.  
  580.         int xoff = cx + dx[i][cy + j];
  581.         int yoff = h2 / 2 - dy[i][cy + j];
  582.         if (xoff >= w2) {
  583.             xoff = xoff - w2;
  584.         }
  585.         else if (xoff < 0) {
  586.             xoff = xoff + w2;
  587.         }
  588.         Point p = new Point(xoff, yoff);
  589.         for (int n = 0; n < polyCount; n++) {
  590.             if (hyperArea[n].inside(p.x, p.y)) {
  591.                 if (!jdk10)
  592.                     setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
  593.                 hyperLink = n;
  594.                 repaint();
  595.                 return;
  596.             }
  597.         }
  598.  
  599.         if (!jdk10)
  600.             setCursor(Cursor.getDefaultCursor());
  601.         if (hyperLink != -1)
  602.             repaint();
  603.         hyperLink = -1;
  604.     }
  605.  
  606. //////////////////////////////////////////
  607. //
  608. // TIP SECTION
  609. //
  610. //////////////////////////////////////////
  611.  
  612.     private void drawTip(String msg) {
  613.         int x, y, w, h, a, d, tw, bx, by, bw, bh;
  614.         int border = 1;
  615.         int maxWidth = 2 * sw / 3;
  616.         int slice;
  617.         String temp, disp, rest;
  618.         int cutTo;
  619.  
  620.         a = screenFM.getAscent();
  621.         d = screenFM.getHeight() - screenFM.getAscent();
  622.         h = a + d + (border << 1);
  623.         bx = bw = 0;
  624.         bh = d;
  625.         by = sh + 1;
  626.         x = 20;
  627.         y = 20;
  628.         disp = msg;
  629.         rest = "";
  630.         while (!disp.equals("")) {
  631.             tw = screenFM.stringWidth(disp);
  632.             cutTo = disp.length();
  633.             rest = "";
  634.             if (tw > maxWidth) {
  635.                 slice = disp.length() * maxWidth / tw;
  636.                 temp = disp.substring(0, slice);
  637.                 cutTo = temp.lastIndexOf(' ');
  638.                 rest = disp.substring(cutTo + 1, disp.length());
  639.                 disp = disp.substring(0, cutTo + 1);
  640.             }
  641.  
  642.             w = screenFM.stringWidth(disp) + (d << 1) + (border << 1);
  643.             if (w > bw)
  644.                 bw = w;
  645.             bh = bh + d + a;
  646.             bx = x;
  647.             if (y < by)
  648.                 by = y;
  649.             screenG.setColor(tipColour);
  650.             screenG.fillRect(x, y, bw, h);
  651.             screenG.setColor(Color.black);
  652.             screenG.drawString(disp, x + d + border, y + a + border);
  653.             y = y + d + a; 
  654.             disp = rest;
  655.         }
  656.         screenG.drawRect(bx, by, bw, bh);
  657.     }
  658. }
  659.