/*****************************************************************************
* jmenu                                                                      *
* by Matthew L Wilson mlavwilson@hotmail.com, mewilson@bluemoon.net          * 
* http://www.bluemoon.net/~mewilson                                          *
* COPYRIGHT NOTICE                                                           *
* Copyright 1999 Matthew L. Wilson  All Rights Reserved.                     *
* Jmenu may be used and modified free of charge by anyone so long            * 
* this copyright notice and the comments above remain intact.  By using this *
* code you agree to indemnify Matthew L. Wilson from any liability that      *
* might arise from it's use.                                                 *
*                                                                            *
* Selling the code for this program without prior written consent is         *
* expressly forbidden.  In other words, please ask first before you try and  *
* make money off of my program.                                              *
*                                                                            *
* Obtain permission before redistributing this software over the Internet or *
* in any other medium.	In all cases copyright and header must remain intact.*
******************************************************************************/

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.*;
import java.util.StringTokenizer;



public class jmenu extends Applet implements Runnable {

//PARAMETERS: name          param name

  Color TEXT_HILIGHT,   // "color-highlight"
        TEXT_NORMAL,    // "color-text"
        BACK_GROUND,    // "color-background"
        CONNECTORS,     // "color-connectors"
        UNDER_LINE;     // "color-under-line"
  Font  NAME_FONT;      // "font-name-size"
  int   SPACING = 16;   // "spacing"

  int Num_Entries=500,
      BufferX,  BufferY, // Used for scrolling up and down. 
      MaxLevel, oldItem, // Last entry the mouse was over.
      AppletY,  AppletX; // Size of th eapplet.
      
  String   string;       // Used for parameters.

  Graphics b;
  Image    buffer = null;

  Image[]  Icon;         // All the icons

  entry    Item[] = new entry[ Num_Entries ]; //Stores all entries.
  int      I[]    = new int[ Num_Entries ];   //Stores entries visable to the user.

  URL url;               // The URl to open a page.
  Thread thread;         // Monitors the icons while they are downloaded
  MediaTracker tracker;  // Monitors the icons while they are downloaded

  public void start()
  { thread = new Thread(this);
    thread.start();
  }

  public void stop()
  { thread.stop();
    thread = null;
  }

// Load all the icons, then call createTree().
  public void run()
  {
    try {
      tracker.waitForID(0);
    }
    catch (InterruptedException e) {return;}
    createTree();
  }

/*1) Init the Media Tracker
  2) Init the double buffer
  3) Get all parameters
  4) Start the download of all the icons
  5) Call addFolder(-1,"") which is a recursive function
  6) Call initConnectors()
  7) Check to see if there is a "start-page"
  8) Call createTree(), which draws the tree on the buffer */

