/*
//  FileName: WBCanvas.java
//  
//  Vijay Konkimalla
//  07 Dec 95
//
//  Objective: 
//
//  $Author: konki $
//  $Log: WBCanvas.java,v $
# Revision 1.5  1996/04/01  21:20:29  konki
# Added Select/Delete Capabilities and took care of text input
#
# Revision 1.4  1996/03/21  18:10:04  konki
# Added Saving and loading from a file
#
# Revision 1.3  1996/03/11  21:03:06  konki
# Added inside to fix the Java bug
# Added code to take care of incomplete drawings going outside the canvas
#
# Revision 1.2  1996/02/21  00:27:09  konki
# *** empty log message ***
#
# Revision 1.1  1996/02/18  23:10:41  konki
# Initial revision
#
//  $Id: WBCanvas.java,v 1.5 1996/04/01 21:20:29 konki Exp $
*/

package wb;

import  java.awt.*;
import  java.lang.*;
import  java.util.*;
import  java.io.*;

/**
 * WBCanvas is derived from Canvas and is corresponds to View in the MVC 
 * paradigm. Objects are not only rendered here but are also sent to all 
 * the clients.
 * It has the Repositary of objects that are created and that came over the
 * network
 */

public class WBCanvas extends Canvas implements GTypes
{
    /**
     * Boolean flag indicating the current mouse state with respect to the
     * object that is being created
    private boolean indrag = false;
     */

    /**
     * Boolean flag indicating the mouse position is inside the canvas
     */
    private boolean inside = false;

    /**
     * Current Drawing mode of canvas
     */
    private int     mode = LINES;

    /**
     * Current lower limits of mouse movement during rubber banding
     */
    private int     leastx, leasty;

    /**
     * Current upper limits of mouse movement during rubber banding
     */
    private int     maxx, maxy;

    /**
     * Start x, y co-ordinates of the current object being drawn
     */
    private int     startx, starty;

    /**
     * x, y co-ordinates that are useful in drawing polylines and spokes
     */
    private int     otherx, othery;

    /**
     * Current cursor style. Depends upon the mode of the canvas
     */
    private int     cursorstyle;

    /**
     * Temporary queue of the objects that are to be erased in the next update
     * provides a feel of rubber banding
     */
    private Vector  eraseunits = new Vector(100, 100);

    /**
     * Temporary queue of the objects that are to be drawn in the next update
     * and are to be pushed onto the eraseunits queue. 
     */
    private Vector  dragunits = new Vector(100, 100);

    /**
     * Temporary storage of keys of GraphUnits that come over the network
     */
    private Vector extkeys = new Vector(100);

    /**
     * Storage of the Objects that are created locally. 
     */
    private Vector  doneunits = new Vector(500,250);

    /**
     * Storage of the Objects that have been selected for delete
     */
    private Vector  delunits = new Vector(50);

    /**
     * Storage for objects that come over the network. 
     */
    private Hashtable  extunits = new Hashtable();
    
    /**
     * Place holder for the objects like polylines, spokes, text.
     */
    private GraphUnit current = null;

    /**
     * Place holder for the current color state
     */
    private Color     thiscol = null;

    /**
     * Place holder for the current Font Size
     */
    private int     thisfont = NORMAL;

    /**
     * Place holder of the frame that contains this canvas. Used for setting
     * the cursor shape
     */
    private Frame   above;

    /**
     * Place holder of the writer thread that transmits the local objects over
     * the network
     */
    private WriteClient writer = null;

    /**
     * Place holder for generating the message like clear or 
     * delete(to be implemented )
     */
    private Message mu = null;

    /**
     * Place holder for Dialog to confirm Delete 
     */
    private boolean decision = false;


    /**
     * setbounds update the lower and upper extents of mouse movement.
     */
    private void setbounds( int x, int y)
    {
        leastx = ( x < leastx ) ? x : leastx;
        leasty = ( y < leasty ) ? y : leasty;
        maxx = ( x > maxx ) ? x : maxx;
        maxy = ( y > maxy ) ? y : maxy;
    }

    /**
     * myupdate is a common local update. for convenience 
     */
    private void myupdate()
    {
        update(getGraphics());
    }

    /**
     * myupdate_n_paint is a common local update. for convenience 
     */
    private void myupdate_n_paint()
    {
        update(getGraphics());
        paint(getGraphics()); 
    }

    /**
     * Not really used
     */
    private void myerase_n_paint()
    {
        Graphics thisg = getGraphics();
        // erase the objects
        // clear
        // paint all
        if ( thisg != null ) {
            update(thisg);
            super.paint(thisg);
            paint(thisg);
        }
    }


