/* // 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 */