// The canvas supporting the drawing window. // (c) 1997 duane a. bailey package element; import java.awt.*; import java.awt.event.*; /** * The private extension to the Canvas class that is used to support * the DrawingWindow. * * @author duane a. bailey */ class DrawingCanvas extends Canvas implements MouseListener, MouseMotionListener, KeyListener, WindowListener { /** * The last place that the mouse was seen. Since the mouse movement * events are tracked, this is a good estimate of the current mouse * location. */ Pt mouse; // where the mouse was last seen /** * Whether or not the mouse was last seen in pressed position. * for multibutton mouse, this is the leftmost mouse button. */ boolean mousePressed; // whether the mouse is currently pressed int buttonMask; // event modifiers for button presses /** * A list of off-screen images that are used for backing store. * One is used to manage the current screen, another is used to * keep track of current updates. They switch roles only when the * secondary image is in an acceptable state. The user can control * this process with hold and release calls. */ Image osi[]; // off screen image /** * The offscreen pixmaps that are targets for graphics operations. * They are derived from the images, above. */ Graphics osg[]; // off screen pixmap /** * The index of the current offscreen bitmap. osi[cbi] is stable * and represents the current contents of the screen. It is never * written to directly. */ int cbi; // which image is being used for double buffer /** * The number of outstanding hold() calls performed by the user. * When this count reaches zero, the offscreen bitmaps are allowed * to swap. */ int holdDepth; // the level of hold-release calls /** * Current estimates of the width and height of the bitmaps. * May be poor estimates if the container is smaller. */ int width, height; /** * This barrier keeps track of whether or not a DrawingWindow has * been constructed. Since many resources are allocated only when * an initial drawing window has made contact with the current graphics * device, we must be careful not to attempt font allocation before * the first window is constructed. This code should be replaced * with a default allocation that is performed at startup. */ Barrier started; // signals startup /** * This barrier is freed when the mouse gets pressed next. It is * used to asynchronously communicate between the user's thread and the * Canvas' event monitor thread. */ Barrier mousePressLock; /** * This barrier is freed when the mouse gets released next. It is * used to asynchronously communicate between the user's thread and the * Canvas' event monitor thread. */ Barrier mouseReleaseLock; /** * This barrier is freed when the next key is pressed. Allows for * asynchronous communication with the user process on a key event. */ Barrier keyLock; /** * This character is the key that was pressed when the key event * occured. */ char keyChar; /** * Construct a 200x200 canvas. */ public DrawingCanvas() // post: construct a 200x200 canvas { this(200,200); } /** * Construct a drawing canvas with desired dimensions. * @param width the desired width of the canvas * @param height the desired height of the canvas */ public DrawingCanvas(int width, int height) // pre: 0 <= width, height // post: construct a canvas with the desired width and height { buttonMask = InputEvent.BUTTON1_MASK| InputEvent.BUTTON2_MASK|InputEvent.BUTTON3_MASK; setBackground(Color.white); setForeground(Color.black); setSize(width,height); this.width = width; this.height = height; // create the off-screen pixmap associated with window osi = null; osg = null; cbi = 0; holdDepth = 0; // listen to basic events addMouseListener(this); addMouseMotionListener(this); addKeyListener(this); // allocate locations mouse = new Pt(); started = new Barrier(); mousePressLock = new Barrier(); mouseReleaseLock = new Barrier(); keyLock = new Barrier(); } /** * Fetch the current writeable offscreen Graphics. * * @return the current writeable offscreen Graphics. */ public Graphics getOffscreenGraphics() // post: returns the offscreen graphics associated with // the canvas { if (osi == null) { started.P(); // wait for offscreen graphics to be created } return osg[0]; } /** * Fetch the current writeable offscreen image. This * generated the offscreen Graphics. */ public Image getImage(java.awt.image.ImageProducer ip) { if (osi == null) { started.P(); // wait for offscreen graphics to be created } return createImage(ip); } /** * Hold the double buffering in its current state. This allows * updating of the offscreen map is a timely and consistent manner. * Holds may be nested. * @see #release */ public void hold() // post: hold the screen updating process { if (cbi != 1) { cbi = 1; osg[cbi].drawImage(osi[0], 0, 0, null); } holdDepth++; } /** * Release one hold on the double buffering. When all holds are * released, the screen may be updated from a new secondary buffer. * @see #hold */ public void release() // pre: a hold has been placed on the screen updating process // post: a hold is released; screen will be updated { holdDepth--; if (holdDepth <= 0) { cbi = 0; repaint(); } } /** * Gets the current estimate of the mouse position. * @return the current estimated mouse position */ public Pt getMouse() // post: the current mouse position { return new Pt(mouse); } private void updateMouse(MouseEvent e) // pre: e is a MouseEvent, but not MouseReleased // post: mouse coordinates and press state is handled. { boolean mouseWasPressed = mousePressed; mousePressed = 0 != (e.getModifiers()&buttonMask); if (mouseWasPressed != mousePressed) { // System.out.println("Mouse state changed with event "+e); if (mousePressed) mousePressLock.V(); // notify anyone holding on the event. else mouseReleaseLock.V(); // notify anyone holding on the event. } else { //System.out.println("Mouse state DIDN'T changed with event "+e); } } private void updateMouseP(MouseEvent e) // pre: e is a MousePress event // post: mouse coordinates and press state is handled. { boolean mouseWasPressed = mousePressed; mousePressed = true; if (mouseWasPressed != mousePressed) { mousePressLock.V(); // notify anyone holding on the event. } } private void updateMouseR(MouseEvent e) // pre: e is a MouseReleased or MouseClicked event // post: mouse coordinates and press state is handled. { boolean mouseWasPressed = mousePressed; mousePressed = false; if (mouseWasPressed != mousePressed) { // System.out.println("Mouse state changed with event "+e); mouseReleaseLock.V(); // notify anyone holding on the event. } } /** * Returns true iff the mouse is currently pressed. * On multibutton mice, the leftmost button is the one queried. * @return true iff the mouse is currently pressed */ public boolean mousePressed() // post: returns true iff the mouse is currently pressed { return mousePressed; } /** * The event handler for a mouse pressed event. * @param e the mousePressed event */ public void mousePressed(MouseEvent e) // pre: the mouse was pressed // post: the event is recorded { mouse.moveTo(e.getX(),e.getY()); updateMouseP(e); } /** * The mouseReleased event handler. * @param e the mouseReleased event handler */ public void mouseReleased(MouseEvent e) // pre: the mouseReleased event handler // post: the event is recorded { mouse.moveTo(e.getX(),e.getY()); updateMouseR(e); } /** * The mouse entered window event handler * @param e the current mouse position is updated. */ public void mouseEntered(MouseEvent e) // pre: the mouse has just entered the window // post: the mouse position is updated { mouse.moveTo(e.getX(),e.getY()); } /** * The mouse left window event handler * @param e the current mouse position is updated */ public void mouseExited(MouseEvent e) // pre: the mouse just left the window // post: the mouse position is updated { mouse.moveTo(e.getX(),e.getY()); } /** * The mouse "clicked" event handler * @param e the mouseClicked event */ public void mouseClicked(MouseEvent e) // pre: the mouse was just clicked // post: the mouse position and state is updated { mouse.moveTo(e.getX(),e.getY()); updateMouseR(e); } /** * The mouse movement event handler * @param e the mouseDragged event */ public void mouseDragged(MouseEvent e) // pre: the mouse has moved // post: the mouse position and state is updated in the window { mouse.moveTo(e.getX(),e.getY()); updateMouseP(e); } /** * The mouse movement event handler * @param e the mouseMoved event */ public void mouseMoved(MouseEvent e) // pre: the mouse has moved // post: the mouse position and state is updated in the window { mouse.moveTo(e.getX(),e.getY()); updateMouseR(e); } /** * Wait until the mouse is pressed. * @return the position of the mouse press */ public Pt awaitMousePress() // post: returns the point of next mouse press; blocks until then { if (!mousePressed) mousePressLock.P(); return getMouse(); } /** * Wait until the mouse is released * @return the position of the mouse release */ public Pt awaitMouseRelease() // post: returns the point of the next mouse release; blocks until then { if (mousePressed) mouseReleaseLock.P(); return getMouse(); } /** * Wait until the key is pressed * @return returns the key pressed */ public char awaitKey() // post: returns the next key pressed { keyLock.P(); return keyChar; } /** * Key type event handler (currently not used) * @param evt the keyTyped event */ public void keyTyped(java.awt.event.KeyEvent evt) { return; } /** * The key pressed event handler * @param evt the keyPressed event */ public void keyPressed(java.awt.event.KeyEvent evt) // pre: the keyPressed event has taken place // post: the key is recorded and returned to awaiting processes { keyChar = evt.getKeyChar(); if (keyChar >= 0 && keyChar <= '~') { keyLock.V(); } return; } /** * The key released event handler (currently not used) * @param evt the key released event */ public void keyReleased(java.awt.event.KeyEvent evt) { return; } /** * Update a portion of the canvas. * * @param g the Graphics associated with the canvas to be updated */ public void update(Graphics g) // post: updates a portion of the canvas from osi { paint(g); } /** * Selective repaint of an area of the canvas. * (Selective portion of code must yet be implemented.) * Only active when there are no outstanding holds. * Repaints a selected are of the screen * @param left the left side of repaint area * @param top the top side of the repaint area * @param width the width of the repaint area * @param height the height of the repaint area */ public void repaint(int left, int top, int width, int height) { if (cbi == 1) return; repaint(); } /** * Update the canvas from offscreen bitmap. * * @param g the Graphics to be updated */ public void paint(Graphics g) // post: updates all of canvas from osg { if (osi == null) // first call; allocate bitmaps/images { osi = new Image[2]; osi[0] = createImage(width,height); osi[1] = createImage(width,height); osg = new Graphics[2]; osg[0] = osi[0].getGraphics(); osg[1] = osi[1].getGraphics(); cbi = 0; // current buffer index started.V(); } g.drawImage(osi[cbi], 0, 0, null); } // Window listening methods: public void windowOpened(WindowEvent e) { System.out.println("windowOpened"); } public void windowClosing(WindowEvent e) { System.out.println("windowClosing"); } public void windowClosed(WindowEvent e) { System.out.println("windowClosed"); } public void windowIconified(WindowEvent e) { System.out.println("windowIconified"); } public void windowDeiconified(WindowEvent e) { System.out.println("windowDeiconified"); } public void windowActivated(WindowEvent e) { System.out.println("windowActivated"); } public void windowDeactivated(WindowEvent e) { System.out.println("windowDeactivated"); } }