    /**
     * myclear_n_paint does the clearing of the screen and repainting
     */
    private void myclear_n_paint()
    {
        super.paint(getGraphics());
        update(getGraphics());
        paint(getGraphics());
    }


    /**
     * check if the mousedown event matches with the handles.
     * if so, remove them from the doneunits and place them on the
     * delunits
     */
    private synchronized void checkdelete( int x, int y)
    {
        int index, nobjects = 0;
        for (index = 0, nobjects = doneunits.size(); 
                  index < nobjects; index++) {
            GraphUnit temp = (GraphUnit) doneunits.elementAt(index);
            if ( temp.isInside(x,y) ) {
                doneunits.removeElement(temp);
                delunits.addElement(temp);
                eraseunits.addElement(temp);
                return;
            }
        }
    }
    

    /**
     * add is a common point to add the object created to the storage plus
     * transmission over the network
     */
    private void add ( GraphUnit passed )
    {
        if ( passed == null )
            return;
        doneunits.addElement(passed);
        if ( writer != null ) 
            writer.Add(passed);
    }

    /**
     * clear is a common point to clear the screen. the boolean transmit tells
     * source of clear command. (local user/ other user on the network)
     */
    private void clear ( boolean transmit ) 
    {
        Exception any = null;
        try {
            super.paint(getGraphics());
        } catch ( Exception excep ) {
            any = excep;
        } finally {
            if ( any == null ) {
                doneunits = new Vector(500,200);
                extunits.clear();
                extkeys = new Vector(100);
                dragunits = new Vector(100,100);
                delunits = new Vector(50);
                eraseunits = new Vector( 100,100);
                current = null;
            }
            if ( transmit && writer != null ) {
                mu = new Message( OCLEAR );
                mu.print();
                writer.Add(mu);
            }
        }
    }
  
    private void afterDelete( )
    {
        while( !delunits.isEmpty() ) {
            GraphUnit x = (GraphUnit) delunits.firstElement();
            delunits.removeElement(x);
            if ( decision ) {
                if ( writer != null ) {
                    Message temp = new Message( ODELETE );
                    temp.sethashkey( x.gethashkey());
                    writer.Add(temp);
                }
            } else {
                x.ResetSelect();
                doneunits.addElement(x);
            }
        }
        decision = true;
    }

    private synchronized void prepareSelect( int t)
    {
        int index, nobjects = 0;
        for (index = 0, nobjects = doneunits.size(); 
                  index < nobjects; index++) {
            GraphUnit x = (GraphUnit) doneunits.elementAt(index);
            if ( t == DELETE ) {
                x.setSelect();
            } else {
                x.ResetSelect();
            }            
        }
/*
        if ( t != DELETE ) {
            afterDelete();
        }
*/
    }

    /**
     * handlePolyUnits does the necessary work of completing "current" object
     */
    private boolean handlePolyUnits( Event e, int x, int y )
    {
        if ( current == null ) {
            if ( mode == POLYS )
                current = (e.clickCount > 1 || e.modifiers == Event.SHIFT_MASK) 
                           ? null : new GraphPolyLine(x,y,thiscol);
            else if ( mode == SPOKES ){
                otherx = x;
                othery = y;
                current = (e.clickCount > 1 || e.modifiers == Event.SHIFT_MASK) 
                           ? null : new GraphSpoke(x, y, thiscol);
            }
            return true;
        }
        if ( e.clickCount > 1 || e.modifiers == Event.SHIFT_MASK) {
            current.addnext(x,y);
            add(current);
            myupdate_n_paint();
            current = null;
        }
        else {
            current.addnext(x,y);
            myupdate();
        }
        return true; 
    }

    /**
     * Creates  a new Canvas.
     * @param arg the frame that holds this canvas
     */
    public WBCanvas( Frame arg )
    {
//        setFont(new Font("Helvetica", Font.BOLD, 11));
        thiscol = new Color(0,0,255);
        setBackground(Color.white);
        above = arg;
        cursorstyle = arg.getCursorType();
        above.setCursor(Frame.CROSSHAIR_CURSOR);
        leastx = maxx = 400;
        leasty = maxy = 250; 
        decision = true;
    }

    /**
     * Creates  a new Canvas that is ready for conference.
     * @param arg the frame that holds this canvas
     * @param passed the writer to whom the locally created  objects/messages 
     *               are sent for transmission. 
     */
    public WBCanvas( Frame arg, WriteClient passed )
    {
//        setFont(new Font("Helvetica", Font.BOLD, 11));
        this( arg );
        writer = passed;
        if( writer != null) {
            writer.start();
        }
    }

