FileDocCategorySizeDatePackage
ScribbleCutAndPaste.javaAPI DocExample11854Mon Sep 22 13:30:30 BST 1997None

ScribbleCutAndPaste.java

// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com)
// Copyright (c) 1997 by David Flanagan
// This example is provided WITHOUT ANY WARRANTY either expressed or implied.
// You may study, use, modify, and distribute it for non-commercial purposes.
// For any commercial use, see http://www.davidflanagan.com/javaexamples

import java.awt.*;               
import java.awt.event.*;
import java.awt.datatransfer.*;  // Clipboard, Transferable, DataFlavor, etc.
import java.util.Vector;         // To store the scribble in

/** 
 * This class demonstrates how to implement cut-and-paste of data 
 * other than strings.  It is a variant of the Scribble program we've
 * seen so much.  Only about a third of this code is directly cut-and-paste
 * code.  The rest is support code to make this an interesting example
 **/
public class ScribbleCutAndPaste extends Frame {
  /** A very simple main() method for our program. */
  public static void main(String[] args) { new ScribbleCutAndPaste(); }

  /** 
   * Remember # of open windows so we can quit when the last one is closed 
   * We support multiple windows so that we can cut-and-paste among them.
   **/
  protected static int num_windows = 0;

  /** Create a Frame, Menu, and ScrollPane for the scribble component */
  public ScribbleCutAndPaste() {
    super("ScribbleCutAndPaste");            // Create the window
    num_windows++;                           // Count it

    // Create scribble area and add to the frame
    ScribblePanel scribble = new ScribblePanel(this, 400, 300); 
    this.add(scribble, "Center");

    // Set up a menubar
    MenuBar menubar = new MenuBar();         // Create menubar
    this.setMenuBar(menubar);                // Add it to the frame
    Menu file = new Menu("File");            // Create a File menu
    menubar.add(file);                       // Add to menubar

    // Create three menu items, with menu shortcuts, and add to the menu
    MenuItem n, c, q;
    file.add(n = new MenuItem("New Window", new MenuShortcut(KeyEvent.VK_N)));
    file.add(c = new MenuItem("Close Window",new MenuShortcut(KeyEvent.VK_W)));
    file.addSeparator();
    file.add(q = new MenuItem("Quit", new MenuShortcut(KeyEvent.VK_Q)));

    // Create and register action listener objects for the three menu items
    n.addActionListener(new ActionListener() {     // Open a new window
      public void actionPerformed(ActionEvent e) { new ScribbleCutAndPaste(); }
    });
    c.addActionListener(new ActionListener() {     // Close this window
      public void actionPerformed(ActionEvent e) { close(); }
    });
    q.addActionListener(new ActionListener() {     // Quit the program
      public void actionPerformed(ActionEvent e) { System.exit(0); }
    });

    // Another event listener, this one to handle window close requests
    this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) { close(); }
    });

    // Set the window size and pop it up
    this.pack();
    this.show();
  }

  /** Close a window.  If this is the last open window, just quit. */
  void close() {
    if (--num_windows == 0) System.exit(0);
    else this.dispose();
  }

  /**
   * This class is a custom component that supports scribbling.  It also has
   * a popup menu that provides access to cut-and-paste facilities.
   **/
  static class ScribblePanel extends Canvas implements ActionListener {
    protected short last_x, last_y;                // Coordinates of last click
    protected Vector lines = new Vector(256,256);  // Store the scribbles
    protected int width, height;                   // The preferred size
    protected PopupMenu popup;                     // The popup menu
    protected Frame frame;                         // The frame we are within
    
    /** This constructor requires a Frame and a desired size */
    public ScribblePanel(Frame frame, int width, int height) {
      this.frame = frame;
      this.width = width;
      this.height = height;
      
      // We handle scribbling with low-level events, so we must specify
      // which events we are interested in.
      this.enableEvents(AWTEvent.MOUSE_EVENT_MASK);
      this.enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);

      // Create the popup menu.
      String[] labels = new String[] {   "Clear", "Cut", "Copy", "Paste" };
      String[] commands = new String[] { "clear", "cut", "copy", "paste" };
      popup = new PopupMenu();                   // Create the menu
      for(int i = 0; i < labels.length; i++) {
        MenuItem mi = new MenuItem(labels[i]);   // Create a menu item 
        mi.setActionCommand(commands[i]);        // Set its action command
        mi.addActionListener(this);              // And its action listener
        popup.add(mi);                           // Add item to the popup menu
      }
      // Finally, register the popup menu with the component it appears over
      this.add(popup);
    }
    
    /** 
     * Specifies how big the component would like to be.  It always returns the
     * preferred size passed to the ScribblePanel() constructor 
     **/
    public Dimension getPreferredSize() {return new Dimension(width, height);}
    
    /** This is the ActionListener method invoked by the popup menu items */
    public void actionPerformed(ActionEvent event) {
      String command = event.getActionCommand();
      if (command.equals("clear")) clear();
      else if (command.equals("cut")) cut();
      else if (command.equals("copy")) copy();
      else if (command.equals("paste")) paste();
    }
    
    /** Draw all the saved lines of the scribble */
    public void paint(Graphics g) {
      for(int i = 0; i < lines.size(); i++) {
        Line l = (Line)lines.elementAt(i);
        g.drawLine(l.x1, l.y1, l.x2, l.y2);
      }
    }
    
    /** 
     * This is the low-level event-handling method called on mouse events 
     * that do not involve mouse motion.  It handles posting the popup menu
     * and also initiates scribbles
     **/
    public void processMouseEvent(MouseEvent e) {
      if (e.isPopupTrigger())                               // If popup trigger,
        popup.show(this, e.getX(), e.getY());               // Pop up the menu
      else if (e.getID() == MouseEvent.MOUSE_PRESSED) {     // Otherwise
        last_x = (short)e.getX(); last_y = (short)e.getY(); // Save position
      }
      else super.processMouseEvent(e);  // Pass other event types on
    }
    
    /**
     * This method is called for mouse motion events.  It adds a line to the
     * scribble, both on the screen and in the saved representation
     **/
    public void processMouseMotionEvent(MouseEvent e) {
      if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
        Graphics g = getGraphics();                      // Object to draw with
        g.drawLine(last_x, last_y, e.getX(), e.getY());  // Draw this line
        lines.addElement(new Line(last_x, last_y,        // And save it, too.
                                  (short) e.getX(), (short)e.getY()));
        last_x = (short) e.getX();  // Remember current mouse coordinates
        last_y = (short) e.getY(); 
      }
      else super.processMouseMotionEvent(e);  // Important!
    }
    
    /** Clear the scribble.  Invoked by popup menu */
    void clear() {
      lines.removeAllElements();   // Throw out the saved scribble
      repaint();                   // And redraw everything.
    }
    
    /** 
     * The DataFlavor used for our particular type of cut-and-paste data.
     * This one will transfer data in the form of a serialized Vector object.
     * Note that in Java 1.1.1, this works intra-application, but not between
     * applications.  Java 1.1.1 inter-application data transfer is limited to
     * the pre-defined string and text data flavors.
     **/
    public static final DataFlavor dataFlavor =
        new DataFlavor(Vector.class, "ScribbleVectorOfLines");
    
    /** 
     * Copy the current scribble and store it in a SimpleSelection object
     * (defined below).  Then put that object on the clipboard for pasting.
     **/
    public void copy() {
      // Get system clipboard
      Clipboard c = this.getToolkit().getSystemClipboard();
      // Copy and save the scribble in a Transferable object
      SimpleSelection s = new SimpleSelection(lines.clone(), dataFlavor);
      // Put that object on the clipboard
      c.setContents(s, s);
    }
    
    /** Cut is just like a copy, except we erase the scribble afterwards */
    public void cut() { copy(); clear();  }
    
    /** 
     * Ask for the Transferable contents of the system clipboard.
     * Then ask that Transferable object for the scribble data it represents.  
     * If either step fails, beep!
     **/
    public void paste() {
      Clipboard c = this.getToolkit().getSystemClipboard(); // Get clipboard
      Transferable t = c.getContents(this);                 // Get its contents
      if (t == null) {              // If there is nothing to paste, beep
        this.getToolkit().beep();
        return;
      }
      try { 
        // Ask for clipboard contents to be converted to our data flavor.
        // This will throw an exception if our flavor is not supported.
        Vector newlines = (Vector) t.getTransferData(dataFlavor); 
        // Add all those pasted lines to our scribble.
        for(int i = 0; i < newlines.size(); i++)
          lines.addElement(newlines.elementAt(i));
        // And redraw the whole thing
        repaint();  
      }
      catch (UnsupportedFlavorException e) { 
        this.getToolkit().beep();   // If clipboard has some other type of data
      }
      catch (Exception e) {          
        this.getToolkit().beep();   // Or if anything else goes wrong...
      }
    }
    
    /**
     * This nested class implements the Transferable and ClipboardOwner 
     * interfaces used in data transfer.  It is a simple class that remembers
     * a selected object and makes it available in only one specified flavor.
     * It would be useful for transferring other types of data, too.
     **/
    static class SimpleSelection implements Transferable, ClipboardOwner {
      protected Object selection;    // The data to be transferred
      protected DataFlavor flavor;   // The one data flavor supported

      /** The constructor.  Just initialize some fields */
      public SimpleSelection(Object selection, DataFlavor flavor) { 
        this.selection = selection;  // Specify data
        this.flavor = flavor;        // Specify flavor
      }
      
      /** Return the list of supported flavors.  Just one in this case */
      public DataFlavor[] getTransferDataFlavors() { 
        return new DataFlavor[] { flavor };
      }

      /** Check whether we support a specified flavor */
      public boolean isDataFlavorSupported(DataFlavor f) {
        return f.equals(flavor);
      }

      /** If the flavor is right, transfer the data (i.e. return it) */
      public Object getTransferData(DataFlavor f) 
           throws UnsupportedFlavorException {
             if (f.equals(flavor)) return selection;
             else throw new UnsupportedFlavorException(f);
      }
      
      /** 
       * This is the ClipboardOwner method.  Called when the data is no
       * longer on the clipboard.  In this case, we don't need to do much. 
       **/
      public void lostOwnership(Clipboard c, Transferable t) { 
        selection = null; 
      }
    }
    
    /** 
     * A class to store the coordinates of one scribbled line. 
     * The complete scribble is stored as a Vector of these objects 
     **/
    static class Line {
      public short x1, y1, x2, y2;
      public Line(short x1, short y1, short x2, short y2) {
        this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2;
      }
    }
  }
}