  public void init()
  {
    tracker = new MediaTracker(this);
    AppletX = size().width;
    AppletY = size().height;
    try {
      buffer = createImage(AppletX,1000); b = buffer.getGraphics();
    }catch (Exception e) {  getAppletContext().showDocument( getDocumentBase(),"_self" );  }

    TEXT_NORMAL  = setParmColor( getParameter("color-text"      ), Color.black);
    BACK_GROUND  = setParmColor( getParameter("color-background"), Color.white);
    CONNECTORS   = setParmColor( getParameter("color-connectors"), BACK_GROUND);
    TEXT_HILIGHT = setParmColor( getParameter("color-highlight" ), TEXT_NORMAL);
    UNDER_LINE   = setParmColor( getParameter("color-under-line"), BACK_GROUND);
    SPACING      = (getParameter("spacing") == null ? SPACING : Integer.parseInt(getParameter("spacing")));
    
    String font  = getParameter("font-name-size");
    if (font != null)
    { StringTokenizer to = new StringTokenizer(font, ",");
      String name = to.nextToken();
      int    size = Integer.parseInt(to.nextToken());
      NAME_FONT  = new Font(name, 0, size);
      b.setFont(NAME_FONT);
    }

    for (Num_Entries = 0; ;Num_Entries++) if (null == getParameter("icon"+Num_Entries) ) break;
    Icon = new Image[Num_Entries];

    Num_Entries = 0;
    while(1==1)
    { string = getParameter("icon"+Num_Entries);
      if (string == null) break;
      Icon[Num_Entries] = getImage(getDocumentBase(),string);
      showStatus("Loading icons");
      tracker.addImage(Icon[Num_Entries], 0);
      Num_Entries++;
    }
    Num_Entries = 0;

    addFolder(-1,"");    initConnectors();
    for (int i = 1; i<500; i++) I[i] = -1; I[0] = 0; 

    String Starter = getParameter("start-page");

    for (int i = Num_Entries-1; i>0; i--) if (( Starter.equals("?"+Item[i].Location) )&&(Item[i].Location != ""))
    { 
      int folder = 0;
      showDocument(Item[i].Location, Item[i].Target);
      folder = Item[i].Folder-1;
      for (int j = i; j>=0; j--) if ( (Item[j].Folder == folder) && (Item[j].Icon < 2) )
      {
    Item[j].Icon = 1;
    folder = Item[j].Folder-1;
      }
      break; 
    }
    createTree();
  }

// This recursive function gets the parameter and loads the item into Item[].
  public void addFolder(int Level, String Folder)
  {
    String parameter, Status, icon, Location, Target, Name;
    int Num = 1;

    while(1==1)
    { string = getParameter(""+Folder+"/"+Num);
      if (string == null) return;
      StringTokenizer to = new StringTokenizer(string, ","); 
    icon     = to.nextToken();
    Name     = to.nextToken();
        if (to.hasMoreTokens()) Location = to.nextToken();
        else                    Location = "";
        if (to.hasMoreTokens()) Target = to.nextToken();
        else                    Target = ""; 
        if (to.hasMoreTokens()) Status = to.nextToken();
        else                    Status = ""; 
      Item[Num_Entries] = new entry(""+Level, icon, Location, Target, Name, Status);
      Num_Entries++;              
      Num++;
      if (Integer.parseInt(icon) < 2) addFolder(Level+1,""+Folder+"/"+Name);
    }
  }
 
//Draws the lines between the items.
  public void initConnectors()
  {
    int lastConnector;
    for (int i = Num_Entries-1; i>0; i--)
    {
      try { lastConnector = Item[i+1].Connector[Item[i].Folder];}
    catch(Exception e) {lastConnector=0;}

      if ( lastConnector == 0)  Item[i].Connector[Item[i].Folder] = 3;
      else          Item[i].Connector[Item[i].Folder] = 2;

      for (int j = i-1; j>0; j--)
    if (Item[i].Folder <= Item[j].Folder) Item[j].Connector[Item[i].Folder] = 1;
    else                      break;
    } 
  }

//Moves the visible items from Item[] to I[].
  public void createTree()
  {
    int i          = 1,
    level      = 0,
    Cur_Folder = 0;

    while( i < Num_Entries)
    {
      if (Item[0].Icon == 0) break; 
      level++;
      I[level] = i;
      if (Item[i].Icon == 0) 
      {
        Cur_Folder = i++;
        while ( (Item[i].Folder > Item[ Cur_Folder ].Folder) ){  if (++i >= Num_Entries ) break; }
      } 
      else i++;
    }
    MaxLevel=level;
    for (i = level+1; i<= Num_Entries; i++)  I[i]=-1;
    paintTree();
  }

//Draws the tree menu on the buffer.
  public void paintTree()
  {
    b.setColor(BACK_GROUND);
    b.fillRect(0,0,AppletX,AppletY);
    if ( Icon[ Item[0].Icon ] != null) b.drawImage(  Icon[ Item[0].Icon ], BufferX,BufferY,null);
     
    if (Item[0].Icon == 1)
    {
      for (int i=1; I[i] != -1; i++)
      {
    b.setColor(CONNECTORS);
        for (int j=0; j<=Item[I[i]].Folder; j++)
    {
       switch(Item[I[i]].Connector[j])
       {
         case 1: b.drawLine( j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+BufferY,j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+SPACING+BufferY);
        break;
         case 2: b.drawLine( j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+BufferY,j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+SPACING+BufferY);
             b.drawLine( j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+((int)SPACING/2)+BufferY,j*SPACING+SPACING+BufferX, SPACING*i+((int)SPACING/2)+BufferY);
        break;
         case 3: b.drawLine( j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+BufferY,j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+((int)SPACING/2)+BufferY);
             b.drawLine( j*SPACING+((int)SPACING/2)+BufferX, SPACING*i+((int)SPACING/2)+BufferY,j*SPACING+SPACING+BufferX, SPACING*i+((int)SPACING/2)+BufferY);
        break;
       }
    }

        if (Icon[ Item[I[i]].Icon ] != null)b.drawImage(  Icon[ Item[I[i]].Icon ], Item[I[i]].Folder*SPACING+SPACING+BufferX,SPACING*i+BufferY, this);
    if (oldItem == i)
    { if (Item[ I[oldItem]].Location != ""){b.setColor(UNDER_LINE); b.drawLine(Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX, SPACING*oldItem+14+BufferY,Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX+( b.getFontMetrics().stringWidth(Item[ I[oldItem] ].Name) ), SPACING*oldItem+14+BufferY);}
      b.setColor(TEXT_HILIGHT);
    }else b.setColor(TEXT_NORMAL);
        b.drawString( Item[ I[i] ].Name, Item[I[i]].Folder*SPACING+SPACING+20+BufferX, SPACING*i+13+BufferY);
      }
    }
    else
    {
      b.setColor(TEXT_NORMAL);b.drawString("Code by: Matthew Wilson",5,AppletY-25);
      b.setColor(TEXT_HILIGHT);b.drawString("mlavwilson@hotmail.com",5,AppletY-10);
    }
    repaint();
  }

//if the entry has a url, this function points the browser to it.
  public boolean mouseUp(Event evt, int x, int y)
  {
    y = y-BufferY;
    if  ((Item[0].Icon == 0) && (y > 350)) showDocument("mailto:mlavwilson@hotmail.com","main"); 
    if (y/SPACING > Num_Entries)           return true;

    if ( (I[y/SPACING] != -1) || ( (y/SPACING) == 0) )
    {
      if (Item[ I[y/SPACING] ].Icon < 2)
      {
    if( (x/SPACING) == Item[I[y/SPACING]].Folder+1 )
        {
          if (Item[I[y/SPACING]].Icon == 1) Item[I[y/SPACING]].Icon = 0; 
          else                  Item[I[y/SPACING]].Icon = 1;
        }
    else if (Item[I[y/SPACING]].Location != "") showDocument(Item[I[y/SPACING]].Location, Item[I[y/SPACING]].Target);
             else if (Item[I[y/SPACING]].Icon == 1) Item[I[y/SPACING]].Icon = 0;else Item[I[y/SPACING]].Icon = 1;
    createTree();     
      } 
      else showDocument(Item[I[y/SPACING]].Location, Item[I[y/SPACING]].Target);
    }
    return true;
  }

//If the item has a url the name is underlined, and the status bar is populated.
  public boolean mouseMove(Event e, int x, int y)
  {
    if ( (I[(y-BufferY)/SPACING] != -1)  )
    {
      if ((y-BufferY)/SPACING != oldItem)
      {
    if(oldItem != 0)
    {
          b.setColor(BACK_GROUND); b.drawLine(Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX, SPACING*oldItem+14+BufferY,Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX+( b.getFontMetrics().stringWidth(Item[ I[oldItem] ].Name) ), SPACING*oldItem+14+BufferY);
      b.setColor(TEXT_NORMAL); b.drawString( Item[ I[oldItem] ].Name, Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX, SPACING*oldItem+13+BufferY);
    }

        oldItem = (y-BufferY)/SPACING;

        if (oldItem != 0)
    {
      if (Item[ I[oldItem]].Location != ""){b.setColor(UNDER_LINE); b.drawLine(Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX, SPACING*oldItem+14+BufferY,Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX+( b.getFontMetrics().stringWidth(Item[ I[oldItem] ].Name) ), SPACING*oldItem+14+BufferY);}
      b.setColor(TEXT_HILIGHT); b.drawString( Item[ I[oldItem] ].Name, Item[I[oldItem]].Folder*SPACING+SPACING+20+BufferX, SPACING*oldItem+13+BufferY);
    }

        repaint(); showStatus(Item[I[(y-BufferY)/SPACING]].Status);
      }
    }
   
    if (Item[0].Icon == 1)
    {
      if ( (y>(AppletY-SPACING)) && ( BufferY > -((SPACING*MaxLevel) - (AppletY-SPACING))   )  )
      { BufferY = BufferY-SPACING;
        paintTree();
        repaint();
      }
      if ( (y< 10) && (BufferY < 0) )
      { BufferY = BufferY+SPACING;
        paintTree();
        repaint();
      }
    }
    else 
    {
      if(y > AppletY-20) showStatus("Contact the author of this menu applet:  mlavwilson@hotmail.com");
    }
    return true;
  }

//converts hex to java Color.
  public Color setParmColor(String s, Color c) // Convert hexadecimal RGB parameter to color
  {
    int[] hex = new int[6];
    String h ="0123456789abcdef";

    if ((s!=null)&&(s.length()==6))
    { for (int i=0;i<6;i++) for (int j=0;j<16;j++)
        if (Character.toLowerCase(s.charAt(i))==h.charAt(j)) hex[i]=j;
      c=new Color(hex[0]*16+hex[1], hex[2]*16+hex[3], hex[4]*16+hex[5]);
    }
    return c;
  }