    /**
     * Set the Writer for this Canvas
     * @param arg WriteClient
     */
    public void setWriter( WriteClient arg )
    {
        writer = arg;
        if (writer != null ) {
            writer.start(); 
        }
    }

    /**
     * NewColor changes the state of current color
     * @param arg the Color of the objects that would be created in future.
     */
    public synchronized void NewColor( Color arg )
    {
        thiscol = arg;
    }

    /**
     * NewFontSize changes the size of the Text size
     * @param arg the size of the objects that would be created in future.
     */
    public synchronized void NewFont( int arg )
    {
        thisfont = arg;
    }

    /**
     * Refresh As the name implies it refreshes the screen.
     */
    public synchronized void Refresh()
    {
        myclear_n_paint();
    }

    /**
     * Clear As the name implies clears the screen and the storage
     * losing all the previously created object info.
     */
    public synchronized void Clear()
    {
        if ( mode != DELETE ) {
            clear ( true );
        }
    }

    /**
     * Add adds a graphunit to the canvas. this specifically handles the
     * requests from other participants. It identifies if the added object 
     * is a message and deciphers it accordingly
     * @param passed the GraphUnit/Message received over the network
     * @param userhost the source of GraphUnit/Message 
     */
    public  synchronized void Add( GraphUnit passed, String userhost)
    {
        if ( passed.gettype() < MESSAGE ) {
            extunits.put( userhost + 
                         Integer.toString(passed.gethashkey()), passed );
            extkeys.addElement(userhost +
                         Integer.toString(passed.gethashkey()));
        } 
        else { 
            switch ( passed.gettype() ) {
                case OCLEAR:
                    clear( false );
                break;
                case ODELETE:
                    GraphUnit x = null;
                    String keystring = new String ( userhost + 
                            Integer.toString(passed.gethashkey()) ); 
                    if ( extunits.containsKey( keystring ) ) {
                        x = (GraphUnit) extunits.remove(keystring); 
                    }
                    if ( x != null ) {
                        eraseunits.addElement(x);
                    } 
                default:
                break;
            }
        }
        myupdate();
    }

    /**
     * returns the current drawing mode of the canvas
     * @return int the current mode
     * @see#setDrawType
     * @see GTypes
     */
    public int getDrawType()
    {
        return mode;
    }

    /**
     * setDrawType set the current mode of the canvas
     * @param t the new mode
     * @see#getDrawType
     */
    public  synchronized void setDrawType(int t)
    {
        if ( mode == DELETE && t != DELETE ) {  
            prepareSelect(t);
            afterDelete();
            myclear_n_paint();
        }
        mode = t;
        if( current != null && current.isValid() ) {
            add(current);
        }
        current = null;
        switch (mode) {
            case TEXTS:
                above.setCursor(Frame.TEXT_CURSOR);
                cursorstyle = Frame.TEXT_CURSOR;
                break;
            case POINTS:
            case POLYS:
            case SPOKES:
            case MARKERS:
            case QUESTION:
                above.setCursor(Frame.DEFAULT_CURSOR);
                cursorstyle = Frame.DEFAULT_CURSOR;
                break;
            case DELETE:
                prepareSelect(DELETE);
                myupdate_n_paint();
            default:
                above.setCursor(Frame.CROSSHAIR_CURSOR);
                cursorstyle = Frame.CROSSHAIR_CURSOR;
                break;
        }
    }


