// This example is from the book "Java in a Nutshell, Second Edition".
// Written by David Flanagan. Copyright (c) 1997 O'Reilly & Associates.
// You may distribute this source code for non-commercial purposes only.
// You may study, modify, and use this example for any purpose, as long as
// this notice is retained. Note that this example is provided "as is",
// WITHOUT WARRANTY of any kind either expressed or implied.
import java.awt.*; // ScrollPane, PopupMenu, MenuShortcut, etc.
import java.awt.datatransfer.*; // Clipboard, Transferable, DataFlavor, etc.
import java.awt.event.*; // New event model.
import java.io.*; // Object serialization streams.
import java.util.zip.*; // Data compression/decompression streams.
import java.util.Vector; // To store the scribble in.
import java.util.Properties; // To store printing preferences in.
/**
* This class places a Scribble component in a ScrollPane container,
* puts the ScrollPane in a window, and adds a simple pulldown menu system.
* The menu uses menu shortcuts. Events are handled with anonymous classes.
*/
public class ScribbleFrame extends Frame {
/** A very simple main() method for our program. */
public static void main(String[] args) { new ScribbleFrame(); }
/** Remember # of open windows so we can quit when last one is closed */
protected static int num_windows = 0;
/** Create a Frame, Menu, and ScrollPane for the scribble component */
public ScribbleFrame() {
super("ScribbleFrame"); // Create the window.
num_windows++; // Count it.
ScrollPane pane = new ScrollPane(); // Create a ScrollPane.
pane.setSize(300, 300); // Specify its size.
this.add(pane, "Center"); // Add it to the frame.
Scribble scribble;
scribble = new Scribble(this, 500, 500); // Create a bigger scribble area.
pane.add(scribble); // Add it to the ScrollPane.
MenuBar menubar = new MenuBar(); // Create a 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(); // Put a separator in the menu
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 ScribbleFrame(); }
});
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 allows the scribble color to be set and provides access
* to printing, cut-and-paste, and file loading and saving facilities.
* Note that it extends Component rather than Canvas, making it "lightweight."
*/
class Scribble extends Component implements ActionListener {
protected short last_x, last_y; // Coordinates of last click.
protected Vector lines = new Vector(256,256); // Store the scribbles.
protected Color current_color = Color.black; // Current drawing color.
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 Scribble(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 using a loop. Note the separation of menu
// "action command" string from menu label. Good for internationalization.
String[] labels = new String[] {
"Clear", "Print", "Save", "Load", "Cut", "Copy", "Paste" };
String[] commands = new String[] {
"clear", "print", "save", "load", "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.
}
Menu colors = new Menu("Color"); // Create a submenu.
popup.add(colors); // And add it to the popup.
String[] colornames = new String[] { "Black", "Red", "Green", "Blue"};
for(int i = 0; i < colornames.length; i++) {
MenuItem mi = new MenuItem(colornames[i]); // Create the submenu items
mi.setActionCommand(colornames[i]); // in the same way.
mi.addActionListener(this);
colors.add(mi);
}
// Finally, register the popup menu with the component it appears over
this.add(popup);
}
/** Specifies big the component would like to be. It always returns the
* preferred size passed to the Scribble() 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) {
// Get the "action command" of the event, and dispatch based on that.
// This method calls a lot of the interesting methods in this class.
String command = event.getActionCommand();
if (command.equals("clear")) clear();
else if (command.equals("print")) print();
else if (command.equals("save")) save();
else if (command.equals("load")) load();
else if (command.equals("cut")) cut();
else if (command.equals("copy")) copy();
else if (command.equals("paste")) paste();
else if (command.equals("Black")) current_color = Color.black;
else if (command.equals("Red")) current_color = Color.red;
else if (command.equals("Green")) current_color = Color.green;
else if (command.equals("Blue")) current_color = Color.blue;
}
/** Draw all the saved lines of the scribble, in the appropriate colors */
public void paint(Graphics g) {
for(int i = 0; i < lines.size(); i++) {
Line l = (Line)lines.elementAt(i);
g.setColor(l.color);
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. Note the use of isPopupTrigger()
* to check for the platform-dependent popup menu posting event, and of
* the show() method to make the popup visible. If the menu is not posted,
* then this method saves the coordinates of a mouse click or invokes
* the superclass method.
*/
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) {
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, on 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.setColor(current_color); // Set the current color.
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(),
current_color));
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.
}
/** Print out the scribble. Invoked by popup menu. */
void print() {
// Obtain a PrintJob object. This posts a Print dialog.
// printprefs (created below) stores user printing preferences.
Toolkit toolkit = this.getToolkit();
PrintJob job = toolkit.getPrintJob(frame, "Scribble", printprefs);
// If the user clicked Cancel in the print dialog, then do nothing.
if (job == null) return;
// Get a Graphics object for the first page of output.
Graphics page = job.getGraphics();
// Check the size of the scribble component and of the page.
Dimension size = this.getSize();
Dimension pagesize = job.getPageDimension();
// Center the output on the page. Otherwise it would be
// be scrunched up in the upper-left corner of the page.
page.translate((pagesize.width - size.width)/2,
(pagesize.height - size.height)/2);
// Draw a border around the output area, so it looks neat.
page.drawRect(-1, -1, size.width+1, size.height+1);
// Set a clipping region so our scribbles don't go outside the border.
// On-screen this clipping happens automatically, but not on paper.
page.setClip(0, 0, size.width, size.height);
// Print this Scribble component. By default this will just call paint().
// This method is named print(), too, but that is just coincidence.
this.print(page);
// Finish up printing.
page.dispose(); // End the page--send it to the printer.
job.end(); // End the print job.
}
/** This Properties object stores the user print dialog settings. */
private static Properties printprefs = new Properties();
/**
* 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
* 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.
*/
static class SimpleSelection implements Transferable, ClipboardOwner {
protected Object selection; // The data to be transferred.
protected DataFlavor flavor; // The one data flavor supported.
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;
}
}
/**
* Prompt the user for a filename, and save the scribble in that file.
* Serialize the vector of lines with an ObjectOutputStream.
* Compress the serialized objects with a GZIPOutputStream.
* Write the compressed, serialized data to a file with a FileOutputStream.
* Don't forget to flush and close the stream.
*/
public void save() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Save Scribble", FileDialog.SAVE);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create the necessary output streams to save the scribble.
FileOutputStream fos = new FileOutputStream(filename); // Save to file
GZIPOutputStream gzos = new GZIPOutputStream(fos); // Compressed
ObjectOutputStream out = new ObjectOutputStream(gzos); // Save objects
out.writeObject(lines); // Write the entire Vector of scribbles
out.flush(); // Always flush the output.
out.close(); // And close the stream.
}
// Print out exceptions. We should really display them in a dialog...
catch (IOException e) { System.out.println(e); }
}
}
/**
* Prompt for a filename, and load a scribble from that file.
* Read compressed, serialized data with a FileInputStream.
* Uncompress that data with a GZIPInputStream.
* Deserialize the vector of lines with a ObjectInputStream.
* Replace current data with new data, and redraw everything.
*/
public void load() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Load Scribble", FileDialog.LOAD);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create necessary input streams
FileInputStream fis = new FileInputStream(filename); // Read from file
GZIPInputStream gzis = new GZIPInputStream(fis); // Uncompress
ObjectInputStream in = new ObjectInputStream(gzis); // Read objects
// Read in an object. It should be a vector of scribbles
Vector newlines = (Vector)in.readObject();
in.close(); // Close the stream.
lines = newlines; // Set the Vector of lines.
repaint(); // And redisplay the scribble.
}
// Print out exceptions. We should really display them in a dialog...
catch (Exception e) { System.out.println(e); }
}
}
/** A class to store the coordinates and color of one scribbled line.
* The complete scribble is stored as a Vector of these objects */
static class Line implements Serializable {
public short x1, y1, x2, y2;
public Color color;
public Line(short x1, short y1, short x2, short y2, Color c) {
this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.color = c;
}
}
}
|