  public String[][] getParameterInfo()
  {
    String[][] info = {
    {"color-highlight"  , "COLOR"   , "#123456"     },
    {"color-text"       , "COLOR"   , "#123456"     },
    {"color-background" , "COLOR"   , "#123456"     },
    {"color-connectors" , "COLOR"   , "#123456"     },
    {"color-under-line" , "COLOR"   , "#123456"     },
    {"!font-name-size"  , "FONT "   , "Helvetica,12"},
    {"spacing"      , "int  "   , "18"          },
    {"icon[0-?]"        , "IMAGE"   , "icons/me.gif"},
    {"[folder name]/[1-?]"  , "Item in menu", "Icon,Name,URL,Target,Status bar"}
    };
    return info;
  }

//Functions the javascript can acess.
  public void setTEXT_NORMAL(String string) {TEXT_NORMAL  = setParmColor( string, TEXT_NORMAL ); paintTree(); }
  public void setBACK_GROUND(String string) {BACK_GROUND  = setParmColor( string, BACK_GROUND ); paintTree(); }
  public void setCONNECTORS(String string)  {CONNECTORS   = setParmColor( string, CONNECTORS  ); paintTree(); }
  public void setTEXT_HILIGHT(String string){TEXT_HILIGHT = setParmColor( string, TEXT_HILIGHT); paintTree(); }
  public void setUNDER_LINE(String string)  {UNDER_LINE   = setParmColor( string, UNDER_LINE  ); }
  public void setSPACING(String string)     {try{SPACING  = Integer.parseInt(string); BufferX=BufferY=0; paintTree();} catch(Exception e) {}}
  public void setFONT_SIZE(String string)   {int size = (string == null ? 14 : Integer.parseInt(string)); NAME_FONT  = new Font("Helvetica", 0, size);  b.setFont(NAME_FONT); paintTree(); }


  public void update(Graphics g){paint(g);}//eliminate flicker.
  public void debug(String string){ System.out.println(string); }//used for debuging.
  public String getAppletInfo()   {return "\t   applet menu v1.5\nby Matthew Wilson:  mlavwilson@hotmail.com";}

//if the window was resized, createTree() is called.
  public void paint(Graphics g)
  {
    if(AppletY != size().height)
    { 
      AppletY = size().height;
      createTree();
    }
    g.drawImage(buffer,0,0,null);
  }

//Points the browser to the URL L and Target T.
  public void showDocument(String L, String T)
  {
    String Path;
    if ( (L.startsWith("http")) || (L.startsWith("mail") ) )  Path ="";
    else                              Path =""+getCodeBase();

    try{ url = new URL( Path+L ); }  catch (MalformedURLException e)   { showStatus("Malformed URL"); }
    getAppletContext().showDocument( url, T );  
  }
}