    /**
     * Handler for mouseEnter events. Takes care of the cursor shape
     * and sets the inside flag true
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public boolean mouseEnter( Event e, int x, int y )
    {
        inside = true;
        above.setCursor(cursorstyle);
        return true;
    }

    /**
     * Handler for mouseExit events.
     * sets the inside flag false
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public boolean mouseExit( Event e, int x, int y )
    {
        inside = false;
        return true;
    }

    /**
     * Handler for mouseDown events. Used to initiate the creation of
     * graphunits.
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public  boolean mouseDown( Event e, int x, int y )
    {
        if( !inside )
            return false;
        setbounds( x, y);
        startx = x;
        starty = y;
        switch( mode ) {
            case QUESTION:
            case MARKERS:
                if( current == null ) { 
                    current = (mode != MARKERS) ? 
                      (GraphUnit) new GQuestMarker(x,y,thiscol)  
                     :(GraphUnit) new GCrossMarker(x,y,thiscol);
                }
                break;
            case POLYS:
            case SPOKES:
                return (handlePolyUnits(e, x, y));
            case POINTS:
                if ( current == null ) {
                    current = new GFreeHand( x, y, thiscol );
                } 
                otherx = x;
                othery = y;
                break;
            case TEXTS:
                if( current != null && current.isValid() ) {
                    add(current);
                }
                current = new GraphText(x, y, thisfont, thiscol);
                break;
            case DELETE:
                checkdelete(x, y);
                myupdate();
                break;
            default:
        }
        return true;
    }


    /**
     * Handler for mouseDrag events. This whole stuff is required 
     * to give the rubber banding effect
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public  boolean mouseDrag( Event e, int x, int y )
    {
        if( !inside )
            return false;
        setbounds( x, y);
        switch( mode ) {
        case OVALS:
            dragunits.addElement(new GraphOval(startx, starty, x, y, thiscol));
            break;
        case RECTS:
            dragunits.addElement(new GraphRect(startx, starty, x, y, thiscol));
            break;
        case LINES:
            dragunits.addElement(new GraphLine(startx, starty, x, y, thiscol)); 
            break;
        case RDRECTS:
            dragunits.addElement(new GraphRRect(startx, starty, x, y, thiscol));
            break;
        case POINTS:
            if ( (current != null) && 
                 ((Math.abs(otherx-x) >= 3) || (Math.abs(othery-y) >= 3))) {
                current.addnext(x,y);
                otherx = x;
                othery = y;
            }
            break;
        default:
            return false;
        }
        myupdate();
        return true;
    }

    /**
     * Handler for mouseMove events. This whole stuff is required 
     * to give the rubber banding effect esp. for polylines and spokes
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public boolean mouseMove( Event e, int x, int y)
    {
        if( !inside )
            return false;
        switch( mode ) {
        case POLYS:
        case SPOKES:
            setbounds( x, y);
            if ( current != null ) {
                dragunits.addElement(new GraphLine(
                           mode == POLYS ? startx : otherx, 
                           mode == POLYS ? starty : othery, 
                           x, y, thiscol));
                myupdate();
            }
            return true;
        default:
            return false;
        }
    }

    /**
     * Handler for mouseUp events. Marks the end of object creation in most
     * cases.
     * @param e  Event to be handled
     * @param x  the x co-ordinate of the event to be handled
     * @param y  the y co-ordinate of the event to be handled
     * @return boolean indicating if the event is handled.
     */ 
    public  boolean mouseUp( Event e, int x, int y )
    {
        GraphUnit temp = null;
        if( !inside )
            return false;
        setbounds( x, y);
        if ( (mode != POINTS) && ((Math.abs(startx-x) < 1) 
                                   && (Math.abs(starty-y) < 1))) {
            return true;
        }
        switch( mode ) {
        case OVALS:
            temp = new GraphOval(startx, starty, x, y, thiscol);
            break;
        case RECTS:
            temp = new GraphRect(startx, starty, x, y, thiscol);
            break;
        case LINES:
            temp = new GraphLine(startx, starty, x, y, thiscol); 
            break;
        case RDRECTS:
            temp = new GraphRRect(startx, starty, x, y, thiscol);
            break;
        case POINTS:
            if ( current != null && current.isValid()) {
              temp = current;
              current = null;
            }
            break;
        case QUESTION: // Not  used any more
        case MARKERS:
            if( current != null ) { 
                temp = current;
                current = null;
            }
            break;
        default:
        }
        add(temp);
        myupdate_n_paint();
        return true;
    }

    /**
     * Handler for keyUp events. 
     * @param e  Event to be handled
     * @param key  the key released
     * @return boolean indicating if the event is handled.
     */ 
    public boolean keyUp( Event evt, int key) 
    {
        return (super.keyUp(evt, key));
    }

    /**
     * Handler for action events. 
     * @param evt  Event to be handled
     * @param what the object
     * @return boolean indicating if the event is handled.
     */ 
    public boolean action( Event evt, Object what)
    {
        return (super.action(evt, what));
    }

    /**
     * Handler for keyDown events. Used in Text 
     * @param evt Event to be handled
     * @param key the key pressed
     * @return boolean indicating if the event is handled.
     */ 
    public boolean keyDown( Event evt, int key )
    {
        if (mode != TEXTS || current == null || !inside)
            return false;
        if ( key == '\n') {
//            current.addnext((char)key);
            if ( current.isValid() )
                add(current); 
            current = null;  
        }
        else if ( key >= 32 && key < 127 ){
            current.addnext((char)key);
        }
        myupdate();
        return true;
    }

    /**
     * Handler for update requests. Called either locally or by the screen
     * update thread of the runtime.
     * @param g Graphics onto which the update needs to be performed
     */ 
    public synchronized void update(Graphics g)      
    {
        while( !eraseunits.isEmpty() ) {
            GraphUnit x = (GraphUnit) eraseunits.firstElement();
            eraseunits.removeElement(x); 
            x.erase(g,getBackground());
//          x.finalize();   // would like to explicilty call finalize
        }
        while( !dragunits.isEmpty() ) {
            GraphUnit x = (GraphUnit) dragunits.firstElement();
            dragunits.removeElement(x); 
            x.paint(g);
            eraseunits.addElement(x);
        }
        while( !extkeys.isEmpty() ) {
            String keystring = (String) extkeys.firstElement();
            extkeys.removeElement(keystring);
            GraphUnit x = (GraphUnit) extunits.get(keystring);
            if ( x != null ) {
                x.paint(g);
            }
        }
        if ( current != null && current.isValid())
                current.paint(g);
    }

    /**
     * Handler for paint requests. Called either locally or by the screen
     * refresh thread of the runtime.
     * @param g Graphics onto which the update needs to be performed
     */ 
    public synchronized void paint(Graphics g)
    {
        int index, nobjects;
        if ( current != null && current.isValid() )
                current.paint(g);
        for (index = 0, nobjects = doneunits.size(); 
                  index < nobjects; index++) {
            GraphUnit x = (GraphUnit) doneunits.elementAt(index);
            x.paint(g);
        }
        for ( Enumeration keys = extunits.elements();
                           keys.hasMoreElements();) {
            GraphUnit x = (GraphUnit) keys.nextElement();
            x.paint(g);
        }
    }


    /**
     * Needed to fix a bug in Java
     * @param x x-coordinate of the point
     * @param y y-coordinate of the point
     * @return true iff (x,y) is inside this
     */
    public boolean inside(int x, int y) 
    {
        Rectangle r = bounds();
        return(x >= 0 && x < r.width && y >= 0 && y < r.height);
    }

    /**
     * Method that saves the GraphUnits to a file.
     * @param dos DataOutputStream to a file stream
     * @exception IOException if there is any I/O Error
     */
    public synchronized void SaveCanvas( DataOutputStream dos )
                                throws IOException
    {
        int index, nobjects;
        for (index = 0, nobjects = doneunits.size(); 
                  index < nobjects; index++) {
            GraphUnit x = (GraphUnit) doneunits.elementAt(index);
            dos.writeInt(x.gettype());
            x.packetize(dos);
        }
        for (Enumeration keys = extunits.elements();
                  keys.hasMoreElements(); ) {
            GraphUnit x = (GraphUnit) keys.nextElement();
            x.packetize(dos);
        }
    }

    /**
     * Method that reads GraphUnits from a file.
     * @param din DataInputStream to a file stream
     * @exception IOException if there is any I/O Error
     */
    public synchronized void LoadCanvas( DataInputStream din )
                                throws IOException
    {
        int type = -1;
        GraphUnit gu = null;
        while( true ) {
            try {
                type = din.readInt();             
                switch( type ) {
                    case OVALS:
                        gu = new GraphOval( din ); 
                        break;
                    case RECTS: 
                        gu = new GraphRect( din ); 
                        break;
                    case LINES: 
                        gu = new GraphLine( din ); 
                        break;
                    case RDRECTS: 
                        gu = new GraphRRect( din ); 
                        break;
                    case POINTS: 
                        gu = new GFreeHand( din ); 
                        break;
                    case TEXTS: 
                        gu = new GraphText( din ); 
                        break;
                    case POLYS: 
                        gu = new GraphPolyLine( din ); 
                        break;
                    case SPOKES: 
                        gu = new GraphSpoke( din ); 
                        break;
                    case MARKERS: 
                        gu = new GCrossMarker( din ); 
                        break;
                    case QUESTION: 
                        gu = new GQuestMarker( din ); 
                        break;
                    default:
                        gu = null;
                }
            } catch (IOException any) {
                if (any instanceof EOFException ) {
                    break;
                }
                throw any; // IO error
            } finally {
                if ( gu != null ) {
                    add(gu);
                }
            }
        }
        myupdate_n_paint();
    
    }

    public void CancelDelete() 
    {
        if ( mode == DELETE ) {
            decision = false;
            setDrawType(LINES);
        }
    }
}

/*
 * End of File: WBCanvas.java
 */