diff --git a/.gitignore b/.gitignore
index 32858aa..c2e3b9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,7 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+#idea
+*.iml
+.idea
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE.html b/LICENSE.html
new file mode 100644
index 0000000..4199a66
--- /dev/null
+++ b/LICENSE.html
@@ -0,0 +1,92 @@
+
+
+Java Examples in a Nutshell: License
+
+
+
+
+
License for the Examples from the Book Java Examples
+in a Nutshell, 2nd Edition
+
+
+
0. Definitions
+
+For the purposes of this license, "the book" means the
+second edition of the book Java Examples in a
+Nutshell. "The examples" means the Java programs and
+other source code included in the book, and available for
+download from the website
+http://www.davidflanagan.com/javaexamples2
+
+
+
2. No Warranty
+
+The examples are provided as-is, with NO WARRANTY OF ANY KIND.
+The examples were written to teach Java programming concepts
+and techniques, and are not intended as production-quality
+software. In particular, they have not been carefully
+tested. Neither David Flanagan nor the publisher O'Reilly
+& Associates is responsible for any loss or damages of
+any kind resulting from the use of these examples.
+
+
3. Non-commercial Use
+
+You may study, use, modify, and distribute these examples
+for any non-commercial purpose, as long as the copyright
+notice at the top of each example is retained. You may use
+the examples in software you develop for personal use, in
+software used internally by non-profit institutions, and in
+software that is publicly released under an open-source
+license.
+
+
4. Educational Use
+
+If you are an educator at non-profit educational
+institution, you may use the examples for educational
+purposes in your courses. Please make the book a required
+or recommended text for the students in your course.
+
+If you are an educator at a for-profit educational
+institution or at an organization whose primary purpose is
+not education, you may also use the examples in your
+courses, but you must ensure that each of your students
+purchases or is given a copy of the book.
+
+
5. Commercial Use
+
+The examples were developed to demonstrate Java programming
+concepts and techniques. They are not intended as
+production code, and have not been through any kind of
+rigorous testing or validation. Nevertheless, if you find
+the code useful, and would like to use it commercially, you
+may purchase a commercial use license for a nominal fee.
+Visit http://www.davidflanagan.com/javaexamples2/ for
+information on obtaining such a license.
+
+Upon payment, a commercial use license entitles you to use
+the examples and modified versions of the examples in
+commercial software. "Commercial software" includes
+software written for sale and software written for internal
+use by a for-profit organization (including your own
+organization) It does not include software that you write
+solely for your personal use, software written for internal
+use by non-profit organizations, and software that is
+publicly released under an open-source license.
+
+Note that a commercial-use license does not entitle to you
+sell or otherwise commercially distribute the examples or
+modified versions of the examples as examples. You may only
+commercially distribute the examples when they are
+integrated into your commercial software.
+
+Note that purchase of a commercial-use license does not
+provide any kind of warranty, expressed or implied, for the
+examples
+
+
+
diff --git a/README.md b/README.md
index 6987d76..3b508db 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,9 @@
# java-examples
source code from book java examples in a nutshell
+
+# source README.MD
+
+These are the examples from the book Java Examples in a Nutshell, 2nd
+Edition, by David Flanagan.
+
+See the file index.html for more information.
diff --git a/cover.gif b/cover.gif
new file mode 100644
index 0000000..a2dd97a
Binary files /dev/null and b/cover.gif differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..42de93e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,263 @@
+
+
+Examples From Java Examples in a Nutshell
+
+
+
+
Examples From Java Examples in a Nutshell, 2nd Edition
+
+
+
+
+
+
+This directory contains the complete set of examples from the book
+Java Examples in a Nutshell, 2nd Edition.
+
+
Get the Book
+If you don't already have a copy of the book, you should get
+one: it provides the context and explanation necessary to
+get the most out of the examples.
+
+You can buy a copy of the book from:
+
+
+The Java examples are all defined in sub-packages of the
+package com.davidflanagan.examples. This means
+that you'll find the example source code in subdirectories
+of the directory
+com/davidflanagan/examples/
+
+In order to work most easily with the examples, you'll need
+to add the current directory (the directory that you're
+reading this file from) into your CLASSPATH environment
+variable.
+
+In the book, the title of each example gives its file
+name, and the package statement in each example
+shows what package it is a part of. With this information,
+you can easily find the source code for the example you
+want. For browsing convenience, however, the examples are
+also listed here by chapter, number, and name:
+
+
+19-1 ListServlets1.java
+19-2 ListServlets2.java
+19-3 WebAppConfig.java
+19-4 XMLDocumentWriter.java
+19-5 DOMTreeWalkerTreeModel.java
+19-6 WebAppConfig2.java
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..57b6b19
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,12 @@
+
+
+ 4.0.0
+
+ org.dean
+ java-examples
+ 1.0-SNAPSHOT
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/davidflanagan/examples/applet/Clock.html b/src/main/java/com/davidflanagan/examples/applet/Clock.html
new file mode 100644
index 0000000..031c3fe
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Clock.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/Clock.java b/src/main/java/com/davidflanagan/examples/applet/Clock.java
new file mode 100644
index 0000000..7aca69f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Clock.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*; // Don't forget this import statement!
+import java.awt.*; // Or this one for the graphics!
+import java.util.Date; // To obtain the current time
+import java.text.DateFormat; // For displaying the time
+
+/**
+ * This applet displays the time, and updates it every second
+ **/
+public class Clock extends Applet implements Runnable {
+ Label time; // A component to display the time in
+ DateFormat timeFormat; // This object converts the time to a string
+ Thread timer; // The thread that updates the time
+ volatile boolean running; // A flag used to stop the thread
+
+ /**
+ * The init method is called when the browser first starts the applet.
+ * It sets up the Label component and obtains a DateFormat object
+ **/
+ public void init() {
+ time = new Label();
+ time.setFont(new Font("helvetica", Font.BOLD, 12));
+ time.setAlignment(Label.CENTER);
+ setLayout(new BorderLayout());
+ add(time, BorderLayout.CENTER);
+ timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
+ }
+
+ /**
+ * This browser calls this method to tell the applet to start running.
+ * Here, we create and start a thread that will update the time each
+ * second. Note that we take care never to have more than one thread
+ **/
+ public void start() {
+ running = true; // Set the flag
+ if (timer == null) { // If we don't already have a thread
+ timer = new Thread(this); // Then create one
+ timer.start(); // And start it running
+ }
+ }
+
+ /**
+ * This method implements Runnable. It is the body of the thread. Once
+ * a second, it updates the text of the Label to display the current time
+ **/
+ public void run() {
+ while(running) { // Loop until we're stopped
+ // Get current time, convert to a String, and display in the Label
+ time.setText(timeFormat.format(new Date()));
+ // Now wait 1000 milliseconds
+ try { Thread.sleep(1000); }
+ catch (InterruptedException e) {}
+ }
+ // If the thread exits, set it to null so we can create a new one
+ // if start() is called again.
+ timer = null;
+ }
+
+ /**
+ * The browser calls this method to tell the applet that it is not visible
+ * and should not run. It sets a flag that tells the run() method to exit
+ **/
+ public void stop() { running = false; }
+
+ /**
+ * Returns information about the applet for display by the applet viewer
+ **/
+ public String getAppletInfo() {
+ return "Clock applet Copyright (c) 2000 by David Flanagan";
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html
new file mode 100644
index 0000000..8094414
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html
@@ -0,0 +1,6 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java
new file mode 100644
index 0000000..2a79064
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*;
+import java.awt.*;
+
+/**
+ * A version of the Scribble applet that reads two applet parameters
+ * to set the foreground and background colors. It also returns
+ * information about itself when queried.
+ **/
+public class ColorScribble extends Scribble {
+ // Read in two color parameters and set the colors.
+ public void init() {
+ super.init(); // Let the superclass initialize itself
+ Color foreground = getColorParameter("foreground");
+ Color background = getColorParameter("background");
+ if (foreground != null) this.setForeground(foreground);
+ if (background != null) this.setBackground(background);
+ }
+
+ // Read the specified parameter. Interpret it as a hexadecimal
+ // number of the form RRGGBB and convert it to a color.
+ protected Color getColorParameter(String name) {
+ String value = this.getParameter(name);
+ try { return new Color(Integer.parseInt(value, 16)); }
+ catch (Exception e) { return null; }
+ }
+
+ // Return information suitable for display in an About dialog box.
+ public String getAppletInfo() {
+ return "ColorScribble v. 0.03. Written by David Flanagan.";
+ }
+
+ // Return info about the supported parameters. Web browsers and applet
+ // viewers should display this information, and may also allow users to
+ // set the parameter values.
+ public String[][] getParameterInfo() { return info; }
+
+ // Here's the information that getParameterInfo() returns.
+ // It is an array of arrays of strings describing each parameter.
+ // Format: parameter name, parameter type, parameter description
+ private String[][] info = {
+ {"foreground", "hexadecimal color value", "foreground color"},
+ {"background", "hexadecimal color value", "background color"}
+ };
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/EventTester.html b/src/main/java/com/davidflanagan/examples/applet/EventTester.html
new file mode 100644
index 0000000..17e955f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/EventTester.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/EventTester.java b/src/main/java/com/davidflanagan/examples/applet/EventTester.java
new file mode 100644
index 0000000..c5dad0a
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/EventTester.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*;
+import java.awt.*;
+import java.util.*;
+
+/** An applet that gives details about Java 1.0 events */
+public class EventTester extends Applet {
+ // Handle mouse events
+ public boolean mouseDown(Event e, int x, int y) {
+ showLine(mods(e.modifiers) + "Mouse Down: [" + x + "," + y + "]");
+ return true;
+ }
+ public boolean mouseUp(Event e, int x, int y) {
+ showLine(mods(e.modifiers) + "Mouse Up: [" + x + "," + y + "]");
+ return true;
+ }
+ public boolean mouseDrag(Event e, int x, int y) {
+ showLine(mods(e.modifiers) + "Mouse Drag: [" + x + "," + y + "]");
+ return true;
+ }
+ public boolean mouseMove(Event e, int x, int y) {
+ showLine(mods(e.modifiers) + "Mouse Move: [" + x + "," + y + "]");
+ return true;
+ }
+ public boolean mouseEnter(Event e, int x, int y) {
+ showLine("Mouse Enter: [" + x + "," + y + "]"); return true;
+ }
+ public boolean mouseExit(Event e, int x, int y) {
+ showLine("Mouse Exit: [" + x + "," + y + "]"); return true;
+ }
+
+ // Handle focus events
+ public boolean gotFocus(Event e, Object what) {
+ showLine("Got Focus"); return true;
+ }
+ public boolean lostFocus(Event e, Object what) {
+ showLine("Lost Focus"); return true;
+ }
+
+ // Handle key down and key up events
+ // This gets more confusing because there are two types of key events
+ public boolean keyDown(Event e, int key) {
+ int flags = e.modifiers;
+ if (e.id == Event.KEY_PRESS) // a regular key
+ showLine("Key Down: " + mods(flags) + key_name(e));
+ else if (e.id == Event.KEY_ACTION) // a function key
+ showLine("Function Key Down: " + mods(flags) +
+ function_key_name(key));
+ return true;
+ }
+ public boolean keyUp(Event e, int key) {
+ int flags = e.modifiers;
+ if (e.id == Event.KEY_RELEASE) // a regular key
+ showLine("Key Up: " + mods(flags) + key_name(e));
+ else if (e.id == Event.KEY_ACTION_RELEASE) // a function key
+ showLine("Function Key Up: " + mods(flags) +
+ function_key_name(key));
+ return true;
+ }
+
+ // The remaining methods help us sort out the various modifiers and keys
+
+ // Return the current list of modifier keys
+ private String mods(int flags) {
+ String s = "[ ";
+ if (flags == 0) return "";
+ if ((flags & Event.SHIFT_MASK) != 0) s += "Shift ";
+ if ((flags & Event.CTRL_MASK) != 0) s += "Control ";
+ if ((flags & Event.META_MASK) != 0) s += "Meta ";
+ if ((flags & Event.ALT_MASK) != 0) s += "Alt ";
+ s += "] ";
+ return s;
+ }
+
+ // Return the name of a regular (non-function) key.
+ private String key_name(Event e) {
+ char c = (char) e.key;
+ if (e.controlDown()) { // If CTRL flag is set, handle control chars.
+ if (c < ' ') {
+ c += '@';
+ return "^" + c;
+ }
+ }
+ else { // If CTRL flag is not set, then certain ASCII
+ switch (c) { // control characters have special meaning.
+ case '\n': return "Return";
+ case '\t': return "Tab";
+ case '\033': return "Escape";
+ case '\010': return "Backspace";
+ }
+ }
+ // Handle the remaining possibilities.
+ if (c == '\177') return "Delete";
+ else if (c == ' ') return "Space";
+ else return String.valueOf(c);
+ }
+
+ // Return the name of a function key. Just compare the key to the
+ // constants defined in the Event class.
+ private String function_key_name(int key) {
+ switch(key) {
+ case Event.HOME: return "Home"; case Event.END: return "End";
+ case Event.PGUP: return "Page Up"; case Event.PGDN: return"Page Down";
+ case Event.UP: return "Up"; case Event.DOWN: return "Down";
+ case Event.LEFT: return "Left"; case Event.RIGHT: return "Right";
+ case Event.F1: return "F1"; case Event.F2: return "F2";
+ case Event.F3: return "F3"; case Event.F4: return "F4";
+ case Event.F5: return "F5"; case Event.F6: return "F6";
+ case Event.F7: return "F7"; case Event.F8: return "F8";
+ case Event.F9: return "F9"; case Event.F10: return "F10";
+ case Event.F11: return "F11"; case Event.F12: return "F12";
+ }
+ return "Unknown Function Key";
+ }
+
+ /** A list of lines to display in the window */
+ protected Vector lines = new Vector();
+ /** Add a new line to the list of lines, and redisplay */
+ protected void showLine(String s) {
+ if (lines.size() == 20) lines.removeElementAt(0);
+ lines.addElement(s);
+ repaint();
+ }
+ /** This method repaints the text in the window */
+ public void paint(Graphics g) {
+ for(int i = 0; i < lines.size(); i++)
+ g.drawString((String)lines.elementAt(i), 20, i*16 + 50);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html
new file mode 100644
index 0000000..68b11e1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java
new file mode 100644
index 0000000..9910a63
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*; // Don't forget this import statement!
+import java.awt.*; // Or this one for the graphics!
+
+/** This applet just says "Hello World! */
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/Scribble.html b/src/main/java/com/davidflanagan/examples/applet/Scribble.html
new file mode 100644
index 0000000..106f53e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Scribble.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/Scribble.java b/src/main/java/com/davidflanagan/examples/applet/Scribble.java
new file mode 100644
index 0000000..d6c8d00
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Scribble.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*;
+import java.awt.*;
+
+/**
+ * This applet lets the user scribble with the mouse.
+ * It demonstrates the Java 1.0 event model.
+ **/
+public class Scribble extends Applet {
+ private int lastx, lasty; // Remember last mouse coordinates.
+ Button erase_button; // The Erase button.
+
+ /** Initialize the erase button, ask for keyboard focus */
+ public void init() {
+ erase_button = new Button("Erase");
+ this.add(erase_button);
+ this.setBackground(Color.white); // Set background color for scribble
+ this.requestFocus(); // Ask for keyboard focus so we get key events
+ }
+
+ /** Respond to mouse clicks */
+ public boolean mouseDown(Event e, int x, int y) {
+ lastx = x; lasty = y; // Remember where the click was
+ return true;
+ }
+
+ /** Respond to mouse drags */
+ public boolean mouseDrag(Event e, int x, int y) {
+ Graphics g = getGraphics();
+ g.drawLine(lastx, lasty, x, y); // Draw from last position to here
+ lastx = x; lasty = y; // And remember new last position
+ return true;
+ }
+
+ /** Respond to key presses: Erase drawing when user types 'e' */
+ public boolean keyDown(Event e, int key) {
+ if ((e.id == Event.KEY_PRESS) && (key == 'e')) {
+ Graphics g = getGraphics();
+ g.setColor(this.getBackground());
+ g.fillRect(0, 0, bounds().width, bounds().height);
+ return true;
+ }
+ else return false;
+ }
+
+ /** Respond to Button clicks: erase drawing when user clicks button */
+ public boolean action(Event e, Object arg) {
+ if (e.target == erase_button) {
+ Graphics g = getGraphics();
+ g.setColor(this.getBackground());
+ g.fillRect(0, 0, bounds().width, bounds().height);
+ return true;
+ }
+ else return false;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/Soundmap.html b/src/main/java/com/davidflanagan/examples/applet/Soundmap.html
new file mode 100644
index 0000000..f2de8f9
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Soundmap.html
@@ -0,0 +1,119 @@
+
+
+The Soundmap Applet
+
+
+Clicking one one of the buttons will make your browser
+jump to the appropriate section of the table of contents below.
+Note the use of the status line.
+
+
+
diff --git a/src/main/java/com/davidflanagan/examples/applet/Soundmap.java b/src/main/java/com/davidflanagan/examples/applet/Soundmap.java
new file mode 100644
index 0000000..bfa463c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/applet/Soundmap.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.applet;
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * A Java applet that simulates a client-side imagemap.
+ * Plays a sound whenever the user clicks on one of the hyperlinks.
+ */
+public class Soundmap extends Applet implements MouseListener {
+ protected Image image; // The image to display.
+ protected Vector rects; // A list of rectangles in it.
+ protected AudioClip sound; // A sound to play on user clicks in a rectangle
+ protected ImagemapRectangle highlight; // Which rectangle is highlighted
+
+ /** Initialize the applet */
+ public void init() {
+ // Look up the name of the image, relative to a base URL, and load it.
+ // Note the use of three Applet methods in this one line.
+ image = this.getImage(this.getDocumentBase(),
+ this.getParameter("image"));
+
+ // Lookup and parse a list of rectangular areas and their URLs.
+ // The convenience routine getRectangleParameter() is defined below.
+ rects = new Vector();
+ ImagemapRectangle r;
+ for(int i = 0; (r = getRectangleParameter("rect" + i)) != null; i++)
+ rects.addElement(r);
+
+ // Look up a sound to play when the user clicks one of those areas.
+ sound = this.getAudioClip(this.getDocumentBase(),
+ this.getParameter("sound"));
+
+ // Specify an "event listener" object to respond to mouse button
+ // presses and releases. Note that this is the Java 1.1 event model.
+ this.addMouseListener(this);
+ }
+
+ /**
+ * Called when the applet is being unloaded from the system.
+ * We use it here to "flush" the image we no longer need. This may
+ * result in memory and other resources being freed more quickly.
+ **/
+ public void destroy() { image.flush(); }
+
+ /**
+ * To display the applet, we simply draw the image, and highlight the
+ * current rectangle if any.
+ **/
+ public void paint(Graphics g) {
+ g.drawImage(image, 0, 0, this);
+ if (highlight != null) {
+ g.setColor(Color.red);
+ g.drawRect(highlight.x, highlight.y,
+ highlight.width, highlight.height);
+ g.drawRect(highlight.x+1, highlight.y+1,
+ highlight.width-2, highlight.height-2);
+ }
+ }
+
+ /**
+ * We override this method so that it doesn't clear the background
+ * before calling paint(). No clear is necessary, since paint() overwrites
+ * everything with an image. Causes less flickering this way.
+ **/
+ public void update(Graphics g) { paint(g); }
+
+ /**
+ * Parse a comma-separated list of rectangle coordinates and a URL.
+ * Used to read the imagemap rectangle definitions from applet parameters
+ **/
+ protected ImagemapRectangle getRectangleParameter(String name) {
+ int x, y, w, h;
+ URL url;
+ String value = this.getParameter(name);
+ if (value == null) return null;
+
+ try {
+ StringTokenizer st = new StringTokenizer(value, ",");
+ x = Integer.parseInt(st.nextToken());
+ y = Integer.parseInt(st.nextToken());
+ w = Integer.parseInt(st.nextToken());
+ h = Integer.parseInt(st.nextToken());
+ url = new URL(this.getDocumentBase(), st.nextToken());
+ }
+ catch (NoSuchElementException e) { return null; }
+ catch (NumberFormatException e) { return null; }
+ catch (MalformedURLException e) { return null; }
+
+ return new ImagemapRectangle(x, y, w, h, url);
+ }
+
+ /** Called when a mouse button is pressed. */
+ public void mousePressed(MouseEvent e) {
+ // On button down, check if we're inside one of the rectangles.
+ // If so, highlight the rectangle, display a message, and play a sound.
+ // The utility routine findrect() is defined below.
+ ImagemapRectangle r = findrect(e);
+ // If a rectangle is found, and is not already highlighted
+ if (r != null && r != highlight) {
+ highlight = r; // Remember which rectangle it is
+ showStatus("To: " + r.url); // display its URL in status line
+ sound.play(); // play the sound
+ repaint(); // request a redraw to highlight it
+ }
+ }
+
+ /** Called when a mouse button is released. */
+ public void mouseReleased(MouseEvent e) {
+ // If the user releases the mouse button over a highlighted
+ // rectangle, tell the browser to display its URL. Also,
+ // erase the highlight and clear status
+ if (highlight != null) {
+ ImagemapRectangle r = findrect(e);
+ if (r == highlight) getAppletContext().showDocument(r.url);
+ showStatus(""); // clear the message.
+ highlight = null; // forget the highlight
+ repaint(); // request a redraw
+ }
+ }
+
+ /** Unused methods of the MouseListener interface */
+ public void mouseEntered(MouseEvent e) {}
+ public void mouseExited(MouseEvent e) {}
+ public void mouseClicked(MouseEvent e) {}
+
+ /** Find the rectangle we're inside. */
+ protected ImagemapRectangle findrect(MouseEvent e) {
+ int i, x = e.getX(), y = e.getY();
+ for(i = 0; i < rects.size(); i++) {
+ ImagemapRectangle r = (ImagemapRectangle) rects.elementAt(i);
+ if (r.contains(x, y)) return r;
+ }
+ return null;
+ }
+
+ /**
+ * A helper class. Just like java.awt.Rectangle, but with a URL field.
+ * Note the use of a nested toplevel class for neatness.
+ **/
+ static class ImagemapRectangle extends Rectangle {
+ URL url;
+ public ImagemapRectangle(int x, int y, int w, int h, URL url) {
+ super(x, y, w, h);
+ this.url = url;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/applet/chirp.au b/src/main/java/com/davidflanagan/examples/applet/chirp.au
new file mode 100644
index 0000000..330cb54
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/applet/chirp.au differ
diff --git a/src/main/java/com/davidflanagan/examples/applet/java_parts.gif b/src/main/java/com/davidflanagan/examples/applet/java_parts.gif
new file mode 100644
index 0000000..8483c3f
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/applet/java_parts.gif differ
diff --git a/src/main/java/com/davidflanagan/examples/basics/Echo.java b/src/main/java/com/davidflanagan/examples/basics/Echo.java
new file mode 100644
index 0000000..231fcbe
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Echo.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This program prints out all its command-line arguments.
+ **/
+public class Echo {
+ public static void main(String[] args) {
+ int i = 0; // Initialize the loop variable
+ while(i < args.length) { // Loop until the end of array
+ System.out.print(args[i] + " "); // Print each argument out
+ i++; // Increment the loop variable
+ }
+ System.out.println(); // Terminate the line
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/FactComputer.java b/src/main/java/com/davidflanagan/examples/basics/FactComputer.java
new file mode 100644
index 0000000..ca2d36e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/FactComputer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This program computes and displays the factorial of a number specified
+ * on the command line. It handles possible user input errors with try/catch.
+ **/
+public class FactComputer {
+ public static void main(String[] args) {
+ // Try to compute a factorial.
+ // If something goes wrong, handle it in the catch clause below.
+ try {
+ int x = Integer.parseInt(args[0]);
+ System.out.println(x + "! = " + Factorial4.factorial(x));
+ }
+ // The user forgot to specify an argument.
+ // Thrown if args[0] is undefined.
+ catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("You must specify an argument");
+ System.out.println("Usage: java FactComputer ");
+ }
+ // The argument is not a number. Thrown by Integer.parseInt().
+ catch (NumberFormatException e) {
+ System.out.println("The argument you specify must be an integer");
+ }
+ // The argument is < 0. Thrown by Factorial4.factorial()
+ catch (IllegalArgumentException e) {
+ // Display the message sent by the factorial() method:
+ System.out.println("Bad argument: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java b/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java
new file mode 100644
index 0000000..a0a1d17
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+import java.io.*; // Import all classes in java.io package. Saves typing.
+
+/**
+ * This program displays factorials as the user enters values interactively
+ **/
+public class FactQuoter {
+ public static void main(String[] args) throws IOException {
+ // This is how we set things up to read lines of text from the user.
+ BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
+ // Loop forever
+ for(;;) {
+ // Display a prompt to the user
+ System.out.print("FactQuoter> ");
+ // Read a line from the user
+ String line = in.readLine();
+ // If we reach the end-of-file,
+ // or if the user types "quit", then quit
+ if ((line == null) || line.equals("quit")) break;
+ // Try to parse the line, and compute and print the factorial
+ try {
+ int x = Integer.parseInt(line);
+ System.out.println(x + "! = " + Factorial4.factorial(x));
+ }
+ // If anything goes wrong, display a generic error message
+ catch(Exception e) { System.out.println("Invalid Input"); }
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial.java b/src/main/java/com/davidflanagan/examples/basics/Factorial.java
new file mode 100644
index 0000000..a3e4a04
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Factorial.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+/**
+ * This class doesn't define a main() method, so it isn't a program by itself.
+ * It does define a useful method that we can use in other programs, though.
+ **/
+public class Factorial {
+ /** Compute and return x!, the factorial of x */
+ public static int factorial(int x) {
+ if (x < 0) throw new IllegalArgumentException("x must be >= 0");
+ int fact = 1;
+ for(int i = 2; i <= x; i++) // loop
+ fact *= i; // shorthand for: fact = fact * i;
+ return fact;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial2.java b/src/main/java/com/davidflanagan/examples/basics/Factorial2.java
new file mode 100644
index 0000000..d0dac0d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Factorial2.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+/**
+ * This class shows a recursive method to compute factorials. This method
+ * calls itself repeatedly based on the formula: n! = n * (n-1)!
+ **/
+public class Factorial2 {
+ public static long factorial(long x) {
+ if (x < 0) throw new IllegalArgumentException("x must be >= 0");
+ if (x <= 1) return 1; // Stop recursing here
+ else return x * factorial(x-1); // Recurse by calling ourselves
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial3.java b/src/main/java/com/davidflanagan/examples/basics/Factorial3.java
new file mode 100644
index 0000000..c3992af
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Factorial3.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This class computes factorials and caches the results in a table for reuse.
+ * 20! is as high as we can go using the long data type, so check the argument
+ * passed and "throw an exception" if it is too big or too small.
+ **/
+public class Factorial3 {
+ // Create an array to cache values 0! through 20!.
+ static long[] table = new long[21];
+ // A "static initializer": initialize the first value in the array
+ static { table[0] = 1; } // factorial of 0 is 1.
+ // Remember the highest initialized value in the array
+ static int last = 0;
+
+ public static long factorial(int x) throws IllegalArgumentException {
+ // Check if x is too big or too small. Throw an exception if so.
+ if (x >= table.length) // ".length" returns length of any array
+ throw new IllegalArgumentException("Overflow; x is too large.");
+ if (x<0) throw new IllegalArgumentException("x must be non-negative.");
+
+ // Compute and cache any values that are not yet cached.
+ while(last < x) {
+ table[last + 1] = table[last] * (last + 1);
+ last++;
+ }
+ // Now return the cached factorial of x.
+ return table[x];
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial4.java b/src/main/java/com/davidflanagan/examples/basics/Factorial4.java
new file mode 100644
index 0000000..5caa347
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Factorial4.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+// Import some other classes we'll use in this example.
+// Once we import a class, we don't have to type its full name.
+import java.math.BigInteger; // Import BigInteger from java.math package
+import java.util.*; // Import all classes (including ArrayList) from java.util
+
+/**
+ * This version of the program uses arbitrary precision integers, so it does
+ * not have an upper-bound on the values it can compute. It uses an ArrayList
+ * object to cache computed values instead of a fixed-size array. An ArrayList
+ * is like an array, but can grow to any size. The factorial() method is
+ * declared "synchronized" so that it can be safely used in multi-threaded
+ * programs. Look up java.math.BigInteger and java.util.ArrayList while
+ * studying this class. Prior to Java 1.2, use Vector instead of ArrayList
+ **/
+public class Factorial4 {
+ protected static ArrayList table = new ArrayList(); // create cache
+ static { // Initialize the first element of the cache with !0 = 1.
+ table.add(BigInteger.valueOf(1));
+ }
+
+ /** The factorial() method, using BigIntegers cached in a ArrayList */
+ public static synchronized BigInteger factorial(int x) {
+ if (x<0) throw new IllegalArgumentException("x must be non-negative.");
+ for(int size = table.size(); size <= x; size++) {
+ BigInteger lastfact = (BigInteger)table.get(size-1);
+ BigInteger nextfact = lastfact.multiply(BigInteger.valueOf(size));
+ table.add(nextfact);
+ }
+ return (BigInteger) table.get(x);
+ }
+
+ /**
+ * A simple main() method that we can use as a standalone test program
+ * for our factorial() method.
+ **/
+ public static void main(String[] args) {
+ for(int i = 0; i <= 50; i++)
+ System.out.println(i + "! = " + factorial(i));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java b/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java
new file mode 100644
index 0000000..decd319
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+/**
+ * This program prints out the first 20 numbers in the Fibonacci sequence.
+ * Each term is formed by adding together the previous two terms in the
+ * sequence, starting with the terms 1 and 1.
+ **/
+public class Fibonacci {
+ public static void main(String[] args) {
+ int n0 = 1, n1 = 1, n2; // Initialize variables
+ System.out.print(n0 + " " + // Print first and second terms
+ n1 + " "); // of the series
+
+ for(int i = 0; i < 18; i++) { // Loop for the next 18 terms
+ n2 = n1 + n0; // Next term is sum of previous two
+ System.out.print(n2 + " "); // Print it out
+ n0 = n1; // First previous becomes 2nd previous
+ n1 = n2; // And current number becomes previous
+ }
+ System.out.println(); // Terminate the line
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java
new file mode 100644
index 0000000..8317ecf
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This program plays the game "Fizzbuzz". It counts to 100, replacing each
+ * multiple of 5 with the word "fizz", each multiple of 7 with the word "buzz",
+ * and each multiple of both with the word "fizzbuzz". It uses the modulo
+ * operator (%) to determine if a number is divisible by another.
+ **/
+public class FizzBuzz { // Everything in Java is a class
+ public static void main(String[] args) { // Every program must have main()
+ for(int i = 1; i <= 100; i++) { // count from 1 to 100
+ if (((i % 5) == 0)&& ((i % 7) == 0)) // Is it a multiple of 5 & 7?
+ System.out.print("fizzbuzz");
+ else if ((i % 5) == 0) // Is it a multiple of 5?
+ System.out.print("fizz");
+ else if ((i % 7) == 0) // Is it a multiple of 7?
+ System.out.print("buzz");
+ else System.out.print(i); // Not a multiple of 5 or 7
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java
new file mode 100644
index 0000000..4fe2dc4
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This class is much like the FizzBuzz class, but uses a switch statement
+ * instead of repeated if/else statements
+ **/
+public class FizzBuzz2 {
+ public static void main(String[] args) {
+ for(int i = 1; i <= 100; i++) { // count from 1 to 100
+ switch(i % 35) { // What's the remainder when divided by 35?
+ case 0: // For multiples of 35...
+ System.out.print("fizzbuzz "); // print "fizzbuzz".
+ break; // Don't forget this statement!
+ case 5: case 10: case 15: // If the remainder is any of these
+ case 20: case 25: case 30: // then the number is a multiple of 5
+ System.out.print("fizz "); // so print "fizz".
+ break;
+ case 7: case 14: case 21: case 28: // For any multiple of 7...
+ System.out.print("buzz "); // print "buzz".
+ break;
+ default: // For any other number...
+ System.out.print(i + " "); // print the number.
+ break;
+ }
+ }
+ System.out.println();
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Hello.java b/src/main/java/com/davidflanagan/examples/basics/Hello.java
new file mode 100644
index 0000000..1b9570b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Hello.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics; // A unique class name prefix
+public class Hello { // Everything in Java is a class
+ public static void main(String[] args) { // All programs must have main()
+ System.out.println("Hello World!"); // Say hello!
+ } // This marks the end of main()
+} // Marks the end of the class
diff --git a/src/main/java/com/davidflanagan/examples/basics/Reverse.java b/src/main/java/com/davidflanagan/examples/basics/Reverse.java
new file mode 100644
index 0000000..40aebd3
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Reverse.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This program echos the command-line arguments backwards.
+ **/
+public class Reverse {
+ public static void main(String[] args) {
+ // Loop backwards through the array of arguments
+ for(int i = args.length-1; i >= 0; i--) {
+ // Loop backwards through the characters in each argument
+ for(int j=args[i].length()-1; j>=0; j--) {
+ // Print out character j of argument i.
+ System.out.print(args[i].charAt(j));
+ }
+ System.out.print(" "); // Add a space at the end of each argument.
+ }
+ System.out.println(); // And terminate the line when we're done.
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java b/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java
new file mode 100644
index 0000000..ddeb720
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+import java.io.*; // We're doing input, so import I/O classes
+
+/**
+ * This program reads lines of text from the user, encodes them using the
+ * trivial "Rot13" substitution cipher, and then prints out the encoded lines.
+ **/
+public class Rot13Input {
+ public static void main(String[] args) throws IOException {
+ // Get set up to read lines of text from the user
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+ for(;;) { // Loop forever
+ System.out.print("> "); // Print a prompt
+ String line = in.readLine(); // Read a line
+ if ((line == null) || line.equals("quit")) // If EOF or "quit"...
+ break; // ...break out of loop
+ StringBuffer buf = new StringBuffer(line); // Use a StringBuffer
+ for(int i = 0; i < buf.length(); i++) // For each character...
+ buf.setCharAt(i, rot13(buf.charAt(i)));// ..read, encode, store
+ System.out.println(buf); // Print encoded line
+ }
+ }
+
+ /**
+ * This method performs the Rot13 substitution cipher. It "rotates"
+ * each letter 13 places through the alphabet. Since the Latin alphabet
+ * has 26 letters, this method both encodes and decodes.
+ **/
+ public static char rot13(char c) {
+ if ((c >= 'A') && (c <= 'Z')) { // For uppercase letters
+ c += 13; // Rotate forward 13
+ if (c > 'Z') c -= 26; // And subtract 26 if necessary
+ }
+ if ((c >= 'a') && (c <= 'z')) { // Do the same for lowercase letters
+ c += 13;
+ if (c > 'z') c -= 26;
+ }
+ return c; // Return the modified letter
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/Sieve.java b/src/main/java/com/davidflanagan/examples/basics/Sieve.java
new file mode 100644
index 0000000..b2feb3b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/Sieve.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This program computes prime numbers using the Sieve of Eratosthenes
+ * algorithm: rule out multiples of all lower prime numbers, and anything
+ * remaining is a prime. It prints out the largest prime number less than
+ * or equal to the supplied command-line argument.
+ **/
+public class Sieve {
+ public static void main(String[] args) {
+ // We will compute all primes less than the value specified on the
+ // command line, or, if no argument, all primes less than 100.
+ int max = 100; // Assign a default value
+ try { max = Integer.parseInt(args[0]); } // Parse user-supplied arg
+ catch (Exception e) {} // Silently ignore exceptions.
+
+ // Create an array that specifies whether each number is prime or not.
+ boolean[] isprime = new boolean[max+1];
+
+ // Assume that all numbers are primes, until proven otherwise.
+ for(int i = 0; i <= max; i++) isprime[i] = true;
+
+ // However, we know that 0 and 1 are not primes. Make a note of it.
+ isprime[0] = isprime[1] = false;
+
+ // To compute all primes less than max, we need to rule out
+ // multiples of all integers less than the square root of max.
+ int n = (int) Math.ceil(Math.sqrt(max)); // See java.lang.Math class
+
+ // Now, for each integer i from 0 to n:
+ // If i is a prime, then none of its multiples are primes,
+ // so indicate this in the array. If i is not a prime, then
+ // its multiples have already been ruled out by one of the
+ // prime factors of i, so we can skip this case.
+ for(int i = 0; i <= n; i++) {
+ if (isprime[i]) // If i is a prime,
+ for(int j = 2*i; j <= max; j = j + i) // loop through multiples
+ isprime[j] = false; // they are not prime.
+ }
+
+ // Now go look for the largest prime:
+ int largest;
+ for(largest = max; !isprime[largest]; largest--) ; // empty loop body
+
+ // Output the result
+ System.out.println("The largest prime less than or equal to " + max +
+ " is " + largest);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java b/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java
new file mode 100644
index 0000000..685534d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.basics;
+
+/**
+ * This class demonstrates how to sort numbers using a simple algorithm
+ **/
+public class SortNumbers {
+ /**
+ * This is a very simple sorting algorithm that is not very efficient
+ * when sorting large numbers of things
+ **/
+ public static void sort(double[] nums) {
+ // Loop through each element of the array, sorting as we go.
+ // Each time through, find the smallest remaining element, and move it
+ // to the first unsorted position in the array.
+ for(int i = 0; i < nums.length; i++) {
+ int min = i; // holds the index of the smallest element
+ // find the smallest one between i and the end of the array
+ for(int j = i; j < nums.length; j++) {
+ if (nums[j] < nums[min]) min = j;
+ }
+ // Now swap the smallest one with element i.
+ // This leaves all elements between 0 and i sorted.
+ double tmp;
+ tmp = nums[i];
+ nums[i] = nums[min];
+ nums[min] = tmp;
+ }
+ }
+
+ /** This is a simple test program for the algorithm above */
+ public static void main(String[] args) {
+ double[] nums = new double[10]; // Create an array to hold numbers
+ for(int i = 0; i < nums.length; i++) // Generate random numbers
+ nums[i] = Math.random() * 100;
+ sort(nums); // Sort them
+ for(int i = 0; i < nums.length; i++) // Print them out
+ System.out.println(nums[i]);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/Alignment.java b/src/main/java/com/davidflanagan/examples/beans/Alignment.java
new file mode 100644
index 0000000..72b8be0
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/Alignment.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+
+/** This class defines an enumerated type with three values */
+public class Alignment {
+ /** This private constructor prevents anyone from instantiating us */
+ private Alignment() {};
+ // The following three constants are the only instances of this class
+ public static final Alignment LEFT = new Alignment();
+ public static final Alignment CENTER = new Alignment();
+ public static final Alignment RIGHT = new Alignment();
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java b/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java
new file mode 100644
index 0000000..6fac3b5
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.beans.*;
+import java.awt.*;
+
+/**
+ * This PropertyEditor defines the enumerated values of the alignment property
+ * so that a bean box or IDE can present those values to the user for selection
+ **/
+public class AlignmentEditor extends PropertyEditorSupport {
+ /** Return the list of value names for the enumerated type. */
+ public String[] getTags() {
+ return new String[] { "left", "center", "right" };
+ }
+
+ /** Convert each of those value names into the actual value. */
+ public void setAsText(String s) {
+ if (s.equals("left")) setValue(Alignment.LEFT);
+ else if (s.equals("center")) setValue(Alignment.CENTER);
+ else if (s.equals("right")) setValue(Alignment.RIGHT);
+ else throw new IllegalArgumentException(s);
+ }
+
+ /** This is an important method for code generation. */
+ public String getJavaInitializationString() {
+ Object o = getValue();
+ if (o == Alignment.LEFT)
+ return "com.davidflanagan.examples.beans.Alignment.LEFT";
+ if (o == Alignment.CENTER)
+ return "com.davidflanagan.examples.beans.Alignment.CENTER";
+ if (o == Alignment.RIGHT)
+ return "com.davidflanagan.examples.beans.Alignment.RIGHT";
+ return null;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java b/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java
new file mode 100644
index 0000000..389efe7
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+
+/**
+ * The YesNoPanel class fires an event of this type when the user clicks one
+ * of its buttons. The id field specifies which button the user pressed.
+ **/
+public class AnswerEvent extends java.util.EventObject {
+ public static final int YES = 0, NO = 1, CANCEL = 2; // Button constants
+ protected int id; // Which button was pressed?
+ public AnswerEvent(Object source, int id) {
+ super(source);
+ this.id = id;
+ }
+ public int getID() { return id; } // Return the button
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java b/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java
new file mode 100644
index 0000000..154cddd
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+
+/**
+ * Classes that want to be notified when the user clicks a button in a
+ * YesNoPanel should implement this interface. The method invoked depends
+ * on which button the user clicked.
+ **/
+public interface AnswerListener extends java.util.EventListener {
+ public void yes(AnswerEvent e);
+ public void no(AnswerEvent e);
+ public void cancel(AnswerEvent e);
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java b/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java
new file mode 100644
index 0000000..beb0291
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.awt.*;
+import java.util.*;
+
+/**
+ * A custom component that displays multiple lines of text with specified
+ * margins and alignment. In Java 1.1 we could also subclass Component,
+ * making this a "lightweight" component. Instead, we try to maintain
+ * Java 1.0 compatibility for this component. This means that you will see
+ * deprecation warnings when you compile this class with Java 1.1 or later.
+ **/
+public class MultiLineLabel extends Canvas {
+ // User-specified properties
+ protected String label; // The label, not broken into lines
+ protected int margin_width; // Left and right margins
+ protected int margin_height; // Top and bottom margins
+ protected Alignment alignment; // The alignment of the text.
+
+ // Computed state values
+ protected int num_lines; // The number of lines
+ protected String[] lines; // The label, broken into lines
+ protected int[] line_widths; // How wide each line is
+ protected int max_width; // The width of the widest line
+ protected int line_height; // Total height of the font
+ protected int line_ascent; // Font height above baseline
+ protected boolean measured = false; // Have the lines been measured?
+
+ // Here are five versions of the constructor.
+ public MultiLineLabel(String label, int margin_width,
+ int margin_height, Alignment alignment) {
+ this.label = label; // Remember all the properties.
+ this.margin_width = margin_width;
+ this.margin_height = margin_height;
+ this.alignment = alignment;
+ newLabel(); // Break the label up into lines.
+ }
+
+ public MultiLineLabel(String label, int margin_width, int margin_height) {
+ this(label, margin_width, margin_height, Alignment.LEFT);
+ }
+
+ public MultiLineLabel(String label, Alignment alignment) {
+ this(label, 10, 10, alignment);
+ }
+
+ public MultiLineLabel(String label) { this(label, 10, 10, Alignment.LEFT);}
+
+ public MultiLineLabel() { this(""); }
+
+ // Methods to set and query the various attributes of the component.
+ // Note that some query methods are inherited from the superclass.
+ public void setLabel(String label) {
+ this.label = label;
+ newLabel(); // Break the label into lines.
+ measured = false; // Note that we need to measure lines.
+ repaint(); // Request a redraw.
+ }
+
+ public void setFont(Font f) {
+ super.setFont(f); // Tell our superclass about the new font.
+ measured = false; // Note that we need to remeasure lines.
+ repaint(); // Request a redraw.
+ }
+
+ public void setForeground(Color c) {
+ super.setForeground(c); // Tell our superclass about the new color.
+ repaint(); // Request a redraw (size is unchanged).
+ }
+
+ public void setAlignment(Alignment a) { alignment = a; repaint(); }
+ public void setMarginWidth(int mw) { margin_width = mw; repaint(); }
+ public void setMarginHeight(int mh) { margin_height = mh; repaint(); }
+
+ // Property getter methods. Note that getFont(), getForeground(), etc.
+ // are inherited from the superclass.
+ public String getLabel() { return label; }
+ public Alignment getAlignment() { return alignment; }
+ public int getMarginWidth() { return margin_width; }
+ public int getMarginHeight() { return margin_height; }
+
+ /**
+ * This method is called by a layout manager when it wants to
+ * know how big we'd like to be. In Java 1.1, getPreferredSize() is
+ * the preferred version of this method. We use this deprecated version
+ * so that this component can interoperate with 1.0 components.
+ */
+ public Dimension preferredSize() {
+ if (!measured) measure();
+ return new Dimension(max_width + 2*margin_width,
+ num_lines * line_height + 2*margin_height);
+ }
+
+ /**
+ * This method is called when the layout manager wants to know
+ * the bare minimum amount of space we need to get by.
+ * For Java 1.1, we'd use getMinimumSize().
+ */
+ public Dimension minimumSize() { return preferredSize(); }
+
+ /**
+ * This method draws the component.
+ * Note that it handles the margins and the alignment, but that
+ * it doesn't have to worry about the color or font--the superclass
+ * takes care of setting those in the Graphics object we're passed.
+ **/
+ public void paint(Graphics g) {
+ int x, y;
+ Dimension size = this.size(); // use getSize() in Java 1.1
+ if (!measured) measure();
+ y = line_ascent + (size.height - num_lines * line_height)/2;
+ for(int i = 0; i < num_lines; i++, y += line_height) {
+ if (alignment == Alignment.LEFT) x = margin_width;
+ else if (alignment == Alignment.CENTER)
+ x = (size.width - line_widths[i])/2;
+ else x = size.width - margin_width - line_widths[i];
+ g.drawString(lines[i], x, y);
+ }
+ }
+
+ /**
+ * This internal method breaks a specified label up into an array of lines.
+ * It uses the StringTokenizer utility class.
+ **/
+ protected synchronized void newLabel() {
+ StringTokenizer t = new StringTokenizer(label, "\n");
+ num_lines = t.countTokens();
+ lines = new String[num_lines];
+ line_widths = new int[num_lines];
+ for(int i = 0; i < num_lines; i++) lines[i] = t.nextToken();
+ }
+
+ /**
+ * This internal method figures out how the font is, and how wide each
+ * line of the label is, and how wide the widest line is.
+ **/
+ protected synchronized void measure() {
+ FontMetrics fm = this.getToolkit().getFontMetrics(this.getFont());
+ line_height = fm.getHeight();
+ line_ascent = fm.getAscent();
+ max_width = 0;
+ for(int i = 0; i < num_lines; i++) {
+ line_widths[i] = fm.stringWidth(lines[i]);
+ if (line_widths[i] > max_width) max_width = line_widths[i];
+ }
+ measured = true;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java
new file mode 100644
index 0000000..74564b2
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+/**
+ * This JavaBean displays a multi-line message and up to three buttons. It
+ * fires an AnswerEvent when the user clicks on one of the buttons
+ **/
+public class YesNoPanel extends Panel {
+ // Properties of the bean.
+ protected String messageText; // The message to display
+ protected Alignment alignment; // The alignment of the message
+ protected String yesLabel; // Text for the yes, no, & cancel buttons
+ protected String noLabel;
+ protected String cancelLabel;
+
+ // Internal components of the panel
+ protected MultiLineLabel message;
+ protected Button yes, no, cancel;
+
+ /** The no-argument bean constructor, with default property values */
+ public YesNoPanel() { this("Your\nMessage\nHere"); }
+
+ public YesNoPanel(String messageText) {
+ this(messageText, Alignment.LEFT, "Yes", "No", "Cancel");
+ }
+
+ /** A constructor for programmers using this class "by hand" */
+ public YesNoPanel(String messageText, Alignment alignment,
+ String yesLabel, String noLabel, String cancelLabel)
+ {
+ // Create the components for this panel
+ setLayout(new BorderLayout(15, 15));
+
+ // Put the message label in the middle of the window.
+ message = new MultiLineLabel(messageText, 20, 20, alignment);
+ add(message, BorderLayout.CENTER);
+
+ // Create a panel for the Panel buttons and put it at the bottom
+ // of the Panel. Specify a FlowLayout layout manager for it.
+ Panel buttonbox = new Panel();
+ buttonbox.setLayout(new FlowLayout(FlowLayout.CENTER, 25, 15));
+ add(buttonbox, BorderLayout.SOUTH);
+
+ // Create each specified button, specifying the action listener
+ // and action command for each, and adding them to the buttonbox
+ yes = new Button(); // Create buttons
+ no = new Button();
+ cancel = new Button();
+ // Add the buttons to the button box
+ buttonbox.add(yes);
+ buttonbox.add(no);
+ buttonbox.add(cancel);
+
+ // Register listeners for each button
+ yes.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fireEvent(new AnswerEvent(YesNoPanel.this,
+ AnswerEvent.YES));
+ }
+ });
+
+ no.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fireEvent(new AnswerEvent(YesNoPanel.this,
+ AnswerEvent.NO));
+ }
+ });
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ fireEvent(new AnswerEvent(YesNoPanel.this,
+ AnswerEvent.CANCEL));
+ }
+ });
+
+ // Now call property setter methods to set the message and button
+ // components to contain the right text
+ setMessageText(messageText);
+ setAlignment(alignment);
+ setYesLabel(yesLabel);
+ setNoLabel(noLabel);
+ setCancelLabel(cancelLabel);
+ }
+
+ // Methods to query all of the bean properties.
+ public String getMessageText() { return messageText; }
+ public Alignment getAlignment() { return alignment; }
+ public String getYesLabel() { return yesLabel; }
+ public String getNoLabel() { return noLabel; }
+ public String getCancelLabel() { return cancelLabel; }
+
+ // Methods to set all of the bean properties.
+ public void setMessageText(String messageText) {
+ this.messageText = messageText;
+ message.setLabel(messageText);
+ validate();
+ }
+
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ message.setAlignment(alignment);
+ }
+
+ public void setYesLabel(String l) {
+ yesLabel = l;
+ yes.setLabel(l);
+ yes.setVisible((l != null) && (l.length() > 0));
+ validate();
+ }
+
+ public void setNoLabel(String l) {
+ noLabel = l;
+ no.setLabel(l);
+ no.setVisible((l != null) && (l.length() > 0));
+ validate();
+ }
+
+ public void setCancelLabel(String l) {
+ cancelLabel = l;
+ cancel.setLabel(l);
+ cancel.setVisible((l != null) && (l.length() > 0));
+ validate();
+ }
+
+ public void setFont(Font f) {
+ super.setFont(f); // Invoke the superclass method
+ message.setFont(f);
+ yes.setFont(f);
+ no.setFont(f);
+ cancel.setFont(f);
+ validate();
+ }
+
+ /** This field holds a list of registered ActionListeners. */
+ protected Vector listeners = new Vector();
+
+ /** Register an action listener to be notified when a button is pressed */
+ public void addAnswerListener(AnswerListener l) {
+ listeners.addElement(l);
+ }
+
+ /** Remove an Answer listener from our list of interested listeners */
+ public void removeAnswerListener(AnswerListener l) {
+ listeners.removeElement(l);
+ }
+
+ /** Send an event to all registered listeners */
+ public void fireEvent(AnswerEvent e) {
+ // Make a copy of the list and fire the events using that copy.
+ // This means that listeners can be added or removed from the original
+ // list in response to this event. We ought to be able to just use an
+ // enumeration for the vector, but that doesn't actually copy the list.
+ Vector list = (Vector) listeners.clone();
+ for(int i = 0; i < list.size(); i++) {
+ AnswerListener listener = (AnswerListener)list.elementAt(i);
+ switch(e.getID()) {
+ case AnswerEvent.YES: listener.yes(e); break;
+ case AnswerEvent.NO: listener.no(e); break;
+ case AnswerEvent.CANCEL: listener.cancel(e); break;
+ }
+ }
+ }
+
+ /** A main method that demonstrates the class */
+ public static void main(String[] args) {
+ // Create an instance of InfoPanel, with title and message specified:
+ YesNoPanel p = new YesNoPanel("Do you really want to quit?");
+
+ // Register an action listener for the Panel. This one just prints
+ // the results out to the console.
+ p.addAnswerListener(new AnswerListener() {
+ public void yes(AnswerEvent e) { System.exit(0); }
+ public void no(AnswerEvent e) { System.out.println("No"); }
+ public void cancel(AnswerEvent e) {
+ System.out.println("Cancel");
+ }
+ });
+
+ Frame f = new Frame();
+ f.add(p);
+ f.pack();
+ f.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java
new file mode 100644
index 0000000..7be3e62
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.beans.*;
+import java.lang.reflect.*;
+import java.awt.*;
+
+/**
+ * This BeanInfo class provides additional information about the YesNoPanel
+ * bean in addition to what can be obtained through introspection alone.
+ **/
+public class YesNoPanelBeanInfo extends SimpleBeanInfo {
+ /**
+ * Return an icon for the bean. We should really check the kind argument
+ * to see what size icon the beanbox wants, but since we only have one
+ * icon to offer, we just return it and let the beanbox deal with it
+ **/
+ public Image getIcon(int kind) { return loadImage("YesNoPanelIcon.gif"); }
+
+ /**
+ * Return a descriptor for the bean itself. It specifies a customizer
+ * for the bean class. We could also add a description string here
+ **/
+ public BeanDescriptor getBeanDescriptor() {
+ return new BeanDescriptor(YesNoPanel.class,
+ YesNoPanelCustomizer.class);
+ }
+
+ /** This is a convenience method for creating PropertyDescriptor objects */
+ static PropertyDescriptor prop(String name, String description) {
+ try {
+ PropertyDescriptor p =
+ new PropertyDescriptor(name, YesNoPanel.class);
+ p.setShortDescription(description);
+ return p;
+ }
+ catch(IntrospectionException e) { return null; }
+ }
+
+ // Initialize a static array of PropertyDescriptor objects that provide
+ // additional information about the properties supported by the bean.
+ // By explicitly specifying property descriptors, we are able to provide
+ // simple help strings for each property; these would not be available to
+ // the beanbox through simple introspection. We are also able to register
+ // a special property editors for the messageText property
+ static PropertyDescriptor[] props = {
+ prop("messageText", "The message text that appears in the bean body"),
+ prop("alignment", "The alignment of the message text"),
+ prop("yesLabel", "The label for the Yes button"),
+ prop("noLabel", "The label for the No button"),
+ prop("cancelLabel","The label for the Cancel button"),
+ prop("font", "The font for the message and buttons"),
+ prop("background", "The background color"),
+ prop("foreground", "The foreground color"),
+ };
+ static {
+ props[0].setPropertyEditorClass(YesNoPanelMessageEditor.class);
+ }
+
+ /** Return the property descriptors for this bean */
+ public PropertyDescriptor[] getPropertyDescriptors() { return props; }
+
+ /** The message property is most often customized; make it the default */
+ public int getDefaultPropertyIndex() { return 0; }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java
new file mode 100644
index 0000000..eada22b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+
+/**
+ * This class is a customizer for the YesNoPanel bean. It displays a
+ * TextArea and three TextFields where the user can enter the main message
+ * and the labels for each of the three buttons. It does not allow the
+ * alignment property to be set.
+ **/
+public class YesNoPanelCustomizer extends Panel
+ implements Customizer, TextListener
+{
+ protected YesNoPanel bean; // The bean being customized
+ protected TextArea message; // For entering the message
+ protected TextField fields[]; // For entering button text
+
+ // The bean box calls this method to tell us what object to customize.
+ // This method will always be called before the customizer is displayed,
+ // so it is safe to create the customizer GUI here.
+ public void setObject(Object o) {
+ bean = (YesNoPanel)o; // save the object we're customizing
+
+ // Put a label at the top of the panel.
+ this.setLayout(new BorderLayout());
+ this.add(new Label("Enter the message to appear in the panel:"),
+ "North");
+
+ // And a big text area below it for entering the message.
+ message = new TextArea(bean.getMessageText());
+ message.addTextListener(this);
+ // TextAreas don't know how big they want to be. You must tell them.
+ message.setSize(400, 200);
+ this.add(message, "Center");
+
+ // Then add a row of textfields for entering the button labels.
+ Panel buttonbox = new Panel(); // The row container
+ buttonbox.setLayout(new GridLayout(1, 0, 25, 10)); // Equally spaced
+ this.add(buttonbox, "South"); // Put row on bottom
+
+ // Now go create three TextFields to put in this row. But actually
+ // position a Label above each, so create an container for each
+ // TextField+Label combination.
+ fields = new TextField[3]; // Array of TextFields.
+ String[] labels = new String[] { // Labels for each.
+ "Yes Button Label", "No Button Label", "Cancel Button Label"};
+ String[] values = new String[] { // Initial values of each.
+ bean.getYesLabel(), bean.getNoLabel(), bean.getCancelLabel()};
+ for(int i = 0; i < 3; i++) {
+ Panel p = new Panel(); // Create a container.
+ p.setLayout(new BorderLayout()); // Give it a BorderLayout.
+ p.add(new Label(labels[i]), "North"); // Put a label on the top.
+ fields[i] = new TextField(values[i]); // Create the text field.
+ p.add(fields[i], "Center"); // Put it below the label.
+ fields[i].addTextListener(this); // Set the event listener.
+ buttonbox.add(p); // Add container to row.
+ }
+ }
+ // Add some space around the outside of the panel.
+ public Insets getInsets() { return new Insets(10, 10, 10, 10); }
+
+ // This is the method defined by the TextListener interface. Whenever the
+ // user types a character in the TextArea or TextFields, this will get
+ // called. It updates the appropriate property of the bean and fires a
+ // property changed event, as all customizers are required to do.
+ // Note that we are not required to fire an event for every keystroke.
+ // Instead we could include an "Apply" button that would make all the
+ // changes at once, with a single property changed event.
+ public void textValueChanged(TextEvent e) {
+ TextComponent t = (TextComponent)e.getSource();
+ String s = t.getText();
+ if (t == message) bean.setMessageText(s);
+ else if (t == fields[0]) bean.setYesLabel(s);
+ else if (t == fields[1]) bean.setNoLabel(s);
+ else if (t == fields[2]) bean.setCancelLabel(s);
+ listeners.firePropertyChange(null, null, null);
+ }
+
+ // This code uses the PropertyChangeSupport class to maintain a list of
+ // listeners interested in the edits we make to the bean.
+ protected PropertyChangeSupport listeners =new PropertyChangeSupport(this);
+ public void addPropertyChangeListener(PropertyChangeListener l) {
+ listeners.addPropertyChangeListener(l);
+ }
+ public void removePropertyChangeListener(PropertyChangeListener l) {
+ listeners.removePropertyChangeListener(l);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif
new file mode 100644
index 0000000..b455f3e
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif differ
diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java
new file mode 100644
index 0000000..8e11d0e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.beans;
+import java.beans.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * This class is a custom editor for the messageText property of the
+ * YesNoPanel bean. It is necessary because the default editor for
+ * properties of type String does not allow multi-line strings
+ * to be entered.
+ */
+public class YesNoPanelMessageEditor implements PropertyEditor {
+ protected String value; // The value we will be editing.
+
+ public void setValue(Object o) { value = (String) o; }
+ public Object getValue() { return value; }
+ public void setAsText(String s) { value = s; }
+ public String getAsText() { return value; }
+ public String[] getTags() { return null; } // not enumerated; no tags
+
+ // Say that we allow custom editing.
+ public boolean supportsCustomEditor() { return true; }
+
+ // Return the custom editor. This just creates and returns a TextArea
+ // to edit the multi-line text. But it also registers a listener on the
+ // text area to update the value as the user types and to fire the
+ // property change events that property editors are required to fire.
+ public Component getCustomEditor() {
+ final TextArea t = new TextArea(value);
+ t.setSize(300, 150); // TextArea has no preferred size, so set one
+ t.addTextListener(new TextListener() {
+ public void textValueChanged(TextEvent e) {
+ value = t.getText();
+ listeners.firePropertyChange(null, null, null);
+ }
+ });
+ return t;
+ }
+
+ // Visual display of the value, for use with the custom editor.
+ // Just print some instructions and hope they fit in the in the box.
+ // This could be more sophisticated.
+ public boolean isPaintable() { return true; }
+ public void paintValue(Graphics g, Rectangle r) {
+ g.setClip(r);
+ g.drawString("Click to edit...", r.x+5, r.y+15);
+ }
+
+ // Important method for code generators. Note that it really ought to
+ // escape any quotes or backslashes in value before returning the string.
+ public String getJavaInitializationString() { return "\"" + value + "\""; }
+
+ // This code uses the PropertyChangeSupport class to maintain a list of
+ // listeners interested in the edits we make to the value.
+ protected PropertyChangeSupport listeners =new PropertyChangeSupport(this);
+ public void addPropertyChangeListener(PropertyChangeListener l) {
+ listeners.addPropertyChangeListener(l);
+ }
+ public void removePropertyChangeListener(PropertyChangeListener l) {
+ listeners.removePropertyChangeListener(l);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/beans/makejar.sh b/src/main/java/com/davidflanagan/examples/beans/makejar.sh
new file mode 100644
index 0000000..5fe4b7c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/makejar.sh
@@ -0,0 +1 @@
+jar cmf manifest.stub YesNoPanelBean.jar -C ../../../../ com/davidflanagan/examples/beans/
diff --git a/src/main/java/com/davidflanagan/examples/beans/manifest.stub b/src/main/java/com/davidflanagan/examples/beans/manifest.stub
new file mode 100644
index 0000000..b2bcb56
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/beans/manifest.stub
@@ -0,0 +1,5 @@
+Name: com/davidflanagan/examples/beans/MultiLineLabel.class
+Java-Bean: true
+
+Name: com/davidflanagan/examples/beans/YesNoPanel.class
+Java-Bean: true
diff --git a/src/main/java/com/davidflanagan/examples/classes/Averager.java b/src/main/java/com/davidflanagan/examples/classes/Averager.java
new file mode 100644
index 0000000..8376e42
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/Averager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+/**
+ * A class to compute the running average of numbers passed to it
+ **/
+public class Averager {
+ // Private fields to hold the current state.
+ private int n = 0;
+ private double sum = 0.0, sumOfSquares = 0.0;
+
+ /**
+ * This method adds a new datum into the average.
+ **/
+ public void addDatum(double x) {
+ n++;
+ sum += x;
+ sumOfSquares += x * x;
+ }
+
+ /** This method returns the average of all numbers passed to addDatum() */
+ public double getAverage() { return sum / n; }
+
+ /** This method returns the standard deviation of the data */
+ public double getStandardDeviation() {
+ return Math.sqrt(((sumOfSquares - sum*sum/n)/n));
+ }
+
+ /** This method returns the number of numbers passed to addDatum() */
+ public double getNum() { return n; }
+
+ /** This method returns the sum of all numbers passed to addDatum() */
+ public double getSum() { return sum; }
+
+ /** This method returns the sum of the squares of all numbers. */
+ public double getSumOfSquares() { return sumOfSquares; }
+
+ /** This method resets the Averager object to begin from scratch */
+ public void reset() { n = 0; sum = 0.0; sumOfSquares = 0.0; }
+
+ /**
+ * This nested class is a simple test program we can use to check that
+ * our code works okay.
+ **/
+ public static class Test {
+ public static void main(String args[]) {
+ Averager a = new Averager();
+ for(int i = 1; i <= 100; i++) a.addDatum(i);
+ System.out.println("Average: " + a.getAverage());
+ System.out.println("Standard Deviation: " +
+ a.getStandardDeviation());
+ System.out.println("N: " + a.getNum());
+ System.out.println("Sum: " + a.getSum());
+ System.out.println("Sum of squares: " + a.getSumOfSquares());
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java b/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java
new file mode 100644
index 0000000..0f04eb8
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+import java.awt.*;
+
+/**
+ * This class subclasses DrawableRect and adds colors to the rectangle it draws
+ **/
+public class ColoredRect extends DrawableRect {
+ // These are new fields defined by this class.
+ // x1, y1, x2, and y2 are inherited from our super-superclass, Rect.
+ protected Color border, fill;
+
+ /**
+ * This constructor uses super() to invoke the superclass constructor, and
+ * also does some initialization of its own.
+ **/
+ public ColoredRect(int x1, int y1, int x2, int y2,
+ Color border, Color fill)
+ {
+ super(x1, y1, x2, y2);
+ this.border = border;
+ this.fill = fill;
+ }
+
+ /**
+ * This method overrides the draw() method of our superclass so that it
+ * can make use of the colors that have been specified.
+ **/
+ public void draw(Graphics g) {
+ g.setColor(fill);
+ g.fillRect(x1, y1, (x2-x1), (y2-y1));
+ g.setColor(border);
+ g.drawRect(x1, y1, (x2-x1), (y2-y1));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java b/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java
new file mode 100644
index 0000000..00a6c6c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+
+/**
+ * This class represents complex numbers, and defines methods for performing
+ * arithmetic on complex numbers.
+ **/
+public class ComplexNumber {
+ // These are the instance variables. Each ComplexNumber object holds
+ // two double values, known as x and y. They are private, so they are
+ // not accessible from outside this class. Instead, they are available
+ // through the real() and imaginary() methods below.
+ private double x, y;
+
+ /** This is the constructor. It initializes the x and y variables */
+ public ComplexNumber(double real, double imaginary) {
+ this.x = real;
+ this.y = imaginary;
+ }
+
+ /**
+ * An accessor method. Returns the real part of the complex number.
+ * Note that there is no setReal() method to set the real part. This means
+ * that the ComplexNumber class is "immutable".
+ **/
+ public double real() { return x; }
+
+ /** An accessor method. Returns the imaginary part of the complex number */
+ public double imaginary() { return y; }
+
+ /** Compute the magnitude of a complex number */
+ public double magnitude() { return Math.sqrt(x*x + y*y); }
+
+ /**
+ * This method converts a ComplexNumber to a string. This is a method of
+ * Object that we override so that complex numbers can be meaningfully
+ * converted to strings, and so they can conveniently be printed out with
+ * System.out.println() and related methods
+ **/
+ public String toString() { return "{" + x + "," + y + "}"; }
+
+ /**
+ * This is a static class method. It takes two complex numbers, adds
+ * them, and returns the result as a third number. Because it is static,
+ * there is no "current instance" or "this" object. Use it like this:
+ * ComplexNumber c = ComplexNumber.add(a, b);
+ **/
+ public static ComplexNumber add(ComplexNumber a, ComplexNumber b) {
+ return new ComplexNumber(a.x + b.x, a.y + b.y);
+ }
+
+ /**
+ * This is a non-static instance method by the same name. It adds the
+ * specified complex number to the current complex number. Use it like
+ * this:
+ * ComplexNumber c = a.add(b);
+ **/
+ public ComplexNumber add(ComplexNumber a) {
+ return new ComplexNumber(this.x + a.x, this.y+a.y);
+ }
+
+ /** A static class method to multiply complex numbers */
+ public static ComplexNumber multiply(ComplexNumber a, ComplexNumber b) {
+ return new ComplexNumber(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
+ }
+
+ /** An instance method to multiply complex numbers */
+ public ComplexNumber multiply(ComplexNumber a) {
+ return new ComplexNumber(x*a.x - y*a.y, x*a.y + y*a.x);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java b/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java
new file mode 100644
index 0000000..c67384b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+/**
+ * This is a subclass of Rect that allows itself to be drawn on a screen.
+ * It inherits all the fields and methods of Rect
+ * It relies on the java.awt.Graphics object to perform the drawing.
+ **/
+public class DrawableRect extends Rect {
+ /** The DrawableRect constructor just invokes the Rect() constructor */
+ public DrawableRect(int x1, int y1, int x2, int y2) { super(x1,y1,x2,y2); }
+
+ /** This is the new method defined by DrawableRect */
+ public void draw(java.awt.Graphics g) {
+ g.drawRect(x1, y1, (x2 - x1), (y2-y1));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/LinkedList.java b/src/main/java/com/davidflanagan/examples/classes/LinkedList.java
new file mode 100644
index 0000000..9036912
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/LinkedList.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+
+/**
+ * This class implements a linked list that can contain any type of object
+ * that implements the nested Linkable interface. Note that the methods are
+ * all synchronized, so that it can safely be used by multiple threads at
+ * the same time.
+ **/
+public class LinkedList {
+ /**
+ * This interface defines the methods required by any object that can be
+ * linked into a linked list.
+ **/
+ public interface Linkable {
+ public Linkable getNext(); // Returns the next element in the list
+ public void setNext(Linkable node); // Sets the next element in the list
+ }
+
+ // This class has a default constructor: public LinkedList() {}
+
+ /** This is the only field of the class. It holds the head of the list */
+ Linkable head;
+
+ /** Return the first node in the list */
+ public synchronized Linkable getHead() { return head; }
+
+ /** Insert a node at the beginning of the list */
+ public synchronized void insertAtHead(Linkable node) {
+ node.setNext(head);
+ head = node;
+ }
+
+ /** Insert a node at the end of the list */
+ public synchronized void insertAtTail(Linkable node) {
+ if (head == null) head = node;
+ else {
+ Linkable p, q;
+ for(p = head; (q = p.getNext()) != null; p = q) /* no body */;
+ p.setNext(node);
+ }
+ }
+
+ /** Remove and return the node at the head of the list */
+ public synchronized Linkable removeFromHead() {
+ Linkable node = head;
+ if (node != null) {
+ head = node.getNext();
+ node.setNext(null);
+ }
+ return node;
+ }
+
+ /** Remove and return the node at the end of the list */
+ public synchronized Linkable removeFromTail() {
+ if (head == null) return null;
+ Linkable p = head, q = null, next = head.getNext();
+ if (next == null) {
+ head = null;
+ return p;
+ }
+ while((next = p.getNext()) != null) {
+ q = p;
+ p = next;
+ }
+ q.setNext(null);
+ return p;
+ }
+
+ /**
+ * Remove a node matching the specified node from the list.
+ * Use equals() instead of == to test for a matched node.
+ **/
+ public synchronized void remove(Linkable node) {
+ if (head == null) return;
+ if (node.equals(head)) {
+ head = head.getNext();
+ return;
+ }
+ Linkable p = head, q = null;
+ while((q = p.getNext()) != null) {
+ if (node.equals(q)) {
+ p.setNext(q.getNext());
+ return;
+ }
+ p = q;
+ }
+ }
+
+ /** This nested class defines a main() method that tests LinkedList */
+ public static class Test {
+ /**
+ * This is a test class that implements the Linkable interface
+ **/
+ static class LinkableInteger implements Linkable {
+ int i; // The data contained in the node
+ Linkable next; // A reference to the next node in the list
+ public LinkableInteger(int i) { this.i = i; } // Constructor
+ public Linkable getNext() { return next; } // Part of Linkable
+ public void setNext(Linkable node) { next = node; } // Linkable
+ public String toString() { return i + ""; } // For easy printing
+ public boolean equals(Object o) { // For comparison
+ if (this == o) return true;
+ if (!(o instanceof LinkableInteger)) return false;
+ if (((LinkableInteger)o).i == this.i) return true;
+ return false;
+ }
+ }
+
+ /**
+ * The test program. Insert some nodes, remove some nodes, then
+ * print out all elements in the list. It should print out the
+ * numbers 4, 6, 3, 1, and 5
+ **/
+ public static void main(String[] args) {
+ LinkedList ll = new LinkedList(); // Create a list
+ ll.insertAtHead(new LinkableInteger(1)); // Insert some stuff
+ ll.insertAtHead(new LinkableInteger(2));
+ ll.insertAtHead(new LinkableInteger(3));
+ ll.insertAtHead(new LinkableInteger(4));
+ ll.insertAtTail(new LinkableInteger(5));
+ ll.insertAtTail(new LinkableInteger(6));
+ System.out.println(ll.removeFromHead()); // Remove and print a node
+ System.out.println(ll.removeFromTail()); // Remove and print again
+ ll.remove(new LinkableInteger(2)); // Remove another one
+
+ // Now print out the contents of the list.
+ for(Linkable l = ll.getHead(); l != null; l = l.getNext())
+ System.out.println(l);
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/Randomizer.java b/src/main/java/com/davidflanagan/examples/classes/Randomizer.java
new file mode 100644
index 0000000..288250b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/Randomizer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+/**
+ * This class defines methods for computing pseudo-random numbers, and defines
+ * the state variable that needs to be maintained for use by those methods.
+ **/
+public class Randomizer {
+ // Carefully chosen constants from the book "Numerical Recipes in C".
+ // All "static final" fields are constants.
+ static final int m = 233280;
+ static final int a = 9301;
+ static final int c = 49297;
+
+ // The state variable maintained by each Randomizer instance
+ int seed = 1;
+
+ /**
+ * The constructor for the Randomizer() class. It must be passed some
+ * arbitrary initial value or "seed" for its pseudo-randomness.
+ **/
+ public Randomizer(int seed) { this.seed = seed; }
+
+ /**
+ * This method computes a pseudo-random number between 0 and 1 using a very
+ * simple algorithm. Math.random() and java.util.Random are actually a lot
+ * better at computing randomness.
+ **/
+ public float randomFloat() {
+ seed = (seed * a + c) % m;
+ return (float) Math.abs((float)seed/(float)m);
+ }
+
+ /**
+ * This method computes a pseudo-random integer between 0 and specified
+ * maximum. It uses randomFloat() above.
+ **/
+ public int randomInt(int max) {
+ return Math.round(max * randomFloat());
+ }
+
+ /**
+ * This nested class is a simple test program: it prints 10 random ints.
+ * Note how the Randomizer object is seeded using the current time.
+ **/
+ public static class Test {
+ public static void main(String[] args) {
+ Randomizer r = new Randomizer((int)new java.util.Date().getTime());
+ for(int i = 0; i < 10; i++) System.out.println(r.randomInt(100));
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/Rect.java b/src/main/java/com/davidflanagan/examples/classes/Rect.java
new file mode 100644
index 0000000..7101faa
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/Rect.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+/**
+ * This class represents a rectangle. Its fields represent the coordinates
+ * of the corners of the rectangle. Its methods define operations that can
+ * be performed on Rect objects.
+ **/
+public class Rect {
+ // These are the data fields of the class
+ public int x1, y1, x2, y2;
+
+ /**
+ * The is the main constructor for the class. It simply uses its arguments
+ * to initialize each of the fields of the new object. Note that it has
+ * the same name as the class, and that it has no return value declared in
+ * its signature.
+ **/
+ public Rect(int x1, int y1, int x2, int y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ }
+
+ /**
+ * This is another constructor. It defines itself in terms of the above
+ **/
+ public Rect(int width, int height) { this(0, 0, width, height); }
+
+ /** This is yet another constructor. */
+ public Rect() { this(0, 0, 0, 0); }
+
+ /** Move the rectangle by the specified amounts */
+ public void move(int deltax, int deltay) {
+ x1 += deltax; x2 += deltax;
+ y1 += deltay; y2 += deltay;
+ }
+
+ /** Test whether the specified point is inside the rectangle */
+ public boolean isInside(int x, int y) {
+ return ((x >= x1)&& (x <= x2)&& (y >= y1)&& (y <= y2));
+ }
+
+ /**
+ * Return the union of this rectangle with another. I.e. return the
+ * smallest rectangle that includes them both.
+ **/
+ public Rect union(Rect r) {
+ return new Rect((this.x1 < r.x1) ? this.x1 : r.x1,
+ (this.y1 < r.y1) ? this.y1 : r.y1,
+ (this.x2 > r.x2) ? this.x2 : r.x2,
+ (this.y2 > r.y2) ? this.y2 : r.y2);
+ }
+
+ /**
+ * Return the intersection of this rectangle with another.
+ * I.e. return their overlap.
+ **/
+ public Rect intersection(Rect r) {
+ Rect result = new Rect((this.x1 > r.x1) ? this.x1 : r.x1,
+ (this.y1 > r.y1) ? this.y1 : r.y1,
+ (this.x2 < r.x2) ? this.x2 : r.x2,
+ (this.y2 < r.y2) ? this.y2 : r.y2);
+ if (result.x1 > result.x2) { result.x1 = result.x2 = 0; }
+ if (result.y1 > result.y2) { result.y1 = result.y2 = 0; }
+ return result;
+ }
+
+ /**
+ * This is a method of our superclass, Object. We override it so that
+ * Rect objects can be meaningfully converted to strings, can be
+ * concatenated to strings with the + operator, and can be passed to
+ * methods like System.out.println()
+ **/
+ public String toString() {
+ return "[" + x1 + "," + y1 + "; " + x2 + "," + y2 + "]";
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/RectTest.java b/src/main/java/com/davidflanagan/examples/classes/RectTest.java
new file mode 100644
index 0000000..d5bc8e1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/RectTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+
+/** This class demonstrates how you might use the Rect class */
+public class RectTest {
+ public static void main(String[] args) {
+ Rect r1 = new Rect(1, 1, 4, 4); // Create Rect objects
+ Rect r2 = new Rect(2, 3, 5, 6);
+ Rect u = r1.union(r2); // Invoke Rect methods
+ Rect i = r2.intersection(r1);
+
+ if (u.isInside(r2.x1, r2.y1)) // Use Rect fields and invoke a method
+ System.out.println("(" + r2.x1 + "," + r2.y1 +
+ ") is inside the union");
+
+ // These lines implicitly call the Rect.toString() method
+ System.out.println(r1 + " union " + r2 + " = " + u);
+ System.out.println(r1 + " intersect " + r2 + " = " + i);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/classes/Sorter.java b/src/main/java/com/davidflanagan/examples/classes/Sorter.java
new file mode 100644
index 0000000..afaf2a1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/classes/Sorter.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.classes;
+// These are some classes we need for internationalized string sorting
+import java.text.Collator;
+import java.text.CollationKey;
+import java.util.Locale;
+
+/**
+ * This class defines a bunch of static methods for efficiently sorting
+ * arrays of Strings or other objects. It also defines two interfaces that
+ * provide two different ways of comparing objects to be sorted.
+ **/
+public class Sorter {
+ /**
+ * This interface defines the compare() method used to compare two objects.
+ * To sort objects of a given type, you must provide a Comparer
+ * object with a compare() method that orders those objects as desired
+ **/
+ public static interface Comparer {
+ /**
+ * Compare objects, return a value that indicates their relative order:
+ * if (a > b) return > 0;
+ * if (a == b) return 0;
+ * if (a < b) return < 0.
+ **/
+ public int compare(Object a, Object b);
+ }
+
+ /**
+ * This is an alternative interface that can be used to order objects. If
+ * a class implements this Comparable interface, then any two instances of
+ * that class can be directly compared by invoking the compareTo() method.
+ **/
+ public static interface Comparable {
+ /**
+ * Compare objects, return a value that indicates their relative order:
+ * if (this > other) return > 0
+ * if (this == other) return 0
+ * if (this < other) return < 0
+ **/
+ public int compareTo(Object other);
+ }
+
+ /**
+ * This is an internal Comparer object (created with an anonymous class)
+ * that compares two ASCII strings.
+ * It is used in the sortAscii methods below.
+ **/
+ private static Comparer ascii_comparer = new Comparer() {
+ public int compare(Object a, Object b) {
+ return ((String)a).compareTo((String)b);
+ }
+ };
+
+ /**
+ * This is another internal Comparer object. It is used to compare two
+ * Comparable objects. It is used by the sort() methods below that take
+ * Comparable objects as arguments instead of arbitrary objects
+ **/
+ private static Comparer comparable_comparer = new Comparer() {
+ public int compare(Object a, Object b) {
+ return ((Comparable)a).compareTo(b);
+ }
+ };
+
+ /** Sort an array of ASCII strings into ascending order */
+ public static void sortAscii(String[] a) {
+ // Note use of the ascii_comparer object
+ sort(a, null, 0, a.length-1, true, ascii_comparer);
+ }
+
+ /**
+ * Sort a portion of an array of ASCII strings into ascending or descending
+ * order, depending on the argument up
+ **/
+ public static void sortAscii(String[] a, int from, int to, boolean up) {
+ // Note use of the ascii_comparer object
+ sort(a, null, from, to, up, ascii_comparer);
+ }
+
+ /** Sort an array of ASCII strings into ascending order, ignoring case */
+ public static void sortAsciiIgnoreCase(String[] a) {
+ sortAsciiIgnoreCase(a, 0, a.length-1, true);
+ }
+
+ /**
+ * Sort an portion of an array of ASCII strings, ignoring case. Sort into
+ * ascending order if up is true, otherwise sort into descending order.
+ **/
+ public static void sortAsciiIgnoreCase(String[] a, int from, int to,
+ boolean up) {
+ if ((a == null) || (a.length < 2)) return;
+ // Create a secondary array of strings that contains lowercase versions
+ // of all the specified strings.
+ String b[] = new String[a.length];
+ for(int i = 0; i < a.length; i++) b[i] = a[i].toLowerCase();
+ // Sort that secondary array, and rearrange the original array
+ // in exactly the same way, resulting in a case-insensitive sort.
+ // Note the use of the ascii_comparer object
+ sort(b, a, from, to, up, ascii_comparer);
+ }
+
+ /**
+ * Sort an array of strings into ascending order, using the correct
+ * collation order for the default locale
+ **/
+ public static void sort(String[] a) {
+ sort(a, 0, a.length-1, true, false, null);
+ }
+
+ /**
+ * Sort a portion of an array of strings, using the collation order of
+ * the default locale. If up is true, sort ascending, otherwise, sort
+ * descending. If ignorecase is true, ignore the capitalization of letters
+ **/
+ public static void sort(String[] a, int from, int to,
+ boolean up, boolean ignorecase) {
+ sort(a, from, to, up, ignorecase, null);
+ }
+
+ /**
+ * Sort a portion of an array of strings, using the collation order of
+ * the specified locale. If up is true, sort ascending, otherwise, sort
+ * descending. If ignorecase is true, ignore the capitalization of letters
+ **/
+ public static void sort(String[] a, int from, int to,
+ boolean up, boolean ignorecase,
+ Locale locale) {
+ // Don't sort if we don't have to
+ if ((a == null) || (a.length < 2)) return;
+
+ // The java.text.Collator object does internationalized string compares
+ // Create one for the specified, or the default locale.
+ Collator c;
+ if (locale == null) c = Collator.getInstance();
+ else c = Collator.getInstance(locale);
+
+ // Specify whether or not case should be considered in the sort.
+ // Note: this option does not seem to work correctly in JDK 1.1.1
+ // using the default American English locale.
+ if (ignorecase) c.setStrength(Collator.SECONDARY);
+
+ // Use the Collator object to create an array of CollationKey objects
+ // that correspond to each of the strings.
+ // Comparing CollationKeys is much quicker than comparing Strings
+ CollationKey[] b = new CollationKey[a.length];
+ for(int i = 0; i < a.length; i++) b[i] = c.getCollationKey(a[i]);
+
+ // Now define a Comparer object to compare collation keys, using an
+ // anonymous class.
+ Comparer comp = new Comparer() {
+ public int compare(Object a, Object b) {
+ return ((CollationKey)a).compareTo((CollationKey)b);
+ }
+ };
+
+ // Finally, sort the array of CollationKey objects, rearranging the
+ // original array of strings in exactly the same way.
+ sort(b, a, from, to, up, comp);
+ }
+
+ /** Sort an array of Comparable objects into ascending order */
+ public static void sort(Comparable[] a) {
+ sort(a, null, 0, a.length-1, true);
+ }
+
+ /**
+ * Sort a portion of an array of Comparable objects. If up is true,
+ * sort into ascending order, otherwise sort into descending order.
+ **/
+ public static void sort(Comparable[] a, int from, int to, boolean up) {
+ sort(a, null, from, to, up, comparable_comparer);
+ }
+
+ /**
+ * Sort a portion of array a of Comparable objects. If up is true,
+ * sort into ascending order, otherwise sort into descending order.
+ * Re-arrange the array b in exactly the same way as a.
+ **/
+ public static void sort(Comparable[] a, Object[] b,
+ int from, int to, boolean up) {
+ sort(a, b, from, to, up, comparable_comparer);
+ }
+
+ /**
+ * Sort an array of arbitrary objects into ascending order, using the
+ * comparison defined by the Comparer object c
+ **/
+ public static void sort(Object[] a, Comparer c) {
+ sort(a, null, 0, a.length-1, true, c);
+ }
+
+ /**
+ * Sort a portion of an array of objects, using the comparison defined by
+ * the Comparer object c. If up is true, sort into ascending order,
+ * otherwise sort into descending order.
+ **/
+ public static void sort(Object[] a, int from, int to, boolean up,
+ Comparer c)
+ {
+ sort(a, null, from, to, up, c);
+ }
+
+ /**
+ * This is the main sort() routine. It performs a quicksort on the elements
+ * of array a between the element from and the element to. The up argument
+ * specifies whether the elements should be sorted into ascending (true) or
+ * descending (false) order. The Comparer argument c is used to perform
+ * comparisons between elements of the array. The elements of the array b
+ * are reordered in exactly the same way as the elements of array a are.
+ **/
+ public static void sort(Object[] a, Object[] b,
+ int from, int to,
+ boolean up, Comparer c)
+ {
+ // If there is nothing to sort, return
+ if ((a == null) || (a.length < 2)) return;
+
+ // This is the basic quicksort algorithm, stripped of frills that can
+ // make it faster but even more confusing than it already is. You
+ // should understand what the code does, but don't have to understand
+ // just why it is guaranteed to sort the array...
+ // Note the use of the compare() method of the Comparer object.
+ int i = from, j = to;
+ Object center = a[(from + to) / 2];
+ do {
+ if (up) { // an ascending sort
+ while((i < to)&& (c.compare(center, a[i]) > 0)) i++;
+ while((j > from)&& (c.compare(center, a[j]) < 0)) j--;
+ } else { // a descending sort
+ while((i < to)&& (c.compare(center, a[i]) < 0)) i++;
+ while((j > from)&& (c.compare(center, a[j]) > 0)) j--;
+ }
+ if (i < j) {
+ Object tmp = a[i]; a[i] = a[j]; a[j] = tmp; // swap elements
+ if (b != null) { tmp = b[i]; b[i] = b[j]; b[j] = tmp; } // swap
+ }
+ if (i <= j) { i++; j--; }
+ } while(i <= j);
+ if (from < j) sort(a, b, from, j, up, c); // recursively sort the rest
+ if (i < to) sort(a, b, i, to, up, c);
+ }
+
+ /**
+ * This nested class defines a test program that demonstrates several
+ * ways to use the Sorter class to sort ComplexNumber objects
+ **/
+ public static class Test {
+ /**
+ * This subclass of ComplexNumber implements the Comparable interface
+ * and defines a compareTo() method for comparing complex numbers.
+ * It compares numbers based on their magnitude. I.e. on their distance
+ * from the origin.
+ **/
+ static class SortableComplexNumber extends ComplexNumber
+ implements Comparable {
+ public SortableComplexNumber(double x, double y) { super(x, y); }
+ public int compareTo(Object other) {
+ return sign(this.magnitude()-((ComplexNumber)other).magnitude());
+ }
+ }
+
+ /** A a test program that sorts complex numbers in various ways. */
+ public static void main(String[] args) {
+ // Define an array of SortableComplexNumber objects. Initialize it
+ // to contain random complex numbers.
+ SortableComplexNumber[] a = new SortableComplexNumber[5];
+ for(int i = 0; i < a.length; i++)
+ a[i] = new SortableComplexNumber(Math.random()*10,
+ Math.random()*10);
+
+ // Now sort it using the SortableComplexNumber compareTo() method,
+ // which sorts by magnitude, and print the results out.
+ System.out.println("Sorted by magnitude:");
+ Sorter.sort(a);
+ for(int i = 0; i < a.length; i++) System.out.println(a[i]);
+
+ // Sort the complex numbers again, using a Comparer object that
+ // compares them based on the sum of their real and imaginary parts
+ System.out.println("Sorted by sum of real and imaginary parts:");
+ Sorter.sort(a, new Comparer() {
+ public int compare(Object a, Object b) {
+ ComplexNumber i = (ComplexNumber)a;
+ ComplexNumber j = (ComplexNumber)b;
+ return sign((i.real() + i.imaginary()) -
+ (j.real() + j.imaginary()));
+ }
+ });
+ for(int i = 0; i < a.length; i++) System.out.println(a[i]);
+
+ // Sort them again using a Comparer object that compares their real
+ // parts, and then their imaginary parts
+ System.out.println("Sorted descending by real, then imaginary:");
+ Sorter.sort(a, 0, a.length-1, false, new Comparer() {
+ public int compare(Object a, Object b) {
+ ComplexNumber i = (ComplexNumber) a;
+ ComplexNumber j = (ComplexNumber) b;
+ double result = i.real() - j.real();
+ if (result == 0) result = i.imaginary()-j.imaginary();
+ return sign(result);
+ }
+ });
+ for(int i = 0; i < a.length; i++) System.out.println(a[i]);
+ }
+
+ /** This is a convenience routine used by comparison routines */
+ public static int sign(double x) {
+ if (x > 0) return 1;
+ else if (x < 0) return -1;
+ else return 0;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java b/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java
new file mode 100644
index 0000000..d893a0b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.datatransfer;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.datatransfer.*;
+import java.io.Serializable;
+import java.util.StringTokenizer;
+
+/**
+ * This class represents a scribble composed of any number of "polylines".
+ * Each "polyline" is set of connected line segments. A scribble is created
+ * through a series of calls to the moveto() and lineto() methods. moveto()
+ * specifies the starting point of a new polyline, and lineto() adds a new
+ * point to the end of the current polyline().
+ *
+ * This class implements the Shape interface which means that it can be drawn
+ * using the Java2D graphics API
+ *
+ * It also implements the Transferable interface, which means that it can
+ * easily be used with cut-and-paste and drag-and-drop. It defines a custom
+ * DataFlavor, scribbleDataFlavor, which transfers Scribble objects as Java
+ * objects. However, it also supports cut-and-paste and drag-and-drop based
+ * on a portable string representation of the scribble. The toString()
+ * and parse() methods write and read this string format
+ **/
+public class Scribble implements Shape, Transferable, Serializable, Cloneable {
+ protected double[] points = new double[64]; // The scribble data
+ protected int numPoints = 0; // The current number of points
+ double maxX = Double.NEGATIVE_INFINITY; // The bounding box
+ double maxY = Double.NEGATIVE_INFINITY;
+ double minX = Double.POSITIVE_INFINITY;
+ double minY = Double.POSITIVE_INFINITY;
+
+ /**
+ * Begin a new polyline at (x,y). Note the use of Double.NaN in the
+ * points array to mark the beginning of a new polyline
+ **/
+ public void moveto(double x, double y) {
+ if (numPoints + 3 > points.length) reallocate();
+ // Mark this as the beginning of a new line
+ points[numPoints++] = Double.NaN;
+ // The rest of this method is just like lineto();
+ lineto(x, y);
+ }
+
+ /**
+ * Add the point (x,y) to the end of the current polyline
+ **/
+ public void lineto(double x, double y) {
+ if (numPoints + 2 > points.length) reallocate();
+ points[numPoints++] = x;
+ points[numPoints++] = y;
+
+ // See if the point enlarges our bounding box
+ if (x > maxX) maxX = x;
+ if (x < minX) minX = x;
+ if (y > maxY) maxY = y;
+ if (y < minY) minY = y;
+ }
+
+ /**
+ * Append the Scribble s to this Scribble
+ **/
+ public void append(Scribble s) {
+ int n = numPoints + s.numPoints;
+ double[] newpoints = new double[n];
+ System.arraycopy(points, 0, newpoints, 0, numPoints);
+ System.arraycopy(s.points, 0, newpoints, numPoints, s.numPoints);
+ points = newpoints;
+ numPoints = n;
+ minX = Math.min(minX, s.minX);
+ maxX = Math.max(maxX, s.maxX);
+ minY = Math.min(minY, s.minY);
+ maxY = Math.max(maxY, s.maxY);
+ }
+
+ /**
+ * Translate the coordinates of all points in the Scribble by x,y
+ **/
+ public void translate(double x, double y) {
+ for(int i = 0; i < numPoints; i++) {
+ if (Double.isNaN(points[i])) continue;
+ points[i++] += x;
+ points[i] += y;
+ }
+ minX += x; maxX += x;
+ minY += y; maxY += y;
+ }
+
+ /** An internal method to make more room in the data array */
+ protected void reallocate() {
+ double[] newpoints = new double[points.length * 2];
+ System.arraycopy(points, 0, newpoints, 0, numPoints);
+ points = newpoints;
+ }
+
+ /** Clone a Scribble object and its internal array of data */
+ public Object clone() {
+ try {
+ Scribble s = (Scribble) super.clone(); // make a copy of all fields
+ s.points = (double[]) points.clone(); // copy the entire array
+ return s;
+ }
+ catch (CloneNotSupportedException e) { // This should never happen
+ return this;
+ }
+ }
+
+ /** Convert the scribble data to a textual format */
+ public String toString() {
+ StringBuffer b = new StringBuffer();
+ for(int i = 0; i < numPoints; i++) {
+ if (Double.isNaN(points[i])) {
+ b.append("m ");
+ }
+ else {
+ b.append(points[i]);
+ b.append(' ');
+ }
+ }
+ return b.toString();
+ }
+
+ /**
+ * Create a new Scribble object and initialize it by parsing a string of
+ * coordinate data in the format produced by toString()
+ **/
+ public static Scribble parse(String s) throws NumberFormatException {
+ StringTokenizer st = new StringTokenizer(s);
+ Scribble scribble = new Scribble();
+ while(st.hasMoreTokens()) {
+ String t = st.nextToken();
+ if (t.charAt(0) == 'm') {
+ scribble.moveto(Double.parseDouble(st.nextToken()),
+ Double.parseDouble(st.nextToken()));
+ }
+ else {
+ scribble.lineto(Double.parseDouble(t),
+ Double.parseDouble(st.nextToken()));
+ }
+ }
+ return scribble;
+ }
+
+ // ========= The following methods implement the Shape interface ========
+
+ /** Return the bounding box of the Shape */
+ public Rectangle getBounds() {
+ return new Rectangle((int)(minX-0.5f), (int)(minY-0.5f),
+ (int)(maxX-minX+0.5f), (int)(maxY-minY+0.5f));
+ }
+
+ /** Return the bounding box of the Shape */
+ public Rectangle2D getBounds2D() {
+ return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);
+ }
+
+ /** Our shape is an open curve, so it never contains anything */
+ public boolean contains(Point2D p) { return false; }
+ public boolean contains(Rectangle2D r) { return false; }
+ public boolean contains(double x, double y) { return false; }
+ public boolean contains(double x, double y, double w, double h) {
+ return false;
+ }
+
+ /**
+ * Determine if the scribble intersects the specified rectangle by testing
+ * each line segment individually
+ **/
+ public boolean intersects(Rectangle2D r) {
+ if (numPoints < 4) return false;
+ int i = 0;
+ double x1, y1, x2 = 0.0, y2 = 0.0;
+ while(i < numPoints) {
+ if (Double.isNaN(points[i])) { // If we're beginning a new line
+ i++; // Skip the NaN
+ x2 = points[i++];
+ y2 = points[i++];
+ }
+ else {
+ x1 = x2;
+ y1 = y2;
+ x2 = points[i++];
+ y2 = points[i++];
+ if (r.intersectsLine(x1, y1, x2, y2)) return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Test for intersection by invoking the method above */
+ public boolean intersects(double x, double y, double w, double h){
+ return intersects(new Rectangle2D.Double(x,y,w,h));
+ }
+
+ /**
+ * Return a PathIterator object that tells Java2D how to draw this scribble
+ **/
+ public PathIterator getPathIterator(AffineTransform at) {
+ return new ScribbleIterator(at);
+ }
+
+ /**
+ * Return a PathIterator that doesn't include curves. Ours never does.
+ **/
+ public PathIterator getPathIterator(AffineTransform at, double flatness) {
+ return getPathIterator(at);
+ }
+
+ /**
+ * This inner class implements the PathIterator interface to describe
+ * the shape of a scribble. Since a Scribble is composed of arbitrary
+ * movetos and linetos, we simply return their coordinates
+ **/
+ public class ScribbleIterator implements PathIterator {
+ protected int i = 0; // Position in array
+ protected AffineTransform transform;
+
+ public ScribbleIterator(AffineTransform transform) {
+ this.transform = transform;
+ }
+
+ /** How to determine insideness and outsideness for this shape */
+ public int getWindingRule() { return PathIterator.WIND_NON_ZERO; }
+
+ /** Have we reached the end of the scribble path yet? */
+ public boolean isDone() { return i >= numPoints; }
+
+ /** Move on to the next segment of the path */
+ public void next() {
+ if (Double.isNaN(points[i])) i += 3;
+ else i += 2;
+ }
+
+ /**
+ * Get the coordinates of the current moveto or lineto as floats
+ **/
+ public int currentSegment(float[] coords) {
+ int retval;
+ if (Double.isNaN(points[i])) { // If its a moveto
+ coords[0] = (float)points[i+1];
+ coords[1] = (float)points[i+2];
+ retval = SEG_MOVETO;
+ }
+ else {
+ coords[0] = (float)points[i];
+ coords[1] = (float)points[i+1];
+ retval = SEG_LINETO;
+ }
+
+ // If a transform was specified, use it on the coordinates
+ if (transform != null) transform.transform(coords, 0, coords, 0,1);
+
+ return retval;
+ }
+
+ /**
+ * Get the coordinates of the current moveto or lineto as doubles
+ **/
+ public int currentSegment(double[] coords) {
+ int retval;
+ if (Double.isNaN(points[i])) {
+ coords[0] = points[i+1];
+ coords[1] = points[i+2];
+ retval = SEG_MOVETO;
+ }
+ else {
+ coords[0] = points[i];
+ coords[1] = points[i+1];
+ retval = SEG_LINETO;
+ }
+ if (transform != null) transform.transform(coords, 0, coords, 0,1);
+ return retval;
+ }
+ }
+
+ //====== The following methods implement the Transferable interface =====
+
+ // This is the custom DataFlavor for Scribble objects
+ public static DataFlavor scribbleDataFlavor =
+ new DataFlavor(Scribble.class, "Scribble");
+
+ // This is a list of the flavors we know how to work with
+ public static DataFlavor[] supportedFlavors = {
+ scribbleDataFlavor,
+ DataFlavor.stringFlavor
+ };
+
+ /** Return the data formats or "flavors" we know how to transfer */
+ public DataFlavor[] getTransferDataFlavors() {
+ return (DataFlavor[]) supportedFlavors.clone();
+ }
+
+ /** Check whether we support a given flavor */
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return (flavor.equals(scribbleDataFlavor) ||
+ flavor.equals(DataFlavor.stringFlavor));
+ }
+
+ /**
+ * Return the scribble data in the requested format, or throw an exception
+ * if we don't support the requested format
+ **/
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException
+ {
+ if (flavor.equals(scribbleDataFlavor)) { return this; }
+ else if (flavor.equals(DataFlavor.stringFlavor)) { return toString(); }
+ else throw new UnsupportedFlavorException(flavor);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java
new file mode 100644
index 0000000..5fde190
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.datatransfer;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.awt.datatransfer.*; // Clipboard, Transferable, DataFlavor, etc.
+
+/**
+ * This component allows the user to scribble in a window, and to cut and
+ * paste scribbles between windows. It stores mouse coordinates in a Scribble
+ * object, which is used to draw the scribble, and also to transfer the
+ * scribble to and from the clipboard. A JPopupMenu provides access to the
+ * cut, copy, and paste commands.
+ **/
+public class ScribbleCutAndPaste extends JComponent
+ implements ActionListener, ClipboardOwner
+{
+ Stroke linestyle = new BasicStroke(3.0f); // Draw with wide lines
+ Scribble scribble = new Scribble(); // Holds our scribble
+ Scribble selection; // A copy of the scribble as cut
+ JPopupMenu popup; // A menu for cut-and-paste
+
+ public ScribbleCutAndPaste() {
+ // Create the popup menu.
+ String[] labels = new String[] { "Clear", "Cut", "Copy", "Paste" };
+ String[] commands = new String[] { "clear", "cut", "copy", "paste" };
+ popup = new JPopupMenu(); // Create the menu
+ popup.setLabel("Edit");
+ for(int i = 0; i < labels.length; i++) {
+ JMenuItem mi = new JMenuItem(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 menu
+ }
+ // Finally, register the popup menu with the component it appears over
+ this.add(popup);
+
+ // Add event listeners to do the drawing and handle the popup
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+ if (e.isPopupTrigger())
+ popup.show((Component)e.getSource(),
+ e.getX(), e.getY());
+ else
+ scribble.moveto(e.getX(), e.getY()); // start new line
+ }
+ });
+
+ addMouseMotionListener(new MouseMotionAdapter() {
+ public void mouseDragged(MouseEvent e) {
+ // If this isn't mouse button 1, ignore it
+ if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == 0)
+ return;
+ scribble.lineto(e.getX(), e.getY()); // Add a line
+ repaint();
+ }
+ });
+ }
+
+ /**
+ * Draw the component.
+ * This method relies on Scribble which implements Shape.
+ **/
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setStroke(linestyle); // Specify wide lines
+ g2.draw(scribble); // Draw the scribble
+ }
+
+ /** 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();
+ }
+
+ /** Clear the scribble. Invoked by popup menu */
+ void clear() {
+ scribble = new Scribble(); // Get a new, empty scribble
+ repaint(); // And redraw everything.
+ }
+
+ /**
+ * Make a copy of the current Scribble and put it on the clipboard
+ * We can do this because Scribble implements Transferable
+ * The user invokes this method through the popup menu
+ **/
+ public void copy() {
+ // Get system clipboard
+ Clipboard c = this.getToolkit().getSystemClipboard();
+
+ // Make a copy of the Scribble object to put on the clipboard
+ selection = (Scribble) scribble.clone();
+
+ // Put the copy on the clipboard
+ c.setContents(selection, // What to put on the clipboard
+ this); // Who to notify when it is no longer there
+ }
+
+ /**
+ * The cut action is just like the copy action, except that we erase the
+ * current scribble after copying it to the clipboard
+ **/
+ public void cut() {
+ copy();
+ clear();
+ }
+
+ /**
+ * The user invokes this method through the popup menu.
+ * First, ask for the Transferable contents of the system clipboard.
+ * Then ask that Transferable object for the scribble data it represents.
+ * Try using both data flavors supported by the Scribble class.
+ * If it doesn't work, beep to tell the user it failed.
+ **/
+ public void paste() {
+ Clipboard c = this.getToolkit().getSystemClipboard(); // Get clipboard
+ Transferable t = c.getContents(this); // Get its contents
+
+ // Now try to get a Scribble object from the transferrable
+ Scribble pastedScribble = null;
+ try {
+ pastedScribble =
+ (Scribble)t.getTransferData(Scribble.scribbleDataFlavor);
+ }
+ catch (Exception e) { // UnsupportedFlavor, NullPointer, etc.
+ // If that didn't work, try asking for a string instead.
+ try {
+ String s = (String)t.getTransferData(DataFlavor.stringFlavor);
+ // We got a string, so try converting it to a Scribble
+ pastedScribble = Scribble.parse(s);
+ }
+ catch (Exception e2) { // UnsupportedFlavor, NumberFormat, etc.
+ // If we couldn't get and parse a string, give up
+ this.getToolkit().beep(); // Tell the user the paste failed
+ return;
+ }
+ }
+
+ // If we get here, we've retrieved a Scribble object from the clipboard
+ // Add it to the current scribble, and ask to be redrawn
+ scribble.append(pastedScribble);
+ repaint();
+ }
+
+ /**
+ * This method implements the ClipboardOwner interface. We specify a
+ * ClipboardOwner when we copy a Scribble to the clipboard. This method
+ * will be invoked when something else is copied to the clipboard, and
+ * bumps our data off the clipboard. When this method is invoked we no
+ * longer have to maintain our copied Scribble object, since it is no
+ * longer available to be pasted. Often, a component will highlight a
+ * selected object while it is on the clipboard, and will use this method
+ * to un-highlight the object when it is no longer on the clipboard.
+ **/
+ public void lostOwnership(Clipboard c, Transferable t) {
+ selection = null;
+ }
+
+ /** A simple main method to test the class. */
+ public static void main(String[] args) {
+ JFrame frame = new JFrame("ScribbleCutAndPaste");
+ ScribbleCutAndPaste s = new ScribbleCutAndPaste();
+ frame.getContentPane().add(s, BorderLayout.CENTER);
+ frame.setSize(400, 400);
+ frame.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java
new file mode 100644
index 0000000..4f5302f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.datatransfer;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.datatransfer.*; // Clipboard, Transferable, DataFlavor, etc.
+import java.awt.dnd.*;
+import java.util.ArrayList;
+
+/**
+ * This component can operate in two modes. In "draw mode", it allows the user
+ * to scribble with the mouse. In "drag mode", it allows the user to drag
+ * scribbles with the mouse. Regardless of the mode, it always allows
+ * scribbles to be dropped on it from other applications.
+ **/
+public class ScribbleDragAndDrop extends JComponent
+ implements DragGestureListener, // For recognizing the start of drags
+ DragSourceListener, // For processing drag source events
+ DropTargetListener, // For processing drop target events
+ MouseListener, // For processing mouse clicks
+ MouseMotionListener // For processing mouse drags
+{
+ ArrayList scribbles = new ArrayList(); // A list of Scribbles to draw
+ Scribble currentScribble; // The scribble in progress
+ Scribble beingDragged; // The scribble being dragged
+ DragSource dragSource; // A central DnD object
+ boolean dragMode; // Are we dragging or scribbling?
+
+ // These are some constants we use
+ static final int LINEWIDTH = 3;
+ static final BasicStroke linestyle = new BasicStroke(LINEWIDTH);
+ static final Border normalBorder = new BevelBorder(BevelBorder.LOWERED);
+ static final Border dropBorder = new BevelBorder(BevelBorder.RAISED);
+
+ /** The constructor: set up drag-and-drop stuff */
+ public ScribbleDragAndDrop() {
+ // Give ourselves a nice default border.
+ // We'll change this border during drag-and-drop.
+ setBorder(normalBorder);
+
+ // Register listeners to handle drawing
+ addMouseListener(this);
+ addMouseMotionListener(this);
+
+ // Create a DragSource and DragGestureRecognizer to listen for drags
+ // The DragGestureRecognizer will notify the DragGestureListener
+ // when the user tries to drag an object
+ dragSource = DragSource.getDefaultDragSource();
+ dragSource.createDefaultDragGestureRecognizer(this, // What component
+ DnDConstants.ACTION_COPY_OR_MOVE, // What drag types?
+ this);// the listener
+
+ // Create and set up a DropTarget that will listen for drags and
+ // drops over this component, and will notify the DropTargetListener
+ DropTarget dropTarget = new DropTarget(this, // component to monitor
+ this); // listener to notify
+ this.setDropTarget(dropTarget); // Tell the component about it.
+ }
+
+ /**
+ * The component draws itself by drawing each of the Scribble objects.
+ **/
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setStroke(linestyle); // Specify wide lines
+
+ int numScribbles = scribbles.size();
+ for(int i = 0; i < numScribbles; i++) {
+ Scribble s = (Scribble)scribbles.get(i);
+ g2.draw(s); // Draw the scribble
+ }
+ }
+
+ public void setDragMode(boolean dragMode) {
+ this.dragMode = dragMode;
+ }
+ public boolean getDragMode() { return dragMode; }
+
+ /**
+ * This method, and the following four methods are from the MouseListener
+ * interface. If we're in drawing mode, this method handles mouse down
+ * events and starts a new scribble.
+ **/
+ public void mousePressed(MouseEvent e) {
+ if (dragMode) return;
+ currentScribble = new Scribble();
+ scribbles.add(currentScribble);
+ currentScribble.moveto(e.getX(), e.getY());
+ }
+ public void mouseReleased(MouseEvent e) {}
+ public void mouseClicked(MouseEvent e) {}
+ public void mouseEntered(MouseEvent e) {}
+ public void mouseExited(MouseEvent e) {}
+
+ /**
+ * This method and mouseMoved() below are from the MouseMotionListener
+ * interface. If we're in drawing mode, this method adds a new point
+ * to the current scribble and requests a redraw
+ **/
+ public void mouseDragged(MouseEvent e) {
+ if (dragMode) return;
+ currentScribble.lineto(e.getX(), e.getY());
+ repaint();
+ }
+ public void mouseMoved(MouseEvent e) {}
+
+ /**
+ * This method implements the DragGestureListener interface. It will be
+ * invoked when the DragGestureRecognizer thinks that the user has
+ * initiated a drag. If we're not in drawing mode, then this method will
+ * try to figure out which Scribble object is being dragged, and will
+ * initiate a drag on that object.
+ **/
+ public void dragGestureRecognized(DragGestureEvent e) {
+ // Don't drag if we're not in drag mode
+ if (!dragMode) return;
+
+ // Figure out where the drag started
+ MouseEvent inputEvent = (MouseEvent) e.getTriggerEvent();
+ int x = inputEvent.getX();
+ int y = inputEvent.getY();
+
+ // Figure out which scribble was clicked on, if any by creating a
+ // small rectangle around the point and testing for intersection.
+ Rectangle r = new Rectangle (x-LINEWIDTH, y-LINEWIDTH,
+ LINEWIDTH*2, LINEWIDTH*2);
+ int numScribbles = scribbles.size();
+ for(int i = 0; i < numScribbles; i++) { // Loop through the scribbles
+ Scribble s = (Scribble) scribbles.get(i);
+ if (s.intersects(r)) {
+ // The user started the drag on top of this scribble, so
+ // start to drag it.
+
+ // First, remember which scribble is being dragged, so we can
+ // delete it later (if this is a move rather than a copy)
+ beingDragged = s;
+
+ // Next, create a copy that will be the one dragged
+ Scribble dragScribble = (Scribble) s.clone();
+ // Adjust the origin to the point the user clicked on.
+ dragScribble.translate(-x, -y);
+
+ // Choose a cursor based on the type of drag the user initiated
+ Cursor cursor;
+ switch(e.getDragAction()) {
+ case DnDConstants.ACTION_COPY:
+ cursor = DragSource.DefaultCopyDrop;
+ break;
+ case DnDConstants.ACTION_MOVE:
+ cursor = DragSource.DefaultMoveDrop;
+ break;
+ default:
+ return; // We only support move and copys
+ }
+
+ // Some systems allow us to drag an image along with the
+ // cursor. If so, create an image of the scribble to drag
+ if (dragSource.isDragImageSupported()) {
+ Rectangle scribbleBox = dragScribble.getBounds();
+ Image dragImage = this.createImage(scribbleBox.width,
+ scribbleBox.height);
+ Graphics2D g = (Graphics2D)dragImage.getGraphics();
+ g.setColor(new Color(0,0,0,0)); // transparent background
+ g.fillRect(0, 0, scribbleBox.width, scribbleBox.height);
+ g.setColor(Color.black);
+ g.setStroke(linestyle);
+ g.translate(-scribbleBox.x, -scribbleBox.y);
+ g.draw(dragScribble);
+ Point hotspot = new Point(-scribbleBox.x, -scribbleBox.y);
+
+ // Now start dragging, using the image.
+ e.startDrag(cursor, dragImage, hotspot, dragScribble,this);
+ }
+ else {
+ // Or start the drag without an image
+ e.startDrag(cursor, dragScribble,this);
+ }
+ // After we've started dragging one scribble, stop looking
+ return;
+ }
+ }
+ }
+
+ /**
+ * This method, and the four unused methods that follow it implement the
+ * DragSourceListener interface. dragDropEnd() is invoked when the user
+ * drops the scribble she was dragging. If the drop was successful, and
+ * if the user did a "move" rather than a "copy", then we delete the
+ * dragged scribble from the list of scribbles to draw.
+ **/
+ public void dragDropEnd(DragSourceDropEvent e) {
+ if (!e.getDropSuccess()) return;
+ int action = e.getDropAction();
+ if (action == DnDConstants.ACTION_MOVE) {
+ scribbles.remove(beingDragged);
+ beingDragged = null;
+ repaint();
+ }
+ }
+
+ // These methods are also part of DragSourceListener.
+ // They are invoked at interesting points during the drag, and can be
+ // used to perform "drag over" effects, such as changing the drag cursor
+ // or drag image.
+ public void dragEnter(DragSourceDragEvent e) {}
+ public void dragExit(DragSourceEvent e) {}
+ public void dropActionChanged(DragSourceDragEvent e) {}
+ public void dragOver(DragSourceDragEvent e) {}
+
+ // The next five methods implement DropTargetListener
+
+ /**
+ * This method is invoked when the user first drags something over us.
+ * If we understand the data type being dragged, then call acceptDrag()
+ * to tell the system that we're receptive. Also, we change our border
+ * as a "drag under" effect to signal that we can accept the drop.
+ **/
+ public void dragEnter(DropTargetDragEvent e) {
+ if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor) ||
+ e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ this.setBorder(dropBorder);
+ }
+ }
+
+ /** The user is no longer dragging over us, so restore the border */
+ public void dragExit(DropTargetEvent e) { this.setBorder(normalBorder); }
+
+ /**
+ * This is the key method of DropTargetListener. It is invoked when the
+ * user drops something on us.
+ **/
+ public void drop(DropTargetDropEvent e) {
+ this.setBorder(normalBorder); // Restore the default border
+
+ // First, check whether we understand the data that was dropped.
+ // If we supports our data flavors, accept the drop, otherwise reject.
+ if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor) ||
+ e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ }
+ else {
+ e.rejectDrop();
+ return;
+ }
+
+ // We've accepted the drop, so now we attempt to get the dropped data
+ // from the Transferable object.
+ Transferable t = e.getTransferable(); // Holds the dropped data
+ Scribble droppedScribble; // This will hold the Scribble object
+
+ // First, try to get the data directly as a scribble object
+ try {
+ droppedScribble =
+ (Scribble) t.getTransferData(Scribble.scribbleDataFlavor);
+ }
+ catch (Exception ex) { // unsupported flavor, IO exception, etc.
+ // If that doesn't work, try to get it as a String and parse it
+ try {
+ String s = (String) t.getTransferData(DataFlavor.stringFlavor);
+ droppedScribble = Scribble.parse(s);
+ }
+ catch(Exception ex2) {
+ // If we still couldn't get the data, tell the system we failed
+ e.dropComplete(false);
+ return;
+ }
+ }
+
+ // If we get here, we've got the Scribble object
+ Point p = e.getLocation(); // Where did the drop happen?
+ droppedScribble.translate(p.getX(), p.getY()); // Move it there
+ scribbles.add(droppedScribble); // add to display list
+ repaint(); // ask for redraw
+ e.dropComplete(true); // signal success!
+ }
+
+ // These are unused DropTargetListener methods
+ public void dragOver(DropTargetDragEvent e) {}
+ public void dropActionChanged(DropTargetDragEvent e) {}
+
+ /**
+ * The main method. Creates a simple application using this class. Note
+ * the buttons for switching between draw mode and drag mode.
+ **/
+ public static void main(String[] args) {
+ // Create a frame and put a scribble pane in it
+ JFrame frame = new JFrame("ScribbleDragAndDrop");
+ final ScribbleDragAndDrop scribblePane = new ScribbleDragAndDrop();
+ frame.getContentPane().add(scribblePane, BorderLayout.CENTER);
+
+ // Create two buttons for switching modes
+ JToolBar toolbar = new JToolBar();
+ ButtonGroup group = new ButtonGroup();
+ JToggleButton draw = new JToggleButton("Draw");
+ JToggleButton drag = new JToggleButton("Drag");
+ draw.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ scribblePane.setDragMode(false);
+ }
+ });
+ drag.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ scribblePane.setDragMode(true);
+ }
+ });
+ group.add(draw); group.add(drag);
+ toolbar.add(draw); toolbar.add(drag);
+ frame.getContentPane().add(toolbar, BorderLayout.NORTH);
+
+ // Start off in drawing mode
+ draw.setSelected(true);
+ scribblePane.setDragMode(false);
+
+ // Pop up the window
+ frame.setSize(400, 400);
+ frame.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java b/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java
new file mode 100644
index 0000000..874e30d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.datatransfer;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.datatransfer.*;
+
+/**
+ * This program demonstrates how to add simple copy-and-paste capabilities
+ * to an application.
+ **/
+public class SimpleCutAndPaste extends Frame implements ClipboardOwner
+{
+ /** The main method creates a frame and pops it up. */
+ public static void main(String[] args) {
+ Frame f = new SimpleCutAndPaste();
+ f.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+ f.pack();
+ f.setVisible(true);
+ }
+
+ /** The text field that holds the text that is cut or pasted */
+ TextField field;
+
+ /**
+ * The constructor builds a very simple test GUI, and registers this object
+ * as the ActionListener for the buttons
+ **/
+ public SimpleCutAndPaste() {
+ super("SimpleCutAndPaste"); // Window title
+ this.setFont(new Font("SansSerif", Font.PLAIN, 18)); // Use a big font
+
+ // Set up the Cut button
+ Button copy = new Button("Copy");
+ copy.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { copy(); }
+ });
+ this.add(copy, "West");
+
+ // Set up the Paste button
+ Button paste = new Button("Paste");
+ paste.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { paste(); }
+ });
+ this.add(paste, "East");
+
+ // Set up the text field that they both operate on
+ field = new TextField();
+ this.add(field, "North");
+ }
+
+ /**
+ * This method takes the current contents of the text field, creates a
+ * StringSelection object to represent that string, and puts the
+ * StringSelection onto the clipboard
+ **/
+ public void copy() {
+ // Get the currently displayed value
+ String s = field.getText();
+
+ // Create a StringSelection object to represent the text.
+ // StringSelection is a pre-defined class that implements
+ // Transferable and ClipboardOwner for us.
+ StringSelection ss = new StringSelection(s);
+
+ // Now set the StringSelection object as the contents of the clipboard
+ // Also specify that we're the clipboard owner
+ this.getToolkit().getSystemClipboard().setContents(ss, this);
+
+ // Highlight the text to indicate it is on the clipboard.
+ field.selectAll();
+ }
+
+ /**
+ * Get the contents of the clipboard, and, if we understand the type,
+ * display the contents. This method understands strings and file lists.
+ **/
+ public void paste() {
+ // Get the clipboard
+ Clipboard c = this.getToolkit().getSystemClipboard();
+
+ // Get the contents of the clipboard, as a Transferable object
+ Transferable t = c.getContents(this);
+
+ // Find out what kind of data is on the clipboard
+ try {
+ if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ // If it is a string, then get and display the string
+ String s = (String) t.getTransferData(DataFlavor.stringFlavor);
+ field.setText(s);
+
+ }
+ else if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
+ // If it is a list of File objects, get the list and display
+ // the name of the first file on the list
+ java.util.List files = (java.util.List)
+ t.getTransferData(DataFlavor.javaFileListFlavor);
+ java.io.File file = (java.io.File)files.get(0);
+ field.setText(file.getName());
+ }
+ }
+ // If anything goes wrong with the transfer, just beep and do nothing.
+ catch (Exception e) { this.getToolkit().beep(); }
+ }
+
+ /**
+ * This method implements the ClipboardOwner interface. It is called when
+ * something else is placed on the clipboard.
+ **/
+ public void lostOwnership(Clipboard c, Transferable t) {
+ // Un-highlight the text field, since we don't "own" the clipboard
+ // anymore, and the text is no longer available to be pasted.
+ field.select(0,0);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java b/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java
new file mode 100644
index 0000000..dc5effa
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+/** A demonstration of anti-aliasing */
+public class AntiAlias implements GraphicsExample {
+ static final int WIDTH = 650, HEIGHT = 350; // Size of our example
+ public String getName() {return "AntiAliasing";} // From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ BufferedImage image = // Create an off-screen image
+ new BufferedImage(65, 35, BufferedImage.TYPE_INT_RGB);
+ Graphics2D ig = image.createGraphics(); // Get its Graphics for drawing
+
+ // Set the background to a gradient fill. The varying color of
+ // the background helps to demonstrate the anti-aliasing effect
+ ig.setPaint(new GradientPaint(0,0,Color.black,65,35,Color.white));
+ ig.fillRect(0, 0, 65, 35);
+
+ // Set drawing attributes for the foreground.
+ // Most importantly, turn on anti-aliasing.
+ ig.setStroke(new BasicStroke(2.0f)); // 2-pixel lines
+ ig.setFont(new Font("Serif", Font.BOLD, 18)); // 18-point font
+ ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // Anti-alias!
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // Now draw pure blue text and a pure red oval
+ ig.setColor(Color.blue);
+ ig.drawString("Java", 9, 22);
+ ig.setColor(Color.red);
+ ig.drawOval(1, 1, 62, 32);
+
+ // Finally, scale the image by a factor of 10 and display it
+ // in the window. This will allow us to see the anti-aliased pixels
+ g.drawImage(image, AffineTransform.getScaleInstance(10, 10), c);
+
+ // Draw the image one more time at its original size, for comparison
+ g.drawImage(image, 0, 0, c);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html
new file mode 100644
index 0000000..bf650dd
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java
new file mode 100644
index 0000000..93801d4
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.applet.*;
+import java.awt.*;
+
+/** An applet that displays a simple animation */
+public class BouncingCircle extends Applet implements Runnable {
+ int x = 150, y = 50, r = 50; // Position and radius of the circle
+ int dx = 11, dy = 7; // Trajectory of circle
+ Thread animator; // The thread that performs the animation
+ volatile boolean pleaseStop; // A flag to ask the thread to stop
+
+ /** This method simply draws the circle at its current position */
+ public void paint(Graphics g) {
+ g.setColor(Color.red);
+ g.fillOval(x-r, y-r, r*2, r*2);
+ }
+
+ /**
+ * This method moves (and bounces) the circle and then requests a redraw.
+ * The animator thread calls this method periodically.
+ **/
+ public void animate() {
+ // Bounce if we've hit an edge.
+ Rectangle bounds = getBounds();
+ if ((x - r + dx < 0) || (x + r + dx > bounds.width)) dx = -dx;
+ if ((y - r + dy < 0) || (y + r + dy > bounds.height)) dy = -dy;
+
+ // Move the circle.
+ x += dx; y += dy;
+
+ // Ask the browser to call our paint() method to draw the circle
+ // at its new position.
+ repaint();
+ }
+
+ /**
+ * This method is from the Runnable interface. It is the body of the
+ * thread that performs the animation. The thread itself is created
+ * and started in the start() method.
+ **/
+ public void run() {
+ while(!pleaseStop) { // Loop until we're asked to stop
+ animate(); // Update and request redraw
+ try { Thread.sleep(100); } // Wait 100 milliseconds
+ catch(InterruptedException e) {} // Ignore interruptions
+ }
+ }
+
+ /** Start animating when the browser starts the applet */
+ public void start() {
+ animator = new Thread(this); // Create a thread
+ pleaseStop = false; // Don't ask it to stop now
+ animator.start(); // Start the thread.
+ // The thread that called start now returns to its caller.
+ // Meanwhile, the new animator thread has called the run() method
+ }
+
+ /** Stop animating when the browser stops the applet */
+ public void stop() {
+ // Set the flag that causes the run() method to end
+ pleaseStop = true;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html
new file mode 100644
index 0000000..0df1926
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html
@@ -0,0 +1,5 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java
new file mode 100644
index 0000000..be2d513
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.applet.*;
+import java.awt.*;
+
+/** An applet that demonstrates the Color class */
+public class ColorGradient extends Applet {
+ Color startColor, endColor; // Start and end color of the gradient
+ Font bigFont; // A font we'll use
+
+ /**
+ * Get the gradient start and end colors as applet parameter values, and
+ * parse them using Color.decode(). If they are malformed, use white.
+ **/
+ public void init() {
+ try {
+ startColor = Color.decode(getParameter("startColor"));
+ endColor = Color.decode(getParameter("endColor"));
+ }
+ catch (NumberFormatException e) {
+ startColor = endColor = Color.white;
+ }
+ bigFont = new Font("Helvetica", Font.BOLD, 72);
+ }
+
+ /** Draw the applet. The interesting code is in fillGradient() below */
+ public void paint(Graphics g) {
+ fillGradient(this, g, startColor, endColor); // display the gradient
+ g.setFont(bigFont); // set a font
+ g.setColor(new Color(100, 100, 200)); // light blue
+ g.drawString("Colors!", 100, 100); // draw something interesting
+ }
+
+ /**
+ * Draw a color gradient from the top of the specified component to the
+ * bottom. Start with the start color and change smoothly to the end
+ **/
+ public void fillGradient(Component c, Graphics g, Color start, Color end) {
+ Rectangle bounds = this.getBounds(); // How big is the component?
+ // Get the red, green, and blue components of the start and end
+ // colors as floats between 0.0 and 1.0. Note that the Color class
+ // also works with int values between 0 and 255
+ float r1 = start.getRed()/255.0f;
+ float g1 = start.getGreen()/255.0f;
+ float b1 = start.getBlue()/255.0f;
+ float r2 = end.getRed()/255.0f;
+ float g2 = end.getGreen()/255.0f;
+ float b2 = end.getBlue()/255.0f;
+ // Figure out how much each component should change at each y value
+ float dr = (r2-r1)/bounds.height;
+ float dg = (g2-g1)/bounds.height;
+ float db = (b2-b1)/bounds.height;
+
+ // Now loop once for each row of pixels in the component
+ for(int y = 0; y < bounds.height; y++) {
+ g.setColor(new Color(r1, g1, b1)); // Set the color of the row
+ g.drawLine(0, y, bounds.width-1, y); // Draw the row
+ r1 += dr; g1 += dg; b1 += db; // Increment color components
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java b/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java
new file mode 100644
index 0000000..eb7a7b1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+public class CompositeEffects implements GraphicsExample {
+ Image cover; // The image we'll be displaying, and its size
+ static final int COVERWIDTH = 127, COVERHEIGHT = 190;
+
+ /** This constructor loads the cover image */
+ public CompositeEffects() {
+ java.net.URL imageurl = this.getClass().getResource("cover.gif");
+ cover = new javax.swing.ImageIcon(imageurl).getImage();
+ }
+
+ // These are basic GraphicsExample methods
+ public String getName() {return "Composite Effects";}
+ public int getWidth() { return 6*COVERWIDTH + 70; }
+ public int getHeight() { return COVERHEIGHT + 35; }
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // fill the background
+ g.setPaint(new Color(175, 175, 175));
+ g.fillRect(0, 0, getWidth(), getHeight());
+
+ // Set text attributes
+ g.setColor(Color.black);
+ g.setFont(new Font("SansSerif", Font.BOLD, 12));
+
+ // Draw the unmodified image
+ g.translate(10, 10);
+ g.drawImage(cover, 0, 0, c);
+ g.drawString("SRC_OVER", 0, COVERHEIGHT+15);
+
+ // Draw the cover again, using AlphaComposite to make the opaque
+ // colors of the image 50% translucent
+ g.translate(COVERWIDTH+10, 0);
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+ 0.5f));
+ g.drawImage(cover, 0, 0, c);
+
+ // Restore the pre-defined default Composite for the screen, so
+ // opaque colors stay opaque.
+ g.setComposite(AlphaComposite.SrcOver);
+ // Label the effect
+ g.drawString("SRC_OVER, 50%", 0, COVERHEIGHT+15);
+
+ // Now get an offscreen image to work with. In order to achieve
+ // certain compositing effects, the drawing surface must support
+ // transparency. Onscreen drawing surfaces cannot, so we have to do the
+ // compositing in an offscreen image that is specially created to have
+ // an "alpha channel", then copy the final result to the screen.
+ BufferedImage offscreen =
+ new BufferedImage(COVERWIDTH, COVERHEIGHT,
+ BufferedImage.TYPE_INT_ARGB);
+
+ // First, fill the image with a color gradient background that varies
+ // left-to-right from opaque to transparent yellow
+ Graphics2D osg = offscreen.createGraphics();
+ osg.setPaint(new GradientPaint(0, 0, Color.yellow,
+ COVERWIDTH, 0,
+ new Color(255, 255, 0, 0)));
+ osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT);
+
+ // Now copy the cover image on top of this, but use the DstOver rule
+ // which draws it "underneath" the existing pixels, and allows the
+ // image to show depending on the transparency of those pixels.
+ osg.setComposite(AlphaComposite.DstOver);
+ osg.drawImage(cover, 0, 0, c);
+
+ // And display this composited image on the screen. Note that the
+ // image is opaque and that none of the screen background shows through
+ g.translate(COVERWIDTH+10, 0);
+ g.drawImage(offscreen, 0, 0, c);
+ g.drawString("DST_OVER", 0, COVERHEIGHT+15);
+
+ // Now start over and do a new effect with the off-screen image.
+ // First, fill the offscreen image with a new color gradient. We
+ // don't care about the colors themselves; we just want the
+ // translucency of the background to vary. We use opaque black to
+ // transparent black. Note that since we've already used this offscreen
+ // image, we set the composite to Src, we can fill the image and
+ // ignore anything that is already there.
+ osg.setComposite(AlphaComposite.Src);
+ osg.setPaint(new GradientPaint(0, 0, Color.black,
+ COVERWIDTH, COVERHEIGHT,
+ new Color(0, 0, 0, 0)));
+ osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT);
+
+ // Now set the compositing type to SrcIn, so colors come from the
+ // source, but translucency comes from the destination
+ osg.setComposite(AlphaComposite.SrcIn);
+
+ // Draw our loaded image into the off-screen image, compositing it.
+ osg.drawImage(cover, 0, 0, c);
+
+ // And then copy our off-screen image to the screen. Note that the
+ // image is translucent and some of the image shows through.
+ g.translate(COVERWIDTH+10, 0);
+ g.drawImage(offscreen, 0, 0, c);
+ g.drawString("SRC_IN", 0, COVERHEIGHT+15);
+
+ // If we do the same thing but use SrcOut, then the resulting image
+ // will have the inverted translucency values of the destination
+ osg.setComposite(AlphaComposite.Src);
+ osg.setPaint(new GradientPaint(0, 0, Color.black,
+ COVERWIDTH, COVERHEIGHT,
+ new Color(0, 0, 0, 0)));
+ osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT);
+ osg.setComposite(AlphaComposite.SrcOut);
+ osg.drawImage(cover, 0, 0, c);
+ g.translate(COVERWIDTH+10, 0);
+ g.drawImage(offscreen, 0, 0, c);
+ g.drawString("SRC_OUT", 0, COVERHEIGHT+15);
+
+ // Here's a cool effect; it has nothing to do with compositing, but
+ // uses an arbitrary shape to clip the image. It uses Area to combine
+ // shapes into more complicated ones.
+ g.translate(COVERWIDTH+10, 0);
+ Shape savedClip = g.getClip(); // Save current clipping region
+ // Create a shape to use as the new clipping region.
+ // Begin with an ellipse
+ Area clip = new Area(new Ellipse2D.Float(0,0,COVERWIDTH,COVERHEIGHT));
+ // Intersect with a rectangle, truncating the ellipse.
+ clip.intersect(new Area(new Rectangle(5,5,
+ COVERWIDTH-10,COVERHEIGHT-10)));
+ // Then subtract an ellipse from the bottom of the truncated ellipse.
+ clip.subtract(new Area(new Ellipse2D.Float(COVERWIDTH/2-40,
+ COVERHEIGHT-20, 80, 40)));
+ // Use the resulting shape as the new clipping region
+ g.clip(clip);
+ // Then draw the image through this clipping region
+ g.drawImage(cover, 0, 0, c);
+ // Restore the old clipping region so we can label the effect
+ g.setClip(savedClip);
+ g.drawString("Clipping", 0, COVERHEIGHT+15);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java b/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java
new file mode 100644
index 0000000..20a59ef
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.font.*;
+
+/** A demonstration of writing custom Stroke classes */
+public class CustomStrokes implements GraphicsExample {
+ static final int WIDTH = 750, HEIGHT = 200; // Size of our example
+ public String getName() {return "Custom Strokes";} // From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ // These are the various stroke objects we'll demonstrate
+ Stroke[] strokes = new Stroke[] {
+ new BasicStroke(4.0f), // The standard, predefined stroke
+ new NullStroke(), // A Stroke that does nothing
+ new DoubleStroke(8.0f, 2.0f), // A Stroke that strokes twice
+ new ControlPointsStroke(2.0f), // Shows the vertices & control points
+ new SloppyStroke(2.0f, 3.0f) // Perturbs the shape before stroking
+ };
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // Get a shape to work with. Here we'll use the letter B
+ Font f = new Font("Serif", Font.BOLD, 200);
+ GlyphVector gv = f.createGlyphVector(g.getFontRenderContext(), "B");
+ Shape shape = gv.getOutline();
+
+ // Set drawing attributes and starting position
+ g.setColor(Color.black);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.translate(10, 175);
+
+ // Draw the shape once with each stroke
+ for(int i = 0; i < strokes.length; i++) {
+ g.setStroke(strokes[i]); // set the stroke
+ g.draw(shape); // draw the shape
+ g.translate(140,0); // move to the right
+ }
+ }
+}
+
+/**
+ * This Stroke implementation does nothing. Its createStrokedShape()
+ * method returns an unmodified shape. Thus, drawing a shape with
+ * this Stroke is the same as filling that shape!
+ **/
+class NullStroke implements Stroke {
+ public Shape createStrokedShape(Shape s) { return s; }
+}
+
+/**
+ * This Stroke implementation applies a BasicStroke to a shape twice.
+ * If you draw with this Stroke, then instead of outlining the shape,
+ * you're outlining the outline of the shape.
+ **/
+class DoubleStroke implements Stroke {
+ BasicStroke stroke1, stroke2; // the two strokes to use
+ public DoubleStroke(float width1, float width2) {
+ stroke1 = new BasicStroke(width1); // Constructor arguments specify
+ stroke2 = new BasicStroke(width2); // the line widths for the strokes
+ }
+
+ public Shape createStrokedShape(Shape s) {
+ // Use the first stroke to create an outline of the shape
+ Shape outline = stroke1.createStrokedShape(s);
+ // Use the second stroke to create an outline of that outline.
+ // It is this outline of the outline that will be filled in
+ return stroke2.createStrokedShape(outline);
+ }
+}
+
+/**
+ * This Stroke implementation strokes the shape using a thin line, and
+ * also displays the end points and Bezier curve control points of all
+ * the line and curve segments that make up the shape. The radius
+ * argument to the constructor specifies the size of the control point
+ * markers. Note the use of PathIterator to break the shape down into
+ * its segments, and of GeneralPath to build up the stroked shape.
+ **/
+class ControlPointsStroke implements Stroke {
+ float radius; // how big the control point markers should be
+ public ControlPointsStroke(float radius) { this.radius = radius; }
+
+ public Shape createStrokedShape(Shape shape) {
+ // Start off by stroking the shape with a thin line. Store the
+ // resulting shape in a GeneralPath object so we can add to it.
+ GeneralPath strokedShape =
+ new GeneralPath(new BasicStroke(1.0f).createStrokedShape(shape));
+
+ // Use a PathIterator object to iterate through each of the line and
+ // curve segments of the shape. For each one, mark the endpoint and
+ // control points (if any) by adding a rectangle to the GeneralPath
+ float[] coords = new float[6];
+ for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) {
+ int type = i.currentSegment(coords);
+ Shape s = null, s2 = null, s3 = null;
+ switch(type) {
+ case PathIterator.SEG_CUBICTO:
+ markPoint(strokedShape, coords[4], coords[5]); // falls through
+ case PathIterator.SEG_QUADTO:
+ markPoint(strokedShape, coords[2], coords[3]); // falls through
+ case PathIterator.SEG_MOVETO:
+ case PathIterator.SEG_LINETO:
+ markPoint(strokedShape, coords[0], coords[1]); // falls through
+ case PathIterator.SEG_CLOSE:
+ break;
+ }
+ }
+
+ return strokedShape;
+ }
+
+ /** Add a small square centered at (x,y) to the specified path */
+ void markPoint(GeneralPath path, float x, float y) {
+ path.moveTo(x-radius, y-radius); // Begin a new sub-path
+ path.lineTo(x+radius, y-radius); // Add a line segment to it
+ path.lineTo(x+radius, y+radius); // Add a second line segment
+ path.lineTo(x-radius, y+radius); // And a third
+ path.closePath(); // Go back to last moveTo position
+ }
+}
+
+/**
+ * This Stroke implementation randomly perturbs the line and curve segments
+ * that make up a Shape, and then strokes that perturbed shape. It uses
+ * PathIterator to loop through the Shape and GeneralPath to build up the
+ * modified shape. Finally, it uses a BasicStroke to stroke the modified
+ * shape. The result is a "sloppy" looking shape.
+ **/
+class SloppyStroke implements Stroke {
+ BasicStroke stroke;
+ float sloppiness;
+ public SloppyStroke(float width, float sloppiness) {
+ this.stroke = new BasicStroke(width); // Used to stroke modified shape
+ this.sloppiness = sloppiness; // How sloppy should we be?
+ }
+
+ public Shape createStrokedShape(Shape shape) {
+ GeneralPath newshape = new GeneralPath(); // Start with an empty shape
+
+ // Iterate through the specified shape, perturb its coordinates, and
+ // use them to build up the new shape.
+ float[] coords = new float[6];
+ for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) {
+ int type = i.currentSegment(coords);
+ switch(type) {
+ case PathIterator.SEG_MOVETO:
+ perturb(coords, 2);
+ newshape.moveTo(coords[0], coords[1]);
+ break;
+ case PathIterator.SEG_LINETO:
+ perturb(coords, 2);
+ newshape.lineTo(coords[0], coords[1]);
+ break;
+ case PathIterator.SEG_QUADTO:
+ perturb(coords, 4);
+ newshape.quadTo(coords[0], coords[1], coords[2], coords[3]);
+ break;
+ case PathIterator.SEG_CUBICTO:
+ perturb(coords, 6);
+ newshape.curveTo(coords[0], coords[1], coords[2], coords[3],
+ coords[4], coords[5]);
+ break;
+ case PathIterator.SEG_CLOSE:
+ newshape.closePath();
+ break;
+ }
+ }
+
+ // Finally, stroke the perturbed shape and return the result
+ return stroke.createStrokedShape(newshape);
+ }
+
+ // Randomly modify the specified number of coordinates, by an amount
+ // specified by the sloppiness field.
+ void perturb(float[] coords, int numCoords) {
+ for(int i = 0; i < numCoords; i++)
+ coords[i] += (float)((Math.random()*2-1.0)*sloppiness);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/FontList.html b/src/main/java/com/davidflanagan/examples/graphics/FontList.html
new file mode 100644
index 0000000..b4e58da
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/FontList.html
@@ -0,0 +1,3 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/graphics/FontList.java b/src/main/java/com/davidflanagan/examples/graphics/FontList.java
new file mode 100644
index 0000000..e002eec
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/FontList.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.applet.*;
+import java.awt.*;
+
+/**
+ * An applet that displays the standard fonts and styles available in Java 1.1
+ **/
+public class FontList extends Applet {
+ // The available font families
+ String[] families = {"Serif", // "TimesRoman" in Java 1.0
+ "SansSerif", // "Helvetica" in Java 1.0
+ "Monospaced"}; // "Courier" in Java 1.0
+
+ // The available font styles and names for each one
+ int[] styles = {Font.PLAIN, Font.ITALIC, Font.BOLD, Font.ITALIC+Font.BOLD};
+ String[] stylenames = {"Plain", "Italic", "Bold", "Bold Italic"};
+
+ // Draw the applet.
+ public void paint(Graphics g) {
+ for(int f=0; f < families.length; f++) { // for each family
+ for(int s = 0; s < styles.length; s++) { // for each style
+ Font font = new Font(families[f],styles[s],18); // create font
+ g.setFont(font); // set font
+ String name = families[f] + " " +stylenames[s]; // create name
+ g.drawString(name, 20, (f*4 + s + 1) * 20); // display name
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java b/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java
new file mode 100644
index 0000000..34214a0
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+/**
+ * This is an abstract Paint implementation that computes the color of each
+ * point to be painted by passing the coordinates of the point to the calling
+ * the abstract methods computeRed(), computeGreen(), computeBlue() and
+ * computeAlpha(). Subclasses must implement these three methods to perform
+ * whatever type of painting is desired. Note that while this class provides
+ * great flexibility, it is not very efficient.
+ **/
+public abstract class GenericPaint implements Paint {
+ /** This is the main Paint method; all it does is return a PaintContext */
+ public PaintContext createContext(ColorModel cm,
+ Rectangle deviceBounds,
+ Rectangle2D userBounds,
+ AffineTransform xform,
+ RenderingHints hints) {
+ return new GenericPaintContext(xform);
+ }
+
+ /** This paint class allows translucent painting */
+ public int getTransparency() { return TRANSLUCENT; }
+
+ /**
+ * These three methods return the red, green, blue, and alpha values of
+ * the pixel at appear at the specified user-space coordinates. The return
+ * value of each method should be between 0 and 255.
+ **/
+ public abstract int computeRed(double x, double y);
+ public abstract int computeGreen(double x, double y);
+ public abstract int computeBlue(double x, double y);
+ public abstract int computeAlpha(double x, double y);
+
+ /**
+ * The PaintContext class does all the work of painting
+ **/
+ class GenericPaintContext implements PaintContext {
+ ColorModel model; // The color model
+ Point2D origin, unitVectorX, unitVectorY; // For device-to-user xform
+
+ public GenericPaintContext(AffineTransform userToDevice) {
+ // Our color model packs ARGB values into a single int
+ model = new DirectColorModel(32, 0x00ff0000,0x0000ff00,
+ 0x000000ff, 0xff000000);
+ // The specified transform converts user to device pixels
+ // We need to figure out the reverse transformation, so we
+ // can compute the user space coordinates of each device pixel
+ try {
+ AffineTransform deviceToUser = userToDevice.createInverse();
+ origin = deviceToUser.transform(new Point(0,0), null);
+ unitVectorX = deviceToUser.deltaTransform(new Point(1,0),null);
+ unitVectorY = deviceToUser.deltaTransform(new Point(0,1),null);
+ }
+ catch (NoninvertibleTransformException e) {
+ // If we can't invert the transform, just use device space
+ origin = new Point(0,0);
+ unitVectorX = new Point(1,0);
+ unitVectorY = new Point(0, 1);
+ }
+ }
+
+ /** Return the color model used by this Paint implementation */
+ public ColorModel getColorModel() { return model; }
+
+ /**
+ * This is the main method of PaintContext. It must return a Raster
+ * that contains fill data for the specified rectangle. It creates a
+ * raster of the specified size, and loops through the device pixels.
+ * For each one, it converts the coordinates to user space, then calls
+ * the computeRed(), computeGreen() and computeBlue() methods to
+ * obtain the appropriate color for the device pixel.
+ **/
+ public Raster getRaster(int x, int y, int w, int h) {
+ WritableRaster raster = model.createCompatibleWritableRaster(w,h);
+ int[] colorComponents = new int[4];
+ for(int j = 0; j < h; j++) { // Loop through rows of raster
+ int deviceY = y + j;
+ for(int i = 0; i < w; i++) { // Loop through columns
+ int deviceX = x + i;
+ // Convert device coordinate to user-space coordinate
+ double userX = origin.getX() +
+ deviceX * unitVectorX.getX() +
+ deviceY * unitVectorY.getX();
+ double userY = origin.getY() +
+ deviceX * unitVectorX.getY() +
+ deviceY * unitVectorY.getY();
+ // Compute the color components of the pixel
+ colorComponents[0] = computeRed(userX, userY);
+ colorComponents[1] = computeGreen(userX, userY);
+ colorComponents[2] = computeBlue(userX, userY);
+ colorComponents[3] = computeAlpha(userX, userY);
+ // Set the color of the pixel
+ raster.setPixel(i, j, colorComponents);
+ }
+ }
+ return raster;
+ }
+
+ /** Called when the PaintContext is no longer needed. */
+ public void dispose() {}
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java
new file mode 100644
index 0000000..e8abc3d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+
+/**
+ * This interface defines the methods that must be implemented by an
+ * object that is to be displayed by the GraphicsExampleFrame object
+ */
+public interface GraphicsExample {
+ public String getName(); // Return the example name
+ public int getWidth(); // Return its width
+ public int getHeight(); // Return its height
+ public void draw(Graphics2D g, Component c); // Draw the example
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java
new file mode 100644
index 0000000..3ee011b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.awt.print.*;
+
+/**
+ * This class displays one or more GraphicsExample objects in a
+ * Swing JFrame and a JTabbedPane
+ */
+public class GraphicsExampleFrame extends JFrame {
+ public GraphicsExampleFrame(final GraphicsExample[] examples) {
+ super("GraphicsExampleFrame");
+
+ Container cpane = getContentPane(); // Set up the frame
+ cpane.setLayout(new BorderLayout());
+ final JTabbedPane tpane = new JTabbedPane(); // And the tabbed pane
+ cpane.add(tpane, BorderLayout.CENTER);
+
+ // Add a menubar
+ JMenuBar menubar = new JMenuBar(); // Create the menubar
+ this.setJMenuBar(menubar); // Add it to the frame
+ JMenu filemenu = new JMenu("File"); // Create a File menu
+ menubar.add(filemenu); // Add to the menubar
+ JMenuItem print = new JMenuItem("Print"); // Create a Print item
+ filemenu.add(print); // Add it to the menu
+ JMenuItem quit = new JMenuItem("Quit"); // Create a Quit item
+ filemenu.add(quit); // Add it to the menu
+
+ // Tell the Print menu item what to do when selected
+ print.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // Get the currently displayed example, and call
+ // the print method (defined below)
+ print(examples[tpane.getSelectedIndex()]);
+ }
+ });
+
+ // Tell the Quit menu item what to do when selected
+ quit.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { System.exit(0); }
+ });
+
+ // In addition to the Quit menu item, also handle window close events
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+
+ // Insert each of the example objects into the tabbed pane
+ for(int i = 0; i < examples.length; i++) {
+ GraphicsExample e = examples[i];
+ tpane.addTab(e.getName(), new GraphicsExamplePane(e));
+ }
+ }
+
+ /**
+ * This inner class is a custom Swing component that displays
+ * a GraphicsExample object.
+ */
+ public class GraphicsExamplePane extends JComponent {
+ GraphicsExample example; // The example to display
+ Dimension size; // How much space it requires
+
+ public GraphicsExamplePane(GraphicsExample example) {
+ this.example = example;
+ size = new Dimension(example.getWidth(), example.getHeight());
+ }
+
+ /** Draw the component and the example it contains */
+ public void paintComponent(Graphics g) {
+ g.setColor(Color.white); // set the background
+ g.fillRect(0, 0, size.width, size.height); // to white
+ g.setColor(Color.black); // set a default drawing color
+ example.draw((Graphics2D) g, this); // ask example to draw itself
+ }
+
+ // These methods specify how big the component must be
+ public Dimension getPreferredSize() { return size; }
+ public Dimension getMinimumSize() { return size; }
+ }
+
+ /** This method is invoked by the Print menu item */
+ public void print(final GraphicsExample example) {
+ // Start off by getting a printer job to do the printing
+ PrinterJob job = PrinterJob.getPrinterJob();
+ // Wrap the example in a Printable object (defined below)
+ // and tell the PrinterJob that we want to print it
+ job.setPrintable(new PrintableExample(example));
+
+ // Display the print dialog to the user
+ if (job.printDialog()) {
+ // If they didn't cancel it, then tell the job to start printing
+ try {
+ job.print();
+ }
+ catch(PrinterException e) {
+ System.out.println("Couldn't print: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * This inner class implements the Printable interface in order to print
+ * a GraphicsExample object.
+ **/
+ class PrintableExample implements Printable {
+ GraphicsExample example; // The example to print
+
+ // The constructor. Just remember the example
+ public PrintableExample(GraphicsExample example) {
+ this.example = example;
+ }
+
+ /**
+ * This method is called by the PrinterJob to print the example
+ **/
+ public int print(Graphics g, PageFormat pf, int pageIndex) {
+ // Tell the PrinterJob that there is only one page
+ if (pageIndex != 0) return NO_SUCH_PAGE;
+
+ // The PrinterJob supplies us a Graphics object to draw with.
+ // Anything drawn with this object will be sent to the printer.
+ // The Graphics object can safely be cast to a Graphics2D object.
+ Graphics2D g2 = (Graphics2D)g;
+
+ // Translate to skip the left and top margins.
+ g2.translate(pf.getImageableX(), pf.getImageableY());
+
+ // Figure out how big the printable area is, and how big
+ // the example is.
+ double pageWidth = pf.getImageableWidth();
+ double pageHeight = pf.getImageableHeight();
+ double exampleWidth = example.getWidth();
+ double exampleHeight = example.getHeight();
+
+ // Scale the example if needed
+ double scalex = 1.0, scaley = 1.0;
+ if (exampleWidth > pageWidth) scalex = pageWidth/exampleWidth;
+ if (exampleHeight > pageHeight) scaley = pageHeight/exampleHeight;
+ double scalefactor = Math.min(scalex, scaley);
+ if (scalefactor != 1) g2.scale(scalefactor, scalefactor);
+
+ // Finally, call the draw() method of the example, passing in
+ // the Graphics2D object for the printer
+ example.draw(g2, GraphicsExampleFrame.this);
+
+ // Tell the PrinterJob that we successfully printed the page
+ return PAGE_EXISTS;
+ }
+ }
+
+ /**
+ * The main program. Use Java reflection to load and instantiate
+ * the specified GraphicsExample classes, then create a
+ * GraphicsExampleFrame to display them.
+ **/
+ public static void main(String[] args) {
+ GraphicsExample[] examples = new GraphicsExample[args.length];
+
+ // Loop through the command line arguments
+ for(int i=0; i < args.length; i++) {
+ // The class name of the requested example
+ String classname = args[i];
+
+ // If no package is specified, assume it is in this package
+ if (classname.indexOf('.') == -1)
+ classname = "com.davidflanagan.examples.graphics."+args[i];
+
+ // Try to instantiate the named GraphicsExample class
+ try {
+ Class exampleClass = Class.forName(classname);
+ examples[i] = (GraphicsExample) exampleClass.newInstance();
+ }
+ catch (ClassNotFoundException e) { // unknown class
+ System.err.println("Couldn't find example: " + classname);
+ System.exit(1);
+ }
+ catch (ClassCastException e) { // wrong type of class
+ System.err.println("Class " + classname +
+ " is not a GraphicsExample");
+ System.exit(1);
+ }
+ catch (Exception e) { // class doesn't have a public constructor
+ // catch InstantiationException, IllegalAccessException
+ System.err.println("Couldn't instantiate example: " +
+ classname);
+ System.exit(1);
+ }
+ }
+
+ // Now create a window to display the examples in, and make it visible
+ GraphicsExampleFrame f = new GraphicsExampleFrame(examples);
+ f.pack();
+ f.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html
new file mode 100644
index 0000000..5edd6c7
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java
new file mode 100644
index 0000000..a310a2e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.applet.*;
+import java.awt.*;
+
+/**
+ * An applet that demonstrates most of the graphics primitives in
+ * java.awt.Graphics.
+ **/
+public class GraphicsSampler extends Applet {
+ Color fill, outline, textcolor; // The various colors we use
+ Font font; // The font we use for text
+ FontMetrics metrics; // Information about font size
+ Image image, background; // Some images we draw with
+
+ // This method is called when the applet is first created.
+ // It performs initialization, such as creating the resources
+ // (graphics attribute values) used by the paint() method.
+ public void init() {
+ // Initialize color resources. Note the use of the Color() constructor
+ // and the use of pre-defined color constants.
+ fill = new Color(200, 200, 200); // Equal red, green, and blue == gray
+ outline = Color.blue; // Same as new Color(0, 0, 255)
+ textcolor = Color.red; // Same as new Color(255, 0, 0)
+
+ // Create a font for use in the paint() method. Get its metrics, too.
+ font = new Font("sansserif", Font.BOLD, 14);
+ metrics = this.getFontMetrics(font);
+
+ // Load some Image objects for use in the paint() method.
+ image = this.getImage(this.getDocumentBase(), "tiger.gif");
+ background = this.getImage(this.getDocumentBase(), "background.gif");
+
+ // Set a property that tells the applet its background color
+ this.setBackground(Color.lightGray);
+ }
+
+ // This method is called whenever the applet needs to be drawn or redrawn
+ public void paint(Graphics g) {
+ g.setFont(font); // Specify the font we'll be using throughout
+
+ // Draw a background by tiling an image tile() is defined below
+ tile(g, this, background);
+
+ // Draw a line
+ g.setColor(outline); // Specify the drawing color
+ g.drawLine(25, 10, 150, 80); // Draw a line from (25,10) to (150,80)
+ // Draw some text. See the centerText() method below.
+ centerText("drawLine()", null, g, textcolor, 25, 10, 150, 80);
+
+ // Draw and fill an arc
+ g.setColor(fill);
+ g.fillArc(225, 10, 150, 80, 90, 135);
+ g.setColor(outline);
+ g.drawArc(225, 10, 150, 80, 90, 135);
+ centerText("fillArc()", "drawArc()", g, textcolor,225,10,150,80);
+
+ // Draw and fill a rectangle
+ g.setColor(fill);
+ g.fillRect(25, 110, 150, 80);
+ g.setColor(outline);
+ g.drawRect(25, 110, 150, 80);
+ centerText("fillRect()", "drawRect()", g, textcolor, 25, 110, 150, 80);
+
+ // Draw and fill a rounded rectangle
+ g.setColor(fill);
+ g.fillRoundRect(225, 110, 150, 80, 20, 20);
+ g.setColor(outline);
+ g.drawRoundRect(225, 110, 150, 80, 20, 20);
+ centerText("fillRoundRect()", "drawRoundRect()", g, textcolor,
+ 225, 110, 150, 80);
+
+ // Draw and fill an oval
+ g.setColor(fill);
+ g.fillOval(25, 210, 150, 80);
+ g.setColor(outline);
+ g.drawOval(25, 210, 150, 80);
+ centerText("fillOval()", "drawOval()", g, textcolor, 25, 210, 150, 80);
+
+ // Define an octagon using arrays of X and Y coordinates
+ int numpoints = 8;
+ int[] xpoints = new int[numpoints+1];
+ int[] ypoints = new int[numpoints+1];
+ for(int i=0; i < numpoints; i++) {
+ double angle = 2*Math.PI * i / numpoints;
+ xpoints[i] = (int)(300 + 75*Math.cos(angle));
+ ypoints[i] = (int)(250 - 40*Math.sin(angle));
+ }
+
+ // Draw and fill the polygon
+ g.setColor(fill);
+ g.fillPolygon(xpoints, ypoints, numpoints);
+ g.setColor(outline);
+ g.drawPolygon(xpoints, ypoints, numpoints);
+ centerText("fillPolygon()", "drawPolygon()", g, textcolor,
+ 225, 210, 150, 80);
+
+ // Draw a 3D rectangle (clear an area for it first)
+ g.setColor(fill);
+ g.fillRect(20, 305, 160, 90);
+ g.draw3DRect(25, 310, 150, 80, true);
+ g.draw3DRect(26, 311, 148, 78, true);
+ g.draw3DRect(27, 312, 146, 76, true);
+ centerText("draw3DRect()", "x 3", g, textcolor, 25, 310, 150, 80);
+
+ // Draw an image (centered within an area)
+ int w = image.getWidth(this);
+ int h = image.getHeight(this);
+ g.drawImage(image, 225 + (150-w)/2, 310 + (80-h)/2, this);
+ centerText("drawImage()", null, g, textcolor, 225, 310, 150, 80);
+ }
+
+ // Utility method to tile an image on the background of the component
+ protected void tile(Graphics g, Component c, Image i) {
+ // Use bounds() instead of getBounds() if you want
+ // compatibility with Java 1.0 and old browsers like Netscape 3
+ Rectangle r = c.getBounds(); // How big is the component?
+ int iw = i.getWidth(c); // How big is the image?
+ int ih = i.getHeight(c);
+ if ((iw <= 0) || (ih <= 0)) return;
+ for(int x=0; x < r.width; x += iw) // Loop horizontally
+ for(int y=0; y < r.height; y += ih) // Loop vertically
+ g.drawImage(i, x, y, c); // Draw the image
+ }
+
+ // Utility method to center two lines of text in a rectangle.
+ // Relies on the FontMetrics obtained in the init() method.
+ protected void centerText(String s1, String s2, Graphics g, Color c,
+ int x, int y, int w, int h)
+ {
+ int height = metrics.getHeight(); // How tall is the font?
+ int ascent = metrics.getAscent(); // Where is the font baseline?
+ int width1=0, width2 = 0, x0=0, x1=0, y0=0, y1=0;
+ width1 = metrics.stringWidth(s1); // How wide are the strings?
+ if (s2 != null) width2 = metrics.stringWidth(s2);
+ x0 = x + (w - width1)/2; // Center the strings horizontally
+ x1 = x + (w - width2)/2;
+ if (s2 == null) // Center one string vertically
+ y0 = y + (h - height)/2 + ascent;
+ else { // Center two strings vertically
+ y0 = y + (h - (int)(height * 2.2))/2 + ascent;
+ y1 = y0 + (int)(height * 1.2);
+ }
+ g.setColor(c); // Set the color
+ g.drawString(s1, x0, y0); // Draw the strings
+ if (s2 != null) g.drawString(s2, x1, y1);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java b/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java
new file mode 100644
index 0000000..7cb759e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import javax.swing.*;
+import javax.swing.Timer; // Import explicitly because of java.util.Timer
+
+/**
+ * A Swing component that smoothly animates a spiral in a hypnotic way.
+ **/
+public class Hypnosis extends JComponent implements ActionListener {
+ double x, y; // The center of the spiral
+ double r1, r2; // The inner and outer radii of the spiral
+ double a1, a2; // The start and end angles of the spiral
+ double deltaA; // How much the angle changes each frame
+ double deltaX, deltaY; // The trajectory of the center
+ float linewidth; // How wide the lines are
+ Timer timer; // The object that triggers the animation
+ BufferedImage buffer; // The image we use for double-buffering
+ Graphics2D osg; // Graphics2D object for drawing into the buffer
+
+ public Hypnosis(double x, double y, double r1, double r2,
+ double a1, double a2, float linewidth, int delay,
+ double deltaA, double deltaX, double deltaY)
+ {
+ this.x = x; this.y = y;
+ this.r1 = r1; this.r2 = r2;
+ this.a1 = a1; this.a2 = a2;
+ this.linewidth = linewidth;
+ this.deltaA = deltaA;
+ this.deltaX = deltaX;
+ this.deltaY = deltaY;
+
+ // Set up a timer to call actionPerformed() every delay milliseconds
+ timer = new Timer(delay, this);
+
+ // Create a buffer for double-buffering
+ buffer = new BufferedImage((int)(2*r2+linewidth),
+ (int)(2*r2+linewidth),
+ BufferedImage.TYPE_INT_RGB);
+
+ // Create a Graphics object for the buffer, and set the linewidth
+ // and request antialiasing when drawing with it
+ osg = buffer.createGraphics();
+ osg.setStroke(new BasicStroke(linewidth, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND));
+ osg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ // Start and stop the animation by starting and stopping the timer
+ public void start() { timer.start(); }
+ public void stop() { timer.stop(); }
+
+ /**
+ * Swing calls this method to ask the component to redraw itself.
+ * This method uses double-buffering to make the animation smoother.
+ * Swing does double-buffering automatically, so this may not actually
+ * make much difference, but it is important to understand the technique.
+ **/
+ public void paintComponent(Graphics g) {
+ // Clear the background of the off-screen image
+ osg.setColor(getBackground());
+ osg.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
+
+ // Now draw a black spiral into the off-screen image
+ osg.setColor(Color.black);
+ osg.draw(new Spiral(r2+linewidth/2, r2+linewidth/2, r1, a1, r2, a2));
+
+ // Now copy that off-screen image onto the screen
+ g.drawImage(buffer, (int)(x-r2), (int)(y-r2), this);
+ }
+
+ /**
+ * This method implements the ActionListener interface. Our Timer object
+ * calls this method periodically. It updates the position and angles
+ * of the spiral and requests a redraw. Instead of redrawing the entire
+ * component, however, this method requests a redraw only for the
+ * area that has changed.
+ **/
+ public void actionPerformed(ActionEvent e) {
+ // Ask to have the old bounding box of the spiral redrawn.
+ // Nothing else has anything drawn in it, so it doesn't need a redraw
+ repaint((int)(x-r2-linewidth), (int)(y-r2-linewidth),
+ (int)(2*(r2+linewidth)), (int)(2*(r2+linewidth)));
+
+ // Now animate: update the position and angles of the spiral
+
+ // Bounce if we've hit an edge
+ Rectangle bounds = getBounds();
+ if ((x - r2 + deltaX < 0) || (x + r2 + deltaX > bounds.width))
+ deltaX = -deltaX;
+ if ((y - r2 + deltaY < 0) || (y + r2 + deltaY > bounds.height))
+ deltaY = -deltaY;
+
+ // Move the center of the spiral
+ x += deltaX;
+ y += deltaY;
+
+ // Increment the start and end angles;
+ a1 += deltaA;
+ a2 += deltaA;
+ if (a1 > 2*Math.PI) { // Don't let them get too big
+ a1 -= 2*Math.PI;
+ a2 -= 2*Math.PI;
+ }
+
+ // Now ask to have the new bounding box of the spiral redrawn. This
+ // rectangle will be intersected with the redraw rectangle requested
+ // above, and only the combined region will be redrawn
+ repaint((int)(x-r2-linewidth), (int)(y-r2-linewidth),
+ (int)(2*(r2+linewidth)), (int)(2*(r2+linewidth)));
+ }
+
+ /** Tell Swing not to double-buffer for us, since we do our own */
+ public boolean isDoubleBuffered() { return false; }
+
+ /** This is a main() method for testing the component */
+ public static void main(String[] args) {
+ JFrame f = new JFrame("Hypnosis");
+ Hypnosis h = new Hypnosis(200, 200, 10, 100, 0, 11*Math.PI, 7, 100,
+ 2*Math.PI/30, 3, 5);
+ f.getContentPane().add(h, BorderLayout.CENTER);
+ f.setSize(400, 400);
+ f.show();
+ h.start();
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java b/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java
new file mode 100644
index 0000000..a449e53
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.awt.color.*;
+
+/** A demonstration of various image processing filters */
+public class ImageOps implements GraphicsExample {
+ static final int WIDTH = 600, HEIGHT = 675; // Size of our example
+ public String getName() {return "Image Processing";}// From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ Image image;
+
+ /** This constructor loads the image we will manipulate */
+ public ImageOps() {
+ java.net.URL imageurl = this.getClass().getResource("cover.gif");
+ image = new javax.swing.ImageIcon(imageurl).getImage();
+ }
+
+ // These arrays of bytes are used by the LookupImageOp image filters below
+ static byte[] brightenTable = new byte[256];
+ static byte[] thresholdTable = new byte[256];
+ static { // Initialize the arrays
+ for(int i = 0; i < 256; i++) {
+ brightenTable[i] = (byte)(Math.sqrt(i/255.0)*255);
+ thresholdTable[i] = (byte)((i < 225)?0:i);
+ }
+ }
+
+ // This AffineTransform is used by one of the image filters below
+ static AffineTransform mirrorTransform;
+ static { // Create and initialize the AffineTransform
+ mirrorTransform = AffineTransform.getTranslateInstance(127, 0);
+ mirrorTransform.scale(-1.0, 1.0); // flip horizontally
+ }
+
+ // These are the labels we'll display for each of the filtered images
+ static String[] filterNames = new String[] {
+ "Original", "Gray Scale", "Negative", "Brighten (linear)",
+ "Brighten (sqrt)", "Threshold", "Blur", "Sharpen",
+ "Edge Detect", "Mirror", "Rotate (center)", "Rotate (lower left)"
+ };
+
+ // The following BufferedImageOp image filter objects perform
+ // different types of image processing operations.
+ static BufferedImageOp[] filters = new BufferedImageOp[] {
+ // 1) No filter here. We'll display the original image
+ null,
+ // 2) Convert to Grayscale color space
+ new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null),
+ // 3) Image negative. Multiply each color value by -1.0 and add 255
+ new RescaleOp(-1.0f, 255f, null),
+ // 4) Brighten using a linear formula that increases all color values
+ new RescaleOp(1.25f, 0, null),
+ // 5) Brighten using the lookup table defined above
+ new LookupOp(new ByteLookupTable(0, brightenTable), null),
+ // 6) Threshold using the lookup table defined above
+ new LookupOp(new ByteLookupTable(0, thresholdTable), null),
+ // 7) Blur by "convolving" the image with a matrix
+ new ConvolveOp(new Kernel(3, 3, new float[] {
+ .1111f,.1111f,.1111f,
+ .1111f,.1111f,.1111f,
+ .1111f,.1111f,.1111f,})),
+ // 8) Sharpen by using a different matrix
+ new ConvolveOp(new Kernel(3, 3, new float[] {
+ 0.0f, -0.75f, 0.0f,
+ -0.75f, 4.0f, -0.75f,
+ 0.0f, -0.75f, 0.0f})),
+ // 9) Edge detect using yet another matrix
+ new ConvolveOp(new Kernel(3, 3, new float[] {
+ 0.0f, -0.75f, 0.0f,
+ -0.75f, 3.0f, -0.75f,
+ 0.0f, -0.75f, 0.0f})),
+ // 10) Compute a mirror image using the transform defined above
+ new AffineTransformOp(mirrorTransform,AffineTransformOp.TYPE_BILINEAR),
+ // 11) Rotate the image 180 degrees about its center point
+ new AffineTransformOp(AffineTransform.getRotateInstance(Math.PI,64,95),
+ AffineTransformOp.TYPE_NEAREST_NEIGHBOR),
+ // 12) Rotate the image 15 degrees about the bottom left
+ new AffineTransformOp(AffineTransform.getRotateInstance(Math.PI/12,
+ 0, 190),
+ AffineTransformOp.TYPE_NEAREST_NEIGHBOR),
+ };
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // Create a BufferedImage big enough to hold the Image loaded
+ // in the constructor. Then copy that image into the new
+ // BufferedImage object so that we can process it.
+ BufferedImage bimage = new BufferedImage(image.getWidth(c),
+ image.getHeight(c),
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D ig = bimage.createGraphics();
+ ig.drawImage(image, 0, 0, c); // copy the image
+
+ // Set some default graphics attributes
+ g.setFont(new Font("SansSerif", Font.BOLD, 12)); // 12pt bold text
+ g.setColor(Color.green); // Draw in green
+ g.translate(10, 10); // Set some margins
+
+ // Loop through the filters
+ for(int i = 0; i < filters.length; i++) {
+ // If the filter is null, draw the original image, otherwise,
+ // draw the image as processed by the filter
+ if (filters[i] == null) g.drawImage(bimage, 0, 0, c);
+ else g.drawImage(filters[i].filter(bimage, null), 0, 0, c);
+ g.drawString(filterNames[i], 0, 205); // Label the image
+ g.translate(137, 0); // Move over
+ if (i % 4 == 3) g.translate(-137*4, 215); // Move down after 4
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java b/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java
new file mode 100644
index 0000000..000b4af
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+
+/** A demonstration of Java2D line styles */
+public class LineStyles implements GraphicsExample {
+ public String getName() { return "LineStyles"; } // From GraphicsExample
+ public int getWidth() { return 450; } // From GraphicsExample
+ public int getHeight() { return 180; } // From GraphicsExample
+
+ int[] xpoints = new int[] { 0, 50, 100 }; // X coordinates of our shape
+ int[] ypoints = new int[] { 75, 0, 75 }; // Y coordinates of our shape
+
+ // Here are three different line styles we will demonstrate
+ // They are thick lines with different cap and join styles
+ Stroke[] linestyles = new Stroke[] {
+ new BasicStroke(25.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL),
+ new BasicStroke(25.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER),
+ new BasicStroke(25.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND),
+ };
+
+ // Another line style: a 2 pixel-wide dot-dashed line
+ Stroke thindashed = new BasicStroke(2.0f, // line width
+ /* cap style */ BasicStroke.CAP_BUTT,
+ /* join style, miter limit */ BasicStroke.JOIN_BEVEL, 1.0f,
+ /* the dash pattern */ new float[] {8.0f, 3.0f, 2.0f, 3.0f},
+ /* the dash phase */ 0.0f); /* on 8, off 3, on 2, off 3 */
+
+ // Labels to appear in the diagram, and the font to use to display them.
+ Font font = new Font("Helvetica", Font.BOLD, 12);
+ String[] capNames = new String[] {"CAP_BUTT", "CAP_SQUARE","CAP_ROUND"};
+ String[] joinNames = new String[] {"JOIN_BEVEL","JOIN_MITER","JOIN_ROUND"};
+
+ /** This method draws the example figure */
+ public void draw(Graphics2D g, Component c) {
+ // Use anti-aliasing to avoid "jaggies" in the lines
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // Define the shape to draw
+ GeneralPath shape = new GeneralPath();
+ shape.moveTo(xpoints[0], ypoints[0]); // start at point 0
+ shape.lineTo(xpoints[1], ypoints[1]); // draw a line to point 1
+ shape.lineTo(xpoints[2], ypoints[2]); // and then on to point 2
+
+ // Move the origin to the right and down, creating a margin
+ g.translate(20,40);
+
+ // Now loop, drawing our shape with the three different line styles
+ for(int i = 0; i < linestyles.length; i++) {
+ g.setColor(Color.gray); // Draw a gray line
+ g.setStroke(linestyles[i]); // Select the line style to use
+ g.draw(shape); // Draw the shape
+
+ g.setColor(Color.black); // Now use black
+ g.setStroke(thindashed); // And the thin dashed line
+ g.draw(shape); // And draw the shape again.
+
+ // Highlight the location of the vertexes of the shape
+ // This accentuates the cap and join styles we're demonstrating
+ for(int j = 0; j < xpoints.length; j++)
+ g.fillRect(xpoints[j]-2, ypoints[j]-2, 5, 5);
+
+ g.drawString(capNames[i], 5, 105); // Label the cap style
+ g.drawString(joinNames[i], 5, 120); // Label the join style
+
+ g.translate(150, 0); // Move over to the right before looping again
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Paints.java b/src/main/java/com/davidflanagan/examples/graphics/Paints.java
new file mode 100644
index 0000000..ddf95fe
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Paints.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.font.*;
+import java.awt.image.*;
+
+/** A demonstration of Java2D transformations */
+public class Paints implements GraphicsExample {
+ static final int WIDTH = 800, HEIGHT = 375; // Size of our example
+
+ public String getName() { return "Paints"; } // From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // Paint the entire background using a GradientPaint.
+ // The background color varies diagonally from deep red to pale blue
+ g.setPaint(new GradientPaint(0, 0, new Color(150, 0, 0),
+ WIDTH, HEIGHT, new Color(200, 200, 255)));
+ g.fillRect(0, 0, WIDTH, HEIGHT); // fill the background
+
+ // Use a different GradientPaint to draw a box.
+ // This one alternates between deep opaque green and transparent green.
+ // Note: the 4th arg to Color() constructor specifies color opacity
+ g.setPaint(new GradientPaint(0, 0, new Color(0, 150, 0),
+ 20, 20, new Color(0, 150, 0, 0), true));
+ g.setStroke(new BasicStroke(15)); // use wide lines
+ g.drawRect(25, 25, WIDTH-50, HEIGHT-50); // draw the box
+
+ // The glyphs of fonts can be used as Shape objects, which enables
+ // us to use Java2D techniques with letters Just as we would with
+ // any other shape. Here we get some letter shapes to draw.
+ Font font = new Font("Serif", Font.BOLD, 10); // a basic font
+ Font bigfont = // a scaled up version
+ font.deriveFont(AffineTransform.getScaleInstance(30.0, 30.0));
+ GlyphVector gv = bigfont.createGlyphVector(g.getFontRenderContext(),
+ "JAV");
+ Shape jshape = gv.getGlyphOutline(0); // Shape of letter J
+ Shape ashape = gv.getGlyphOutline(1); // Shape of letter A
+ Shape vshape = gv.getGlyphOutline(2); // Shape of letter V
+
+ // We're going to outline the letters with a 5-pixel wide line
+ g.setStroke(new BasicStroke(5.0f));
+
+ // We're going to fake shadows for the letters using the
+ // following Paint and AffineTransform objects
+ Paint shadowPaint = new Color(0, 0, 0, 100); // Translucent black
+ AffineTransform shadowTransform =
+ AffineTransform.getShearInstance(-1.0, 0.0); // Shear to the right
+ shadowTransform.scale(1.0, 0.5); // Scale height by 1/2
+
+ // Move to the baseline of our first letter
+ g.translate(65, 270);
+
+ // Draw the shadow of the J shape
+ g.setPaint(shadowPaint);
+ g.translate(15,20); // Compensate for the descender of the J
+ // transform the J into the shape of its shadow, and fill it
+ g.fill(shadowTransform.createTransformedShape(jshape));
+ g.translate(-15,-20); // Undo the translation above
+
+ // Now fill the J shape with a solid (and opaque) color
+ g.setPaint(Color.blue); // Fill with solid, opaque blue
+ g.fill(jshape); // Fill the shape
+ g.setPaint(Color.black); // Switch to solid black
+ g.draw(jshape); // And draw the outline of the J
+
+ // Now draw the A shadow
+ g.translate(75, 0); // Move to the right
+ g.setPaint(shadowPaint); // Set shadow color
+ g.fill(shadowTransform.createTransformedShape(ashape)); // draw shadow
+
+ // Draw the A shape using a solid transparent color
+ g.setPaint(new Color(0, 255, 0, 125)); // Transparent green as paint
+ g.fill(ashape); // Fill the shape
+ g.setPaint(Color.black); // Switch to solid back
+ g.draw(ashape); // Draw the outline
+
+ // Move to the right and draw the shadow of the letter V
+ g.translate(175, 0);
+ g.setPaint(shadowPaint);
+ g.fill(shadowTransform.createTransformedShape(vshape));
+
+ // We're going to fill the next letter using a TexturePaint, which
+ // repeatedly tiles an image. The first step is to obtain the image.
+ // We could load it from an image file, but here we create it
+ // ourselves by drawing a into an off-screen image. Note that we use
+ // a GradientPaint to fill the off-screen image, so the fill pattern
+ // combines features of both Paint classes.
+ BufferedImage tile = // Create an image
+ new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
+ Graphics2D tg = tile.createGraphics(); // Get its Graphics for drawing
+ tg.setColor(Color.pink);
+ tg.fillRect(0, 0, 50, 50); // Fill tile background with pink
+ tg.setPaint(new GradientPaint(40, 0, Color.green, // diagonal gradient
+ 0, 40, Color.gray)); // green to gray
+ tg.fillOval(5, 5, 40, 40); // Draw a circle with this gradient
+
+ // Use this new tile to create a TexturePaint and fill the letter V
+ g.setPaint(new TexturePaint(tile, new Rectangle(0, 0, 50, 50)));
+ g.fill(vshape); // Fill letter shape
+ g.setPaint(Color.black); // Switch to solid black
+ g.draw(vshape); // Draw outline of letter
+
+ // Move to the right and draw the shadow of the final A
+ g.translate(160, 0);
+ g.setPaint(shadowPaint);
+ g.fill(shadowTransform.createTransformedShape(ashape));
+
+ // For the last letter, use a custom Paint class to fill with a
+ // complex mathematically defined pattern. The GenericPaint
+ // class is defined later in the chapter.
+ g.setPaint(new GenericPaint() {
+ public int computeRed(double x, double y) { return 128; }
+ public int computeGreen(double x, double y) {
+ return (int)((Math.sin(x/7) + Math.cos(y/5) + 2)/4 *255);
+ }
+ public int computeBlue(double x, double y) {
+ return ((int)(x*y))%256;
+ }
+ public int computeAlpha(double x, double y) {
+ return ((int)x%25*8+50) + ((int)y%25*8+50);
+ }
+ });
+ g.fill(ashape); // Fill letter A
+ g.setPaint(Color.black); // Revert to solid black
+ g.draw(ashape); // Draw the outline of the A
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java b/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java
new file mode 100644
index 0000000..0d9ff51
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+/**
+ * This is a Paint implementation that fills every raster with a random
+ * opaque color. Using this Paint class allows you to visualize the
+ * painting algorithm used by your Java2D implementation.
+ **/
+public class RandomPaint implements Paint {
+ /** This is the main Paint method; all it does is return a PaintContext */
+ public PaintContext createContext(ColorModel cm,
+ Rectangle deviceBounds,
+ Rectangle2D userBounds,
+ AffineTransform xform,
+ RenderingHints hints) {
+ return new RandomPaintContext();
+ }
+
+ /** This Paint object only uses opaque colors */
+ public int getTransparency() { return OPAQUE; }
+
+ /**
+ * The PaintContext class does all the work of painting
+ **/
+ class RandomPaintContext implements PaintContext {
+ BufferedImage image; // An image we can draw into
+ Graphics2D imageGraphics; // The Graphics object to do it with
+ java.util.Random randomizer = // For generating random numbers
+ new java.util.Random(System.currentTimeMillis()); // seed value
+ Rectangle rect = new Rectangle(); // A scratch rectangle
+
+ /** Return the color model used by this Paint implementation */
+ public ColorModel getColorModel() { return image.getColorModel(); }
+
+ /**
+ * This is the main method of PaintContext. It must return a Raster
+ * that contains fill data for the specified rectangle. For this
+ * implementation, we just fill with a random solid color each time.
+ * Instead of setting pixels at the Raster level, we instead
+ * manipulate a BufferedImage using the Graphics.fillRect() method.
+ * Note that we never create an image larger than we need.
+ **/
+ public Raster getRaster(int x, int y, int w, int h) {
+ // Create an initial image or a larger image as needed
+ if ((image == null) || (image.getWidth() < w) ||
+ (image.getHeight() < h)) {
+ image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ imageGraphics = image.createGraphics();
+ }
+
+ // Choose and use a random color
+ imageGraphics.setColor(new Color(randomizer.nextInt(256),
+ randomizer.nextInt(256),
+ randomizer.nextInt(256)));
+ // Fill a rectangle of the specified size with that color
+ imageGraphics.fillRect(0, 0, w, h);
+
+ // Then extract the corresponding Raster from the image and return
+ rect.x = 0; rect.y = 0; rect.width = w; rect.height = h;
+ return image.getData(rect);
+ }
+
+ /** Called when the PaintContext is no longer needed. */
+ public void dispose() {
+ imageGraphics.dispose();
+ image = null;
+ imageGraphics = null;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Shapes.java b/src/main/java/com/davidflanagan/examples/graphics/Shapes.java
new file mode 100644
index 0000000..0b4496a
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Shapes.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.font.*;
+import java.awt.image.*;
+
+/** A demonstration of Java2D shapes */
+public class Shapes implements GraphicsExample {
+ static final int WIDTH = 725, HEIGHT = 250; // Size of our example
+ public String getName() {return "Shapes";} // From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ Shape[] shapes = new Shape[] {
+ // A straight line segment
+ new Line2D.Float(0, 0, 100, 100),
+ // A quadratic bezier curve. Two end points and one control point
+ new QuadCurve2D.Float(0, 0, 80, 15, 100, 100),
+ // A cubic bezier curve. Two end points and two control points
+ new CubicCurve2D.Float(0, 0, 80, 15, 10, 90, 100, 100),
+ // A 120 degree portion of an ellipse
+ new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.OPEN),
+ // A 120 degree portion of an ellipse, closed with a chord
+ new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.CHORD),
+ // A 120 degree pie slice of an ellipse
+ new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.PIE),
+ // An ellipse
+ new Ellipse2D.Float(0, 20, 100, 60),
+ // A rectangle
+ new Rectangle2D.Float(0, 20, 100, 60),
+ // A rectangle with rounded corners
+ new RoundRectangle2D.Float(0, 20, 100, 60, 15, 15),
+ // A triangle
+ new Polygon(new int[] { 0, 0, 100 }, new int[] {20, 80, 80}, 3),
+ // A random polygon, initialized in code below
+ null,
+ // A spiral: an instance of a custom Shape implementation
+ new Spiral(50, 50, 5, 0, 50, 4*Math.PI),
+ };
+
+ { // Initialize the null shape above as a Polygon with random points
+ Polygon p = new Polygon();
+ for(int i = 0; i < 10; i++)
+ p.addPoint((int)(100*Math.random()), (int)(100*Math.random()));
+ shapes[10] = p;
+ }
+
+ // These are the labels for each of the shapes
+ String[] labels = new String[] {
+ "Line2D", "QuadCurve2D", "CubicCurve2D", "Arc2D (OPEN)",
+ "Arc2D (CHORD)", "Arc2D (PIE)", "Ellipse2D", "Rectangle2D",
+ "RoundRectangle2D", "Polygon", "Polygon (random)", "Spiral"
+ };
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // Set basic drawing attributes
+ g.setFont(new Font("SansSerif", Font.PLAIN, 10)); // select font
+ g.setStroke(new BasicStroke(2.0f)); // 2 pixel lines
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // antialiasing
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.translate(10, 10); // margins
+
+ // Loop through each shape
+ for(int i = 0; i < shapes.length; i++) {
+ g.setColor(Color.yellow); // Set a color
+ g.fill(shapes[i]); // Fill the shape with it
+ g.setColor(Color.black); // Switch to black
+ g.draw(shapes[i]); // Outline the shape with it
+ g.drawString(labels[i], 0, 110); // Label the shape
+ g.translate(120, 0); // Move over for next shape
+ if (i % 6 == 5) g.translate(-6*120, 120); // Move down after 6
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Spiral.java b/src/main/java/com/davidflanagan/examples/graphics/Spiral.java
new file mode 100644
index 0000000..3046c4b
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Spiral.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+
+/** This Shape implementation represents a spiral curve */
+public class Spiral implements Shape {
+ double centerX, centerY; // The center of the spiral
+ double startRadius, startAngle; // The spiral starting point
+ double endRadius, endAngle; // The spiral ending point
+ double outerRadius; // the bigger of the two radii
+ int angleDirection; // 1 if angle increases, -1 otherwise
+
+ /**
+ * The constructor. It takes arguments for the center of the shape, the
+ * start point, and the end point. The start and end points are specified
+ * in terms of angle and radius. The spiral curve is formed by varying
+ * the angle and radius smoothly between the two end points.
+ **/
+ public Spiral(double centerX, double centerY,
+ double startRadius, double startAngle,
+ double endRadius, double endAngle)
+ {
+ // Save the parameters that describe the spiral
+ this.centerX = centerX; this.centerY = centerY;
+ this.startRadius = startRadius; this.startAngle = startAngle;
+ this.endRadius = endRadius; this.endAngle = endAngle;
+
+ // figure out the maximum radius, and the spiral direction
+ this.outerRadius = Math.max(startRadius, endRadius);
+ if (startAngle < endAngle) angleDirection = 1;
+ else angleDirection = -1;
+
+ if ((startRadius < 0) || (endRadius < 0))
+ throw new IllegalArgumentException("Spiral radii must be >= 0");
+ }
+
+ /**
+ * The bounding box of a Spiral is the same as the bounding box of a
+ * circle with the same center and the maximum radius
+ **/
+ public Rectangle getBounds() {
+ return new Rectangle((int)(centerX-outerRadius),
+ (int)(centerY-outerRadius),
+ (int)(outerRadius*2), (int)(outerRadius*2));
+ }
+
+ /** Same as getBounds(), but with floating-point coordinates */
+ public Rectangle2D getBounds2D() {
+ return new Rectangle2D.Double(centerX-outerRadius, centerY-outerRadius,
+ outerRadius*2, outerRadius*2);
+ }
+
+ /**
+ * A spiral is an open curve, not a not a closed area; it does not have an
+ * inside and an outsize, so the contains() methods always return false.
+ **/
+ public boolean contains(double x, double y) { return false; }
+ public boolean contains(Point2D p) { return false; }
+ public boolean contains(Rectangle2D r) { return false; }
+ public boolean contains(double x, double y, double w, double h) {
+ return false;
+ }
+
+ /**
+ * This method is allowed to approximate if it would be too computationally
+ * intensive to determine an exact answer. Therefore, we check whether
+ * the rectangle intersects a circle of the outer radius. This is a good
+ * guess for a tight spiral, but less good for a "loose" spiral.
+ **/
+ public boolean intersects(double x, double y, double w, double h) {
+ Shape approx = new Ellipse2D.Double(centerX-outerRadius,
+ centerY-outerRadius,
+ outerRadius*2, outerRadius*2);
+ return approx.intersects(x, y, w, h);
+ }
+
+ /** This version of intersects() just calls the one above */
+ public boolean intersects(Rectangle2D r) {
+ return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
+ }
+
+ /**
+ * This method is the heart of all Shape implementations. It returns a
+ * PathIterator that describes the shape in terms of the line and curve
+ * segments that comprise it. Our iterator implementation approximates
+ * the shape of the spiral using line segments only. We pass in a
+ * "flatness" argument that tells it how good the approximation must be.
+ * (smaller numbers mean a better approximation).
+ */
+ public PathIterator getPathIterator(AffineTransform at) {
+ return new SpiralIterator(at, outerRadius/500.0);
+ }
+
+ /**
+ * Return a PathIterator that describes the shape in terms of line
+ * segments only, with an approximation quality specified by flatness.
+ **/
+ public PathIterator getPathIterator(AffineTransform at, double flatness) {
+ return new SpiralIterator(at, flatness);
+ }
+
+ /**
+ * This inner class is the PathIterator for our Spiral shape. For
+ * simplicity, it does not describe the spiral path in terms of Bezier
+ * curve segments, but simply approximates it with line segments. The
+ * flatness property specifies how far the approximation is allowed to
+ * deviate from the true curve.
+ **/
+ class SpiralIterator implements PathIterator {
+ AffineTransform transform; // How to transform generated coordinates
+ double flatness; // How close an approximation
+ double angle = startAngle; // Current angle
+ double radius = startRadius; // Current radius
+ boolean done = false; // Are we done yet?
+
+ /** A simple constructor. Just store the parameters into fields */
+ public SpiralIterator(AffineTransform transform, double flatness) {
+ this.transform = transform;
+ this.flatness = flatness;
+ }
+
+ /**
+ * All PathIterators have a "winding rule" that helps to specify what
+ * is the inside of a area and what is the outside. If you fill a
+ * spiral (which you're not supposed to do) the winding rule returned
+ * here yields better results than the alternative, WIND_EVEN_ODD
+ **/
+ public int getWindingRule() { return WIND_NON_ZERO; }
+
+ /** Returns true if the entire path has been iterated */
+ public boolean isDone() { return done; }
+
+ /**
+ * Store the coordinates of the current segment of the path into the
+ * specified array, and return the type of the segment. Use
+ * trigonometry to compute the coordinates based on the current angle
+ * and radius. If this was the first point, return a MOVETO segment,
+ * otherwise return a LINETO segment. Also, check to see if we're done.
+ **/
+ public int currentSegment(float[] coords) {
+ // given the radius and the angle, compute the point coords
+ coords[0] = (float)(centerX + radius*Math.cos(angle));
+ coords[1] = (float)(centerY - radius*Math.sin(angle));
+
+ // If a transform was specified, use it on the coordinates
+ if (transform != null) transform.transform(coords, 0, coords, 0,1);
+
+ // If we've reached the end of the spiral remember that fact
+ if (angle == endAngle) done = true;
+
+ // If this is the first point in the spiral then move to it
+ if (angle == startAngle) return SEG_MOVETO;
+
+ // Otherwise draw a line from the previous point to this one
+ return SEG_LINETO;
+ }
+
+ /** This method is the same as above, except using double values */
+ public int currentSegment(double[] coords) {
+ coords[0] = centerX + radius*Math.cos(angle);
+ coords[1] = centerY - radius*Math.sin(angle);
+ if (transform != null) transform.transform(coords, 0, coords, 0,1);
+ if (angle == endAngle) done = true;
+ if (angle == startAngle) return SEG_MOVETO;
+ else return SEG_LINETO;
+ }
+
+ /**
+ * Move on to the next segment of the path. Compute the angle and
+ * radius values for the next point in the spiral.
+ **/
+ public void next() {
+ if (done) return;
+
+ // First, figure out how much to increment the angle. This
+ // depends on the required flatness, and also upon the current
+ // radius. When drawing a circle (which we'll use as our
+ // approximation) of radius r, we can maintain a flatness f by
+ // using angular increments given by this formula:
+ // a = acos(2*(f/r)*(f/r) - 4*(f/r) + 1)
+ // Use this formula to figure out how much we can increment the
+ // angle for the next segment. Note that the formula does not
+ // work well for very small radii, so we special case those.
+ double x = flatness/radius;
+ if (Double.isNaN(x) || (x > .1))
+ angle += Math.PI/4*angleDirection;
+ else {
+ double y = 2*x*x - 4*x + 1;
+ angle += Math.acos(y)*angleDirection;
+ }
+
+ // Check whether we've gone past the end of the spiral
+ if ((angle-endAngle)*angleDirection > 0) angle = endAngle;
+
+ // Now that we know the new angle, we can use interpolation to
+ // figure out what the corresponding radius is.
+ double fractionComplete = (angle-startAngle)/(endAngle-startAngle);
+ radius = startRadius + (endRadius-startRadius)*fractionComplete;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Stroking.java b/src/main/java/com/davidflanagan/examples/graphics/Stroking.java
new file mode 100644
index 0000000..616afd5
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Stroking.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+
+/** A demonstration of how Stroke objects work */
+public class Stroking implements GraphicsExample {
+ static final int WIDTH = 725, HEIGHT = 250; // Size of our example
+ public String getName() {return "Stroking";} // From GraphicsExample
+ public int getWidth() { return WIDTH; } // From GraphicsExample
+ public int getHeight() { return HEIGHT; } // From GraphicsExample
+
+ /** Draw the example */
+ public void draw(Graphics2D g, Component c) {
+ // Create the shape we'll work with. See convenience method below.
+ Shape pentagon = createRegularPolygon(5, 75);
+
+ // Set up basic drawing attributes
+ g.setColor(Color.black); // Draw in black
+ g.setStroke(new BasicStroke(1.0f)); // Use thin lines
+ g.setFont(new Font("SansSerif", Font.PLAIN, 12)); // Basic small font
+
+ g.translate(100, 100); // Move to position
+ g.draw(pentagon); // Outline the shape
+ g.drawString("The shape", -30, 90); // Draw the caption
+
+ g.translate(175, 0); // Move over
+ g.fill(pentagon); // Fill the shape
+ g.drawString("The filled shape", -50, 90); // Another caption
+
+ // Now use a Stroke object to create a "stroked shape" for our shape
+ BasicStroke wideline = new BasicStroke(10.0f);
+ Shape outline = wideline.createStrokedShape(pentagon);
+
+ g.translate(175, 0); // Move over
+ g.draw(outline); // Draw the stroked shape
+ g.drawString("A Stroke creates",-50,90); // Draw the caption
+ g.drawString("a new shape", -35, 105);
+
+ g.translate(175,0); // Move over
+ g.fill(outline); // Fill the stroked shape
+ g.drawString("Filling the new shape",-65,90); // Draw the caption
+ g.drawString("outlines the old one",-65,105);
+ }
+
+ // A convenience method to define a regular polygon.
+ // Returns a shape that represents a regular polygon with the specified
+ // radius and number of sides, and centered at the origin.
+ public Shape createRegularPolygon(int numsides, int radius) {
+ Polygon p = new Polygon();
+ double angle = 2 * Math.PI / numsides; // Angle between vertices
+ for(int i = 0; i < numsides; i++) // Compute location of each vertex
+ p.addPoint((int)(radius * Math.sin(angle*i)),
+ (int)(radius * -Math.cos(angle*i)));
+ return p;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/Transforms.java b/src/main/java/com/davidflanagan/examples/graphics/Transforms.java
new file mode 100644
index 0000000..950da35
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/graphics/Transforms.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.graphics;
+import java.awt.*;
+import java.awt.geom.*;
+
+/** A demonstration of Java2D transformations */
+public class Transforms implements GraphicsExample {
+ public String getName() { return "Transforms"; } // From GraphicsExample
+ public int getWidth() { return 750; } // From GraphicsExample
+ public int getHeight() { return 250; } // From GraphicsExample
+
+ Shape shape; // The shape to draw
+ AffineTransform[] transforms; // The ways to transform it
+ String[] transformLabels; // Labels for each transform
+
+ /**
+ * This constructor sets up the Shape and AffineTransform objects we need
+ **/
+ public Transforms() {
+ GeneralPath path = new GeneralPath(); // Create a shape to draw
+ path.append(new Line2D.Float(0.0f, 0.0f, 0.0f, 100.0f), false);
+ path.append(new Line2D.Float(-10.0f, 50.0f, 10.0f, 50.0f), false);
+ path.append(new Polygon(new int[] { -5, 0, 5 },
+ new int[] { 5, 0, 5 }, 3), false);
+ this.shape = path; // Remember this shape
+
+ // Set up some transforms to alter the shape
+ this.transforms = new AffineTransform[6];
+ // 1) the identity transform
+ transforms[0] = new AffineTransform();
+ // 2) A scale tranform: 3/4 size
+ transforms[1] = AffineTransform.getScaleInstance(0.75, 0.75);
+ // 3) A shearing transform
+ transforms[2] = AffineTransform.getShearInstance(-0.4, 0.0);
+ // 4) A 30 degree clockwise rotation about the origin of the shape
+ transforms[3] = AffineTransform.getRotateInstance(Math.PI*2/12);
+ // 5) A 180 degree rotation about the midpoint of the shape
+ transforms[4] = AffineTransform.getRotateInstance(Math.PI, 0.0, 50.0);
+ // 6) A combination transform
+ transforms[5] = AffineTransform.getScaleInstance(0.5, 1.5);
+ transforms[5].shear(0.0, 0.4);
+ transforms[5].rotate(Math.PI/2, 0.0, 50.0); // 90 degrees
+
+ // Define names for the transforms
+ transformLabels = new String[] {
+ "identity", "scale", "shear", "rotate", "rotate", "combo"
+ };
+ }
+
+ /** Draw the defined shape and label, using each transform */
+ public void draw(Graphics2D g, Component c) {
+ // Define basic drawing attributes
+ g.setColor(Color.black); // black
+ g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_SQUARE, // 2-pixel
+ BasicStroke.JOIN_BEVEL));
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // antialias
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // Now draw the shape once using each of the transforms we've defined
+ for(int i = 0; i < transforms.length; i++) {
+ AffineTransform save = g.getTransform(); // save current state
+ g.translate(i*125 + 50, 50); // move origin
+ g.transform(transforms[i]); // apply transform
+ g.draw(shape); // draw shape
+ g.drawString(transformLabels[i], -25, 125); // draw label
+ g.drawRect(-40, -10, 80, 150); // draw box
+ g.setTransform(save); // restore transform
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/graphics/background.gif b/src/main/java/com/davidflanagan/examples/graphics/background.gif
new file mode 100644
index 0000000..34072cf
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/background.gif differ
diff --git a/src/main/java/com/davidflanagan/examples/graphics/cover.gif b/src/main/java/com/davidflanagan/examples/graphics/cover.gif
new file mode 100644
index 0000000..d4c58a8
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/cover.gif differ
diff --git a/src/main/java/com/davidflanagan/examples/graphics/tiger.gif b/src/main/java/com/davidflanagan/examples/graphics/tiger.gif
new file mode 100644
index 0000000..b455f3e
Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/tiger.gif differ
diff --git a/src/main/java/com/davidflanagan/examples/gui/ActionParser.java b/src/main/java/com/davidflanagan/examples/gui/ActionParser.java
new file mode 100644
index 0000000..ebdb6bd
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ActionParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import com.davidflanagan.examples.reflect.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * This class parses an Action object from a GUIResourceBundle.
+ * The specified key is used to look up the Command string for the action.
+ * The key is also used as a prefix for other resource names that specify
+ * other attributes (such as the label and icon) associated with the Action.
+ * An action named "zoomOut" might be specified like this:
+ *
+ * zoomOut: zoom(0.5);
+ * zoomOut.label: Zoom Out
+ * zoomOut.description: Zoom out by a factor of 2
+ *
+ * Because Action objects are often reused by an application (for example
+ * in a toolbar and a menu system, this ResourceParser caches the Action
+ * objects it returns. By sharing Action objects, you can disable and enable
+ * an action and that change will affect the entire GUI.
+ **/
+public class ActionParser implements ResourceParser {
+ static final Class[] supportedTypes = new Class[] { Action.class };
+ public Class[] getResourceTypes() { return supportedTypes; }
+
+ HashMap bundleToCacheMap = new HashMap();
+
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws MissingResourceException
+ {
+ // Look up the Action cache associated with this bundle
+ HashMap cache = (HashMap) bundleToCacheMap.get(bundle);
+ if (cache == null) { // If there isn't one, create one and save it
+ cache = new HashMap();
+ bundleToCacheMap.put(bundle, cache);
+ }
+ // Now look up the Action associated with the key in the cache.
+ Action action = (Action) cache.get(key);
+ // If we found a cached action, return it.
+ if (action != null) return action;
+
+ // If there was no cached action create one. The command is
+ // the only required resource. It will throw an exception if
+ // missing or malformed.
+ Command command = (Command) bundle.getResource(key, Command.class);
+
+ // The remaining calls all supply default values, so they will not
+ // throw exceptions, even if ResourceParsers haven't been registered
+ // for types like Icon and KeyStroke
+ String label = bundle.getString(key + ".label", null);
+ Icon icon = (Icon) bundle.getResource(key + ".icon", Icon.class, null);
+ String tooltip = bundle.getString(key + ".description", null);
+ KeyStroke accelerator =
+ (KeyStroke) bundle.getResource(key + ".accelerator",
+ KeyStroke.class, null);
+ int mnemonic = bundle.getInt(key + ".mnemonic", KeyEvent.VK_UNDEFINED);
+ boolean enabled = bundle.getBoolean(key + ".enabled", true);
+
+ // Create a CommandAction object with these values
+ action = new CommandAction(command, label, icon, tooltip,
+ accelerator, mnemonic, enabled);
+
+ // Save it in the cache, then return it
+ cache.put(key, action);
+ return action;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java
new file mode 100644
index 0000000..585bde9
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+public class AppletMenuBar extends Panel {
+ // Menubar contents
+ Vector labels = new Vector();
+ Vector menus = new Vector();
+
+ // Properties
+ Insets margins = new Insets(3, 10, 3, 10); // top, left, bottom, right
+ int spacing = 10; // Space between menu labels
+ Color highlightColor; // Rollover color for labels
+
+ // internal stuff
+ boolean remeasure = true; // Whether the labels need to be remeasured
+ int[] widths; // The width of each label
+ int[] startPositions; // Where each label starts
+ int ascent, descent; // Font metrics
+ Dimension prefsize = new Dimension(); // How big do we want to be?
+ int highlightedItem = -1; // Which item is the mouse over?
+
+ /**
+ * Create a new component that simulates a menubar by displaying
+ * the specified labels. Whenever the user clicks the specified label,
+ * popup up the PopupMenu specified in the menus array.
+ * Elements of the menus arra may be a static PopupMenu object, or
+ * a PopupMenuFactory object for dynamically creating menus.
+ * Perhaps we'll also provide some other kind of constructor or factory
+ * method that reads popup menus out of a config file.
+ */
+ public AppletMenuBar() {
+ // We'd like these kinds of events to be delivered
+ enableEvents(AWTEvent.MOUSE_EVENT_MASK |
+ AWTEvent.MOUSE_MOTION_EVENT_MASK);
+ }
+
+ /** Add a popup menu to the menubar */
+ public void addMenu(String label, PopupMenu menu) {
+ insertMenu(label, menu, -1);
+ }
+
+ /** Insert a popup menu into the menubar */
+ public void insertMenu(String label, PopupMenu menu, int index) {
+ if (index < 0) index += labels.size()+1; // Position to put it at
+ this.add(menu); // Popup belongs to us
+ labels.insertElementAt(label, index); // Remember the label
+ menus.insertElementAt(menu, index); // Remember the menu
+ remeasure = true; // Remeasure everything
+ invalidate(); // Container must relayout
+ }
+
+ /** Property accessor methods for margins property */
+ public Insets getMargins() { return (Insets) margins.clone(); }
+ public void setMargins(Insets margins) {
+ this.margins = margins;
+ remeasure = true;
+ invalidate();
+ }
+
+ /** Property accessor methods for spacing property */
+ public int getSpacing() { return spacing; }
+ public void setSpacing(int spacing) {
+ if (this.spacing != spacing) {
+ this.spacing = spacing;
+ remeasure = true;
+ invalidate();
+ }
+ }
+
+ /** Accessor methods for highlightColor property */
+ public Color getHighlightColor() {
+ if (highlightColor == null) return getForeground();
+ else return highlightColor;
+ }
+ public void setHighlightColor(Color c) {
+ if (highlightColor != c) {
+ highlightColor = c;
+ repaint();
+ }
+ }
+
+ /** We override the setFont() method so we can remeasure */
+ public void setFont(Font f) {
+ super.setFont(f);
+ remeasure = true;
+ invalidate();
+ }
+
+ /** Override these color property setter method so we can repaint */
+ public void setForeground(Color c) {
+ super.setForeground(c);
+ repaint();
+ }
+ public void setBackground(Color c) {
+ super.setBackground(c);
+ repaint();
+ }
+
+ /**
+ * This method is called to draw tell the component to redraw itself.
+ * If we were implementing a Swing component, we'd override
+ * paintComponent() instead
+ **/
+ public void paint(Graphics g) {
+ if (remeasure) measure(); // Remeasure everything first, if needed
+
+ // Figure out Y coordinate to draw at
+ Dimension size = getSize();
+ int baseline = size.height - margins.bottom - descent;
+ // Set the font to draw with
+ g.setFont(getFont());
+ // Loop through the labels
+ int nummenus = labels.size();
+ for(int i = 0; i < nummenus; i++) {
+ // Set the drawing color. Highlight the current item
+ if ((i == highlightedItem) && (highlightColor != null))
+ g.setColor(getHighlightColor());
+ else
+ g.setColor(getForeground());
+
+ // Draw the menu label at the position computed in measure()
+ g.drawString((String)labels.elementAt(i),
+ startPositions[i], baseline);
+ }
+
+ // Now draw a groove at the bottom of the menubar.
+ Color bg = getBackground();
+ g.setColor(bg.darker());
+ g.drawLine(0, size.height-2, size.width, size.height-2);
+ g.setColor(bg.brighter());
+ g.drawLine(0, size.height-1, size.width, size.height-1);
+ }
+
+ /** Called when a mouse event happens over the menubar */
+ protected void processMouseEvent(MouseEvent e) {
+ int type = e.getID(); // What type of event?
+ int item = findItemAt(e.getX()); // Over which menu label?
+
+ if (type == MouseEvent.MOUSE_PRESSED) {
+ // If it was a mouse down event, then pop up the menu
+ if (item == -1) return;
+ Dimension size = getSize();
+ PopupMenu pm = (PopupMenu) menus.elementAt(item);
+ if (pm != null) pm.show(this, startPositions[item]-3, size.height);
+
+ }
+ else if (type == MouseEvent.MOUSE_EXITED) {
+ // If the mouse left the menubar, then unhighlight
+ if (highlightedItem != -1) {
+ highlightedItem = -1;
+ if (highlightColor != null) repaint();
+ }
+ }
+ else if ((type == MouseEvent.MOUSE_MOVED) ||
+ (type == MouseEvent.MOUSE_ENTERED)) {
+ // If the mouse moved, change the highlighted item, if necessary
+ if (item != highlightedItem) {
+ highlightedItem = item;
+ if (highlightColor != null) repaint();
+ }
+ }
+ }
+
+ /** This method is called when the mouse moves */
+ protected void processMouseMotionEvent(MouseEvent e) {
+ processMouseEvent(e);
+ }
+
+ /** This utility method converts an X coordinate to a menu label index */
+ protected int findItemAt(int x) {
+ // This could be a more efficient search...
+ int nummenus = labels.size();
+ int halfspace = spacing/2-1;
+ int i;
+ for(i = nummenus-1; i >= 0; i--) {
+ if ((x >= startPositions[i]-halfspace) &&
+ (x <= startPositions[i]+widths[i]+halfspace)) break;
+ }
+ return i;
+ }
+
+
+ /**
+ * Measure the menu labels, and figure out their positions, so we
+ * can determine when a click happens, and so we can redraw efficiently.
+ **/
+ protected void measure() {
+ // Get information about the font
+ FontMetrics fm = this.getFontMetrics(getFont());
+ // Remember the basic font size
+ ascent = fm.getAscent();
+ descent = fm.getDescent();
+ // Create arrays to hold the measurements and positions
+ int nummenus = labels.size();
+ widths = new int[nummenus];
+ startPositions = new int[nummenus];
+
+ // Measure the label strings and
+ // figure out the starting position of each label
+ int pos = margins.left;
+ for(int i = 0; i < nummenus; i++) {
+ startPositions[i] = pos;
+ String label = (String)labels.elementAt(i);
+ widths[i] = fm.stringWidth(label);
+ pos += widths[i] + spacing;
+ }
+
+ // Compute our preferred size from this data
+ prefsize.width = pos - spacing + margins.right;
+ prefsize.height = ascent + descent + margins.top + margins.bottom;
+
+ // We've don't need to be remeasured anymore.
+ remeasure = false;
+ }
+
+ /**
+ * These methods tell the container how big the menubar wants to be.
+ *
+ **/
+ public Dimension getMinimumSize() { return getPreferredSize(); }
+ public Dimension getPreferredSize() {
+ if (remeasure) measure();
+ return prefsize;
+ }
+ /** @deprecated Here for compatibility with Java 1.0 */
+ public Dimension minimumSize() { return getPreferredSize(); }
+ /** @deprecated Here for compatibility with Java 1.0 */
+ public Dimension preferredSize() { return getPreferredSize(); }
+
+ /**
+ * This method is called when the underlying AWT component is created.
+ * We can't measure ourselves (no font metrics) until this is called.
+ **/
+ public void addNotify() {
+ super.addNotify();
+ measure();
+ }
+
+ /** This method tells the container not to give us keyboard focus */
+ public boolean isFocusTraversable() { return false; }
+}
+
diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html
new file mode 100644
index 0000000..adfa9cb
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html
@@ -0,0 +1,4 @@
+
diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java
new file mode 100644
index 0000000..35067fb
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.applet.*;
+
+public class AppletMenuBarDemo extends Applet {
+ public void init() {
+ AppletMenuBar menubar = new AppletMenuBar();
+ menubar.setForeground(Color.black);
+ menubar.setHighlightColor(Color.red);
+ menubar.setFont(new Font("helvetica", Font.BOLD, 12));
+ this.setLayout(new BorderLayout());
+ this.add(menubar, BorderLayout.NORTH);
+
+ PopupMenu file = new PopupMenu();
+ file.add("New..."); file.add("Open..."); file.add("Save As...");
+ PopupMenu edit = new PopupMenu();
+ edit.add("Cut"); edit.add("Copy"); edit.add("Paste");
+
+ menubar.addMenu("File", file);
+ menubar.addMenu("Edit", edit);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java
new file mode 100644
index 0000000..70e889a
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class BorderLayoutPane extends JPanel {
+ String[] borders = {"North", "East", "South", "West", "Center"};
+ public BorderLayoutPane() {
+ // Use a BorderLayout with 10-pixel margins between components
+ this.setLayout(new BorderLayout(10, 10));
+ for(int i = 0; i < 5; i++) { // Add children to the pane
+ this.add(new JButton(borders[i]), // Add this component
+ borders[i]); // Using this constraint
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java
new file mode 100644
index 0000000..54caac5
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+
+public class BoxLayoutPane extends JPanel {
+ public BoxLayoutPane() {
+ // Use a BorderLayout layout manager to arrange various Box components
+ this.setLayout(new BorderLayout());
+
+ // Give the entire panel a margin by adding an empty border
+ // We could also do this by overriding getInsets()
+ this.setBorder(new EmptyBorder(10,10,10,10));
+
+ // Add a plain row of buttons along the top of the pane
+ Box row = Box.createHorizontalBox();
+ for(int i = 0; i < 4; i++) {
+ JButton b = new JButton("B" + i);
+ b.setFont(new Font("serif", Font.BOLD, 12+i*2));
+ row.add(b);
+ }
+ this.add(row, BorderLayout.NORTH);
+
+ // Add a plain column of buttons along the right edge
+ // Use BoxLayout with a different kind of Swing container
+ // Give the column a border: can't do this with the Box class
+ JPanel col = new JPanel();
+ col.setLayout(new BoxLayout(col, BoxLayout.Y_AXIS));
+ col.setBorder(new TitledBorder(new EtchedBorder(), "Column"));
+ for(int i = 0; i < 4; i++) {
+ JButton b = new JButton("Button " + i);
+ b.setFont(new Font("sanserif", Font.BOLD, 10+i*2));
+ col.add(b);
+ }
+ this.add(col, BorderLayout.EAST); // Add column to right of panel
+
+ // Add a button box along the bottom of the panel.
+ // Use "Glue" to space the buttons evenly
+ Box buttonbox = Box.createHorizontalBox();
+ buttonbox.add(Box.createHorizontalGlue()); // stretchy space
+ buttonbox.add(new JButton("Okay"));
+ buttonbox.add(Box.createHorizontalGlue()); // stretchy space
+ buttonbox.add(new JButton("Cancel"));
+ buttonbox.add(Box.createHorizontalGlue()); // stretchy space
+ buttonbox.add(new JButton("Help"));
+ buttonbox.add(Box.createHorizontalGlue()); // stretchy space
+ this.add(buttonbox, BorderLayout.SOUTH);
+
+ // Create a component to display in the center of the panel
+ JTextArea textarea = new JTextArea();
+ textarea.setText("This component has 12-pixel margins on left and top"+
+ " and has 72-pixel margins on right and bottom.");
+ textarea.setLineWrap(true);
+ textarea.setWrapStyleWord(true);
+
+ // Use Box objects to give the JTextArea an unusual spacing
+ // First, create a column with 3 kids. The first and last kids
+ // are rigid spaces. The middle kid is the text area
+ Box fixedcol = Box.createVerticalBox();
+ fixedcol.add(Box.createVerticalStrut(12)); // 12 rigid pixels
+ fixedcol.add(textarea); // Component fills in the rest
+ fixedcol.add(Box.createVerticalStrut(72)); // 72 rigid pixels
+
+ // Now create a row. Give it rigid spaces on the left and right,
+ // and put the column from above in the middle.
+ Box fixedrow = Box.createHorizontalBox();
+ fixedrow.add(Box.createHorizontalStrut(12));
+ fixedrow.add(fixedcol);
+ fixedrow.add(Box.createHorizontalStrut(72));
+
+ // Now add the JTextArea in the column in the row to the panel
+ this.add(fixedrow, BorderLayout.CENTER);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java b/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java
new file mode 100644
index 0000000..33bab07
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+
+/**
+ * This LayoutManager arranges the components into a column.
+ * Components are always given their preferred size.
+ *
+ * When you create a ColumnLayout, you may specify four values:
+ * margin_height -- how much space to leave on top and bottom
+ * margin_width -- how much space to leave on left and right
+ * spacing -- how much vertical space to leave between items
+ * alignment -- the horizontal position of the components:
+ * ColumnLayout.LEFT -- left-justify the components
+ * ColumnLayout.CENTER -- horizontally center the components
+ * ColumnLayout.RIGHT -- right-justify the components
+ *
+ * You never call the methods of a ColumnLayout object. Just create one
+ * and make it the layout manager for your container by passing it to
+ * the addLayout() method of the Container object.
+ */
+public class ColumnLayout implements LayoutManager2 {
+ protected int margin_height;
+ protected int margin_width;
+ protected int spacing;
+ protected int alignment;
+
+ // Constants for the alignment argument to the constructor.
+ public static final int LEFT = 0;
+ public static final int CENTER = 1;
+ public static final int RIGHT = 2;
+
+ /** The constructor. See comment above for meanings of these arguments */
+ public ColumnLayout(int margin_height, int margin_width,
+ int spacing, int alignment) {
+ this.margin_height = margin_height;
+ this.margin_width = margin_width;
+ this.spacing = spacing;
+ this.alignment = alignment;
+ }
+
+ /**
+ * A default constructor that creates a ColumnLayout using 5-pixel
+ * margin width and height, 5-pixel spacing, and left alignment
+ **/
+ public ColumnLayout() { this(5, 5, 5, LEFT); }
+
+ /**
+ * The method that actually performs the layout.
+ * Called by the Container
+ **/
+ public void layoutContainer(Container parent) {
+ Insets insets = parent.getInsets();
+ Dimension parent_size = parent.getSize();
+ Component kid;
+ int nkids = parent.getComponentCount();
+ int x0 = insets.left + margin_width; // The base X position
+ int x;
+ int y = insets.top + margin_height; // Start at the top of the column
+
+ for(int i = 0; i < nkids; i++) { // Loop through the kids
+ kid = parent.getComponent(i); // Get the kid
+ if (!kid.isVisible()) continue; // Skip hidden ones
+ Dimension pref = kid.getPreferredSize(); // How big is it?
+ switch(alignment) { // Compute X coordinate
+ default:
+ case LEFT: x = x0; break;
+ case CENTER: x = (parent_size.width - pref.width)/2; break;
+ case RIGHT:
+ x = parent_size.width-insets.right-margin_width-pref.width;
+ break;
+ }
+ // Set the size and position of this kid
+ kid.setBounds(x, y, pref.width, pref.height);
+ y += pref.height + spacing; // Get Y position of the next one
+ }
+ }
+
+ /** The Container calls this to find out how big the layout should to be */
+ public Dimension preferredLayoutSize(Container parent) {
+ return layoutSize(parent, 1);
+ }
+ /** The Container calls this to find out how big the layout must be */
+ public Dimension minimumLayoutSize(Container parent) {
+ return layoutSize(parent, 2);
+ }
+ /** The Container calls this to find out how big the layout can be */
+ public Dimension maximumLayoutSize(Container parent) {
+ return layoutSize(parent, 3);
+ }
+
+ // Compute min, max, or preferred size of all the visible children
+ protected Dimension layoutSize(Container parent, int sizetype) {
+ int nkids = parent.getComponentCount();
+ Dimension size = new Dimension(0,0);
+ Insets insets = parent.getInsets();
+ int num_visible_kids = 0;
+
+ // Compute maximum width and total height of all visible kids
+ for(int i = 0; i < nkids; i++) {
+ Component kid = parent.getComponent(i);
+ Dimension d;
+ if (!kid.isVisible()) continue;
+ num_visible_kids++;
+ if (sizetype == 1) d = kid.getPreferredSize();
+ else if (sizetype == 2) d = kid.getMinimumSize();
+ else d = kid.getMaximumSize();
+ if (d.width > size.width) size.width = d.width;
+ size.height += d.height;
+ }
+
+ // Now add in margins and stuff
+ size.width += insets.left + insets.right + 2*margin_width;
+ size.height += insets.top + insets.bottom + 2*margin_height;
+ if (num_visible_kids > 1)
+ size.height += (num_visible_kids - 1) * spacing;
+ return size;
+ }
+
+ // Other LayoutManager(2) methods that are unused by this class
+ public void addLayoutComponent(String constraint, Component comp) {}
+ public void addLayoutComponent(Component comp, Object constraint) {}
+ public void removeLayoutComponent(Component comp) {}
+ public void invalidateLayout(Container parent) {}
+ public float getLayoutAlignmentX(Container parent) { return 0.5f; }
+ public float getLayoutAlignmentY(Container parent) { return 0.5f; }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java
new file mode 100644
index 0000000..21f3636
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class ColumnLayoutPane extends JPanel {
+ public ColumnLayoutPane() {
+ // Get rid of the default layout manager.
+ // We'll arrange the components ourselves.
+ this.setLayout(new ColumnLayout(5, 5, 10, ColumnLayout.RIGHT));
+
+ // Create some buttons and set their sizes and positions explicitly
+ for(int i = 0; i < 6; i++) {
+ int pointsize = 8 + i*2;
+ JButton b = new JButton("Point size " + pointsize);
+ b.setFont(new Font("helvetica", Font.BOLD, pointsize));
+ this.add(b);
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/CommandAction.java b/src/main/java/com/davidflanagan/examples/gui/CommandAction.java
new file mode 100644
index 0000000..a70bfda
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/CommandAction.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import com.davidflanagan.examples.reflect.*;
+import javax.swing.*;
+import java.awt.event.*;
+
+public class CommandAction extends AbstractAction {
+ Command command; // The command to execute in response to an ActionEvent
+
+ /**
+ * Create an Action object that has the various specified attributes,
+ * and invokes the specified Command object in response to ActionEvents
+ **/
+ public CommandAction(Command command, String label,
+ Icon icon, String tooltip,
+ KeyStroke accelerator, int mnemonic,
+ boolean enabled)
+ {
+ this.command = command; // Remember the command to invoke
+
+ // Set the various action attributes with putValue()
+ if (label != null) putValue(NAME, label);
+ if (icon != null) putValue(SMALL_ICON, icon);
+ if (tooltip != null) putValue(SHORT_DESCRIPTION, tooltip);
+ if (accelerator != null) putValue(ACCELERATOR_KEY, accelerator);
+ if (mnemonic != KeyEvent.VK_UNDEFINED)
+ putValue(MNEMONIC_KEY, new Integer(mnemonic));
+
+ // Tell the action whether it is currently enabled or not
+ setEnabled(enabled);
+ }
+
+ /**
+ * This method implements ActionListener, which is a super-interface of
+ * Action. When a component generates an ActionEvent, it is passed to
+ * this method. This method simply passes it on to the Command object
+ * which is also an ActionListener object
+ **/
+ public void actionPerformed(ActionEvent e) { command.actionPerformed(e); }
+
+ // These constants are defined by Action in Java 1.3.
+ // For compatibility with Java 1.2, we re-define them here.
+ public static final String ACCELERATOR_KEY = "AcceleratorKey";
+ public static final String MNEMONIC_KEY = "MnemonicKey";
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/CommandParser.java b/src/main/java/com/davidflanagan/examples/gui/CommandParser.java
new file mode 100644
index 0000000..62cbfdb
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/CommandParser.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import com.davidflanagan.examples.reflect.Command;
+
+/**
+ * This class parses a Command object from a GUIResourceBundle. It uses
+ * the Command.parse() method to perform all the actual parsing work.
+ **/
+public class CommandParser implements ResourceParser {
+ static final Class[] supportedTypes = new Class[] { Command.class };
+ public Class[] getResourceTypes() { return supportedTypes;}
+
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws java.util.MissingResourceException, java.io.IOException
+ {
+ String value = bundle.getString(key); // look up the command text
+ return Command.parse(bundle.getRoot(), value); // parse it!
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java b/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java
new file mode 100644
index 0000000..244c60d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.tree.*;
+
+/**
+ * This class is a JTree subclass that displays the tree of AWT or Swing
+ * component that make up a GUI.
+ **/
+public class ComponentTree extends JTree {
+ /**
+ * All this constructor method has to do is set the TreeModel and
+ * TreeCellRenderer objects for the tree. It is these classes (defined
+ * below) that do all the real work.
+ **/
+ public ComponentTree(Component c) {
+ super(new ComponentTreeModel(c));
+ setCellRenderer(new ComponentCellRenderer(getCellRenderer()));
+ }
+
+ /**
+ * The TreeModel class puts hierarchical data in a form that the JTree
+ * can display. This implementation interprets the containment hierarchy
+ * of a Component for display by the ComponentTree class. Note that any
+ * kind of Object can be a node in the tree, as long as the TreeModel knows
+ * how to handle it.
+ **/
+ static class ComponentTreeModel implements TreeModel {
+ Component root; // The root object of the tree
+
+ // Constructor: just remember the root object
+ public ComponentTreeModel(Component root) { this.root = root; }
+
+ // Return the root of the tree
+ public Object getRoot() { return root; }
+
+ // Is this node a leaf? (Leaf nodes are displayed differently by JTree)
+ // Any node that isn't a container is a leaf, since they cannot have
+ // children. We also define containers with no children as leaves.
+ public boolean isLeaf(Object node) {
+ if (!(node instanceof Container)) return true;
+ Container c = (Container) node;
+ return c.getComponentCount() == 0;
+ }
+
+ // How many children does this node have?
+ public int getChildCount(Object node) {
+ if (node instanceof Container) {
+ Container c = (Container) node;
+ return c.getComponentCount();
+ }
+ return 0;
+ }
+
+ // Return the specified child of a parent node.
+ public Object getChild(Object parent, int index) {
+ if (parent instanceof Container) {
+ Container c = (Container) parent;
+ return c.getComponent(index);
+ }
+ return null;
+ }
+
+ // Return the index of the child node in the parent node
+ public int getIndexOfChild(Object parent, Object child) {
+ if (!(parent instanceof Container)) return -1;
+ Container c = (Container) parent;
+ Component[] children = c.getComponents();
+ if (children == null) return -1;
+ for(int i = 0; i < children.length; i++) {
+ if (children[i] == child) return i;
+ }
+ return -1;
+ }
+
+ // This method is only required for editable trees, so it is not
+ // implemented here.
+ public void valueForPathChanged(TreePath path, Object newvalue) {}
+
+ // This TreeModel never fires any events (since it is not editable)
+ // so event listener registration methods are left unimplemented
+ public void addTreeModelListener(TreeModelListener l) {}
+ public void removeTreeModelListener(TreeModelListener l) {}
+ }
+
+
+ /**
+ * A TreeCellRenderer displays each node of a tree. The default renderer
+ * displays arbitrary Object nodes by calling their toString() method.
+ * The Component.toString() method returns long strings with extraneous
+ * information. Therefore, we use this "wrapper" implementation of
+ * TreeCellRenderer to convert nodes from Component objects to useful
+ * String values before passing those String values on to the default
+ * renderer.
+ **/
+ static class ComponentCellRenderer implements TreeCellRenderer {
+ TreeCellRenderer renderer; // The renderer we are a wrapper for
+ // Constructor: just remember the renderer
+ public ComponentCellRenderer(TreeCellRenderer renderer) {
+ this.renderer = renderer;
+ }
+
+ // This is the only TreeCellRenderer method.
+ // Compute the string to display, and pass it to the wrapped renderer
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected,
+ boolean expanded,
+ boolean leaf, int row,
+ boolean hasFocus) {
+ String newvalue = value.getClass().getName(); // Component type
+ String name = ((Component)value).getName(); // Component name
+ if (name != null) newvalue += " (" + name + ")"; // unless null
+ // Use the wrapped renderer object to do the real work
+ return renderer.getTreeCellRendererComponent(tree, newvalue,
+ selected, expanded,
+ leaf, row, hasFocus);
+ }
+ }
+
+ /**
+ * This main() method demonstrates the use of the ComponentTree class: it
+ * puts a ComponentTree component in a Frame, and uses the ComponentTree
+ * to display its own GUI hierarchy. It also adds a TreeSelectionListener
+ * to display additional information about each component as it is selected
+ **/
+ public static void main(String[] args) {
+ // Create a frame for the demo, and handle window close requests
+ JFrame frame = new JFrame("ComponentTree Demo");
+ frame.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+
+ // Create a scroll pane and a "message line" and add them to the
+ // center and bottom of the frame.
+ JScrollPane scrollpane = new JScrollPane();
+ final JLabel msgline = new JLabel(" ");
+ frame.getContentPane().add(scrollpane, BorderLayout.CENTER);
+ frame.getContentPane().add(msgline, BorderLayout.SOUTH);
+
+ // Now create the ComponentTree object, specifying the frame as the
+ // component whose tree is to be displayed. Also set the tree's font.
+ JTree tree = new ComponentTree(frame);
+ tree.setFont(new Font("SansSerif", Font.BOLD, 12));
+
+ // Only allow a single item in the tree to be selected at once
+ tree.getSelectionModel().setSelectionMode(
+ TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+ // Add an event listener for notifications when
+ // the tree selection state changes.
+ tree.addTreeSelectionListener(new TreeSelectionListener() {
+ public void valueChanged(TreeSelectionEvent e) {
+ // Tree selections are referred to by "path"
+ // We only care about the last node in the path
+ TreePath path = e.getPath();
+ Component c = (Component) path.getLastPathComponent();
+ // Now we know what component was selected, so
+ // display some information about it in the message line
+ if (c.isShowing()) {
+ Point p = c.getLocationOnScreen();
+ msgline.setText("x: " + p.x + " y: " + p.y +
+ " width: " + c.getWidth() +
+ " height: " + c.getHeight());
+ }
+ else {
+ msgline.setText("component is not showing");
+ }
+ }
+ });
+
+ // Now that we've set up the tree, add it to the scrollpane
+ scrollpane.setViewportView(tree);
+
+ // Finally, set the size of the main window, and pop it up.
+ frame.setSize(600, 400);
+ frame.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/Containers.java b/src/main/java/com/davidflanagan/examples/gui/Containers.java
new file mode 100644
index 0000000..5249b7f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/Containers.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * A component subclass that demonstrates nested containers and components.
+ * It creates the hierarchy shown below, and uses different colors to
+ * distinguish the different nesting levels of the containers
+ *
+ * Containers---panel1----button1
+ * | |---panel2----button2
+ * | | |----panel3----button3
+ * | |------panel4----button4
+ * | |----button5
+ * |---button6
+ */
+public class Containers extends JPanel {
+ public Containers() {
+ this.setBackground(Color.white); // This component is white
+ this.setFont(new Font("Dialog", Font.BOLD, 24));
+
+ JPanel p1 = new JPanel();
+ p1.setBackground(new Color(200, 200, 200)); // Panel1 is darker
+ this.add(p1); // p1 is contained by this component
+ p1.add(new JButton("#1")); // Button 1 is contained in p1
+
+ JPanel p2 = new JPanel();
+ p2.setBackground(new Color(150, 150, 150)); // p2 is darker than p2
+ p1.add(p2); // p2 is contained in p1
+ p2.add(new JButton("#2")); // Button 2 is contained in p2
+
+ JPanel p3 = new JPanel();
+ p3.setBackground(new Color(100, 100, 100)); // p3 is darker than p2
+ p2.add(p3); // p3 is contained in p2
+ p3.add(new JButton("#3")); // Button 3 is contained in p3
+
+ JPanel p4 = new JPanel();
+ p4.setBackground(new Color(150, 150, 150)); // p4 is darker than p1
+ p1.add(p4); // p4 is contained in p1
+ p4.add(new JButton("#4")); // Button4 is contained in p4
+ p4.add(new JButton("#5")); // Button5 is also contained in p4
+
+ this.add(new JButton("#6")); // Button6 is contained in this component
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java b/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java
new file mode 100644
index 0000000..35afda5
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+
+/** A program that displays all the event that occur in its window */
+public class EventTestPane extends JPanel {
+ /** The constructor: register the event types we are interested in */
+ public EventTestPane() {
+ // We're interested in all types of events
+ this.enableEvents(AWTEvent.MOUSE_EVENT_MASK |
+ AWTEvent.MOUSE_MOTION_EVENT_MASK |
+ AWTEvent.KEY_EVENT_MASK |
+ AWTEvent.FOCUS_EVENT_MASK |
+ AWTEvent.COMPONENT_EVENT_MASK |
+ AWTEvent.WINDOW_EVENT_MASK);
+ this.setPreferredSize(new Dimension(500, 400));
+ }
+
+ /**
+ * Display mouse events that don't involve mouse motion. The mousemods()
+ * method prints modifiers, and is defined below. The other methods
+ * return additional information about the mouse event. showLine()
+ * displays a line of text in the window. It is defined at the end of
+ * this class, along with the paintComponent() method.
+ **/
+ public void processMouseEvent(MouseEvent e) {
+ String type = null;
+ switch(e.getID()) {
+ case MouseEvent.MOUSE_PRESSED: type = "MOUSE_PRESSED"; break;
+ case MouseEvent.MOUSE_RELEASED: type = "MOUSE_RELEASED"; break;
+ case MouseEvent.MOUSE_CLICKED: type = "MOUSE_CLICKED"; break;
+ case MouseEvent.MOUSE_ENTERED: type = "MOUSE_ENTERED"; break;
+ case MouseEvent.MOUSE_EXITED: type = "MOUSE_EXITED"; break;
+ }
+ showLine(mousemods(e) + type +
+ ": [" + e.getX() + "," + e.getY() + "] " +
+ "num clicks = " + e.getClickCount() +
+ (e.isPopupTrigger()?"; is popup trigger":""));
+
+ // When the mouse enters the component, request keyboard focus so
+ // we can receive and respond to keyboard events
+ if (e.getID() == MouseEvent.MOUSE_ENTERED)
+ requestFocus();
+ }
+
+ /**
+ * Display mouse moved and dragged mouse event. Note that MouseEvent is
+ * the only event type that has two methods, two EventListener interfaces
+ * and two adapter classes to handle two distinct categories of events.
+ * Also, as seen in init(), mouse motion events must be requested
+ * separately from other mouse event types.
+ **/
+ public void processMouseMotionEvent(MouseEvent e) {
+ String type = null;
+ switch(e.getID()) {
+ case MouseEvent.MOUSE_MOVED: type = "MOUSE_MOVED"; break;
+ case MouseEvent.MOUSE_DRAGGED: type = "MOUSE_DRAGGED"; break;
+ }
+ showLine(mousemods(e) + type +
+ ": [" + e.getX() + "," + e.getY() + "] " +
+ "num clicks = " + e.getClickCount() +
+ (e.isPopupTrigger()?"; is popup trigger":""));
+ }
+
+ /**
+ * Return a string representation of the modifiers for a MouseEvent.
+ * Note that the methods called here are inherited from InputEvent.
+ **/
+ protected String mousemods(MouseEvent e) {
+ int mods = e.getModifiers();
+ String s = "";
+ if (e.isShiftDown()) s += "Shift ";
+ if (e.isControlDown()) s += "Ctrl ";
+ if ((mods & InputEvent.BUTTON1_MASK) != 0) s += "Button 1 ";
+ if ((mods & InputEvent.BUTTON2_MASK) != 0) s += "Button 2 ";
+ if ((mods & InputEvent.BUTTON3_MASK) != 0) s += "Button 3 ";
+ return s;
+ }
+
+ /**
+ * Display keyboard events.
+ *
+ * Note that there are three distinct types of key events, and that key
+ * events are reported by key code and/or Unicode character. KEY_PRESSED
+ * and KEY_RELEASED events are generated for all key strokes. KEY_TYPED
+ * events are only generated when a key stroke produces a Unicode
+ * character; these events do not report a key code. If isActionKey()
+ * returns true, then the key event reports only a key code, because the
+ * key that was pressed or released (such as a function key) has no
+ * corresponding Unicode character. Key codes can be interpreted by using
+ * the many VK_ constants defined by the KeyEvent class, or they can be
+ * converted to strings using the static getKeyText() method as we do
+ * here.
+ **/
+ public void processKeyEvent(KeyEvent e) {
+ String eventtype, modifiers, code, character;
+ switch(e.getID()) {
+ case KeyEvent.KEY_PRESSED: eventtype = "KEY_PRESSED"; break;
+ case KeyEvent.KEY_RELEASED: eventtype = "KEY_RELEASED"; break;
+ case KeyEvent.KEY_TYPED: eventtype = "KEY_TYPED"; break;
+ default: eventtype = "UNKNOWN";
+ }
+
+ // Convert the list of modifier keys to a string
+ modifiers = KeyEvent.getKeyModifiersText(e.getModifiers());
+
+ // Get string and numeric versions of the key code, if any.
+ if (e.getID() == KeyEvent.KEY_TYPED) code = "";
+ else code = "Code=" + KeyEvent.getKeyText(e.getKeyCode()) +
+ " (" + e.getKeyCode() + ")";
+
+ // Get string and numeric versions of the Unicode character, if any.
+ if (e.isActionKey()) character = "";
+ else character = "Character=" + e.getKeyChar() +
+ " (Unicode=" + ((int)e.getKeyChar()) + ")";
+
+ // Display it all.
+ showLine(eventtype + ": " + modifiers + " " + code + " " + character);
+ }
+
+ /**
+ * Display keyboard focus events. Focus can be permanently gained or
+ * lost, or temporarily transferred to or from a component.
+ **/
+ public void processFocusEvent(FocusEvent e) {
+ if (e.getID() == FocusEvent.FOCUS_GAINED)
+ showLine("FOCUS_GAINED" + (e.isTemporary()?" (temporary)":""));
+ else
+ showLine("FOCUS_LOST" + (e.isTemporary()?" (temporary)":""));
+ }
+
+ /** Display Component events. */
+ public void processComponentEvent(ComponentEvent e) {
+ switch(e.getID()) {
+ case ComponentEvent.COMPONENT_MOVED:
+ showLine("COMPONENT_MOVED"); break;
+ case ComponentEvent.COMPONENT_RESIZED:
+ showLine("COMPONENT_RESIZED");break;
+ case ComponentEvent.COMPONENT_HIDDEN:
+ showLine("COMPONENT_HIDDEN"); break;
+ case ComponentEvent.COMPONENT_SHOWN:
+ showLine("COMPONENT_SHOWN"); break;
+ }
+ }
+
+ /** Display Window events. Note the special handling of WINDOW_CLOSING */
+ public void processWindowEvent(WindowEvent e) {
+ switch(e.getID()) {
+ case WindowEvent.WINDOW_OPENED: showLine("WINDOW_OPENED"); break;
+ case WindowEvent.WINDOW_CLOSED: showLine("WINDOW_CLOSED"); break;
+ case WindowEvent.WINDOW_CLOSING: showLine("WINDOW_CLOSING"); break;
+ case WindowEvent.WINDOW_ICONIFIED: showLine("WINDOW_ICONIFIED"); break;
+ case WindowEvent.WINDOW_DEICONIFIED:
+ showLine("WINDOW_DEICONIFIED"); break;
+ case WindowEvent.WINDOW_ACTIVATED:
+ showLine("WINDOW_ACTIVATED"); break;
+ case WindowEvent.WINDOW_DEACTIVATED:
+ showLine("WINDOW_DEACTIVATED"); break;
+ }
+
+ // If the user requested a window close, quit the program.
+ // But first display a message, force it to be visible, and make
+ // sure the user has time to read it.
+ if (e.getID() == WindowEvent.WINDOW_CLOSING) {
+ showLine("WINDOW_CLOSING event received.");
+ showLine("Application will exit in 5 seconds");
+ // Force the updates to appear now.
+ update(this.getGraphics());
+ // Wait five seconds
+ try {Thread.sleep(5000);} catch (InterruptedException ie) { ; }
+ // Exit now
+ System.exit(0);
+ }
+ }
+
+ /** The list of lines to display in the window */
+ protected Vector lines = new Vector();
+
+ /** Add a new line to the list of lines, and request redisplay */
+ protected void showLine(String s) {
+ if (lines.size() == 20) lines.removeElementAt(0);
+ lines.addElement(s);
+ repaint();
+ }
+
+ /** This method repaints the text in the window */
+ public void paintComponent(Graphics g) {
+ for(int i = 0; i < lines.size(); i++)
+ g.drawString((String)lines.elementAt(i), 20, i*16 + 50);
+ }
+
+ public boolean isOpaque() { return false; }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java
new file mode 100644
index 0000000..dd56ac8
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class FlowLayoutPane extends JPanel {
+ public FlowLayoutPane() {
+ // Use a FlowLayout layout manager. Left justify rows.
+ // Leave 10 pixels of horizontal and vertical space between components.
+ this.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 10));
+
+ // Add some buttons to demonstrate the layout.
+ String spaces = ""; // Used to make the buttons different
+ for(int i = 1; i <= 9; i++) {
+ this.add(new JButton("Button #" + i + spaces));
+ spaces += " ";
+ }
+
+ // Give ourselves a default size
+ this.setPreferredSize(new Dimension(500, 200));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/FontChooser.java b/src/main/java/com/davidflanagan/examples/gui/FontChooser.java
new file mode 100644
index 0000000..97f7360
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/FontChooser.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import com.davidflanagan.examples.gui.ItemChooser;
+
+/**
+ * This is a JDialog subclass that allows the user to select a font, in any
+ * style and size, from the list of available fonts on the system. The
+ * dialog is modal. Display it with show(); this method does not return
+ * until the user dismisses the dialog. When show() returns, call
+ * getSelectedFont() to obtain the user's selection. If the user clicked the
+ * dialog's "Cancel" button, getSelectedFont() will return null.
+ **/
+public class FontChooser extends JDialog {
+ // These fields define the component properties
+ String family; // The name of the font family
+ int style; // The font style
+ int size; // The font size
+ Font selectedFont; // The Font they correspond to
+
+ // This is the list of all font families on the system
+ String[] fontFamilies;
+
+ // The various Swing components used in the dialog
+ ItemChooser families, styles, sizes;
+ JTextArea preview;
+ JButton okay, cancel;
+
+ // The names to appear in the "Style" menu
+ static final String[] styleNames = new String[] {
+ "Plain", "Italic", "Bold", "BoldItalic"
+ };
+ // The style values that correspond to those names
+ static final Integer[] styleValues = new Integer[] {
+ new Integer(Font.PLAIN), new Integer(Font.ITALIC),
+ new Integer(Font.BOLD), new Integer(Font.BOLD+Font.ITALIC)
+ };
+ // The size "names" to appear in the size menu
+ static final String[] sizeNames = new String[] {
+ "8", "10", "12", "14", "18", "20", "24", "28", "32",
+ "40", "48", "56", "64", "72"
+ };
+
+ // This is the default preview string displayed in the dialog box
+ static final String defaultPreviewString =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" +
+ "abcdefghijklmnopqrstuvwxyz\n" +
+ "1234567890!@#$%^&*()_-=+[]{}<,.>\n" +
+ "The quick brown fox jumps over the lazy dog";
+
+ /** Create a font chooser dialog for the specified frame. */
+ public FontChooser(Frame owner) {
+ super(owner, "Choose a Font"); // Set dialog frame and title
+
+ // This dialog must be used as a modal dialog. In order to be used
+ // as a modeless dialog, it would have to fire a PropertyChangeEvent
+ // whenever the selected font changed, so that applications could be
+ // notified of the user's selections.
+ setModal(true);
+
+ // Figure out what fonts are available on the system
+ GraphicsEnvironment env =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ fontFamilies = env.getAvailableFontFamilyNames();
+
+ // Set initial values for the properties
+ family = fontFamilies[0];
+ style = Font.PLAIN;
+ size = 18;
+ selectedFont = new Font(family, style, size);
+
+ // Create ItemChooser objects that allow the user to select font
+ // family, style, and size.
+ families = new ItemChooser("Family", fontFamilies, null, 0,
+ ItemChooser.COMBOBOX);
+ styles = new ItemChooser("Style", styleNames, styleValues, 0,
+ ItemChooser.COMBOBOX);
+ sizes = new ItemChooser("Size", sizeNames,null,4,ItemChooser.COMBOBOX);
+
+ // Now register event listeners to handle selections
+ families.addItemChooserListener(new ItemChooser.Listener() {
+ public void itemChosen(ItemChooser.Event e) {
+ setFontFamily((String)e.getSelectedValue());
+ }
+ });
+ styles.addItemChooserListener(new ItemChooser.Listener() {
+ public void itemChosen(ItemChooser.Event e) {
+ setFontStyle(((Integer)e.getSelectedValue()).intValue());
+ }
+ });
+ sizes.addItemChooserListener(new ItemChooser.Listener() {
+ public void itemChosen(ItemChooser.Event e) {
+ setFontSize(Integer.parseInt((String)e.getSelectedValue()));
+ }
+ });
+
+ // Create a component to preview the font.
+ preview = new JTextArea(defaultPreviewString, 5, 40);
+ preview.setFont(selectedFont);
+
+ // Create buttons to dismiss the dialog, and set handlers on them
+ okay = new JButton("Okay");
+ cancel = new JButton("Cancel");
+ okay.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { hide(); }
+ });
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ selectedFont = null;
+ hide();
+ }
+ });
+
+ // Put the ItemChoosers in a Box
+ Box choosersBox = Box.createHorizontalBox();
+ choosersBox.add(Box.createHorizontalStrut(15));
+ choosersBox.add(families);
+ choosersBox.add(Box.createHorizontalStrut(15));
+ choosersBox.add(styles);
+ choosersBox.add(Box.createHorizontalStrut(15));
+ choosersBox.add(sizes);
+ choosersBox.add(Box.createHorizontalStrut(15));
+ choosersBox.add(Box.createGlue());
+
+ // Put the dismiss buttons in another box
+ Box buttonBox = Box.createHorizontalBox();
+ buttonBox.add(Box.createGlue());
+ buttonBox.add(okay);
+ buttonBox.add(Box.createGlue());
+ buttonBox.add(cancel);
+ buttonBox.add(Box.createGlue());
+
+ // Put the choosers at the top, the buttons at the bottom, and
+ // the preview in the middle.
+ Container contentPane = getContentPane();
+ contentPane.add(new JScrollPane(preview), BorderLayout.CENTER);
+ contentPane.add(choosersBox, BorderLayout.NORTH);
+ contentPane.add(buttonBox, BorderLayout.SOUTH);
+
+ // Set the dialog size based on the component size.
+ pack();
+ }
+
+ /**
+ * Call this method after show() to obtain the user's selection. If the
+ * user used the "Cancel" button, this will return null
+ **/
+ public Font getSelectedFont() { return selectedFont; }
+
+
+ // These are other property getter methods
+ public String getFontFamily() { return family; }
+ public int getFontStyle() { return style; }
+ public int getFontSize() { return size; }
+
+ // The property setter methods are a little more complicated.
+ // Note that none of these setter methods update the corresponding
+ // ItemChooser components as they ought to.
+ public void setFontFamily(String name) {
+ family = name;
+ changeFont();
+ }
+ public void setFontStyle(int style) {
+ this.style = style;
+ changeFont();
+ }
+ public void setFontSize(int size) {
+ this.size = size;
+ changeFont();
+ }
+ public void setSelectedFont(Font font) {
+ selectedFont = font;
+ family = font.getFamily();
+ style = font.getStyle();
+ size = font.getSize();
+ preview.setFont(font);
+ }
+
+ // This method is called when the family, style, or size changes
+ protected void changeFont() {
+ selectedFont = new Font(family, style, size);
+ preview.setFont(selectedFont);
+ }
+
+ // Override this inherited method to prevent anyone from making us modeless
+ public boolean isModal() { return true; }
+
+ /** This inner class demonstrates the use of FontChooser */
+ public static class Demo {
+ public static void main(String[] args) {
+ // Create some components and a FontChooser dialog
+ final JFrame frame = new JFrame("demo");
+ final JButton button = new JButton("Push Me!");
+ final FontChooser chooser = new FontChooser(frame);
+
+ // Handle button clicks
+ button.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // Pop up the dialog
+ chooser.show();
+ // Get the user's selection
+ Font font = chooser.getSelectedFont();
+ // If not cancelled, set the button font
+ if (font != null) button.setFont(font);
+ }
+ });
+
+ // Display the demo
+ frame.getContentPane().add(button);
+ frame.setSize(200, 100);
+ frame.show();
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java b/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java
new file mode 100644
index 0000000..f6f33a9
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.io.*;
+import java.util.*;
+import java.awt.*;
+
+/**
+ * This class extends ResourceBundle and adds methods to retrieve types of
+ * resources commonly used in GUIs. Additionally, it adds extensibility
+ * by allowing ResourceParser objects to be registered to parse other
+ * resource types.
+ **/
+public class GUIResourceBundle extends ResourceBundle {
+ // The root object. Required to parse certain resource types like Commands
+ Object root;
+
+ // The resource bundle that actually contains the textual resources
+ // This class is a wrapper around this bundle
+ ResourceBundle bundle;
+
+ /** Create a GUIResourceBundle wrapper around a specified bundle */
+ public GUIResourceBundle(Object root, ResourceBundle bundle) {
+ this.root = root;
+ this.bundle = bundle;
+ }
+
+ /**
+ * Load a named bundle and create a GUIResourceBundle around it. This
+ * constructor takes advantage of the internationalization features of
+ * the ResourceBundle.getBundle() method.
+ **/
+ public GUIResourceBundle(Object root, String bundleName)
+ throws MissingResourceException
+ {
+ this.root = root;
+ this.bundle = ResourceBundle.getBundle(bundleName);
+ }
+
+ /**
+ * Create a PropertyResourceBundle from the specified stream and then
+ * create a GUIResourceBundle wrapper for it
+ **/
+ public GUIResourceBundle(Object root, InputStream propertiesStream)
+ throws IOException
+ {
+ this.root = root;
+ this.bundle = new PropertyResourceBundle(propertiesStream);
+ }
+
+ /**
+ * Create a PropertyResourceBundle from the specified properties file and
+ * then create a GUIResourceBundle wrapper for it.
+ **/
+ public GUIResourceBundle(Object root, File propertiesFile)
+ throws IOException
+ {
+ this(root, new FileInputStream(propertiesFile));
+ }
+
+ /** This is one of the abstract methods of ResourceBundle */
+ public Enumeration getKeys() { return bundle.getKeys(); }
+
+ /** This is the other abstract method of ResourceBundle */
+ protected Object handleGetObject(String key)
+ throws MissingResourceException
+ {
+ return bundle.getObject(key); // simply defer to the wrapped bundle
+ }
+
+ /** This is a property accessor method for our root object */
+ public Object getRoot() { return root; }
+
+ /**
+ * This method is like the inherited getString() method, except that
+ * when the named resource is not found, it returns the specified default
+ * instead of throwing an exception
+ **/
+ public String getString(String key, String defaultValue) {
+ try { return bundle.getString(key); }
+ catch(MissingResourceException e) { return defaultValue; }
+ }
+
+ /**
+ * Look up the named resource and parse it as a list of strings separated
+ * by spaces, tabs, or commas.
+ **/
+ public java.util.List getStringList(String key)
+ throws MissingResourceException
+ {
+ String s = getString(key);
+ StringTokenizer t = new StringTokenizer(s, ", \t", false);
+ ArrayList list = new ArrayList();
+ while(t.hasMoreTokens()) list.add(t.nextToken());
+ return list;
+ }
+
+ /** Like above, but return a default instead of throwing an exception */
+ public java.util.List getStringList(String key,
+ java.util.List defaultValue) {
+ try { return getStringList(key); }
+ catch(MissingResourceException e) { return defaultValue; }
+ }
+
+ /** Look up the named resource and try to interpret it as a boolean. */
+ public boolean getBoolean(String key) throws MissingResourceException {
+ String s = bundle.getString(key);
+ s = s.toLowerCase();
+ if (s.equals("true")) return true;
+ else if (s.equals("false")) return false;
+ else if (s.equals("yes")) return true;
+ else if (s.equals("no")) return false;
+ else if (s.equals("on")) return true;
+ else if (s.equals("off")) return false;
+ else {
+ throw new MalformedResourceException("boolean", key);
+ }
+ }
+
+ /** As above, but return the default instead of throwing an exception */
+ public boolean getBoolean(String key, boolean defaultValue) {
+ try { return getBoolean(key); }
+ catch(MissingResourceException e) {
+ if (e instanceof MalformedResourceException)
+ System.err.println("WARNING: " + e.getMessage());
+ return defaultValue;
+ }
+ }
+
+ /** Like getBoolean(), but for integers */
+ public int getInt(String key) throws MissingResourceException {
+ String s = bundle.getString(key);
+
+ try {
+ // Use decode() instead of parseInt() so we support octal
+ // and hexadecimal numbers
+ return Integer.decode(s).intValue();
+ } catch (NumberFormatException e) {
+ throw new MalformedResourceException("int", key);
+ }
+ }
+
+ /** As above, but with a default value */
+ public int getInt(String key, int defaultValue) {
+ try { return getInt(key); }
+ catch(MissingResourceException e) {
+ if (e instanceof MalformedResourceException)
+ System.err.println("WARNING: " + e.getMessage());
+ return defaultValue;
+ }
+ }
+
+ /** Return a resource of type double */
+ public double getDouble(String key) throws MissingResourceException {
+ String s = bundle.getString(key);
+
+ try {
+ return Double.parseDouble(s);
+ } catch (NumberFormatException e) {
+ throw new MalformedResourceException("double", key);
+ }
+ }
+
+ /** As above, but with a default value */
+ public double getDouble(String key, double defaultValue) {
+ try { return getDouble(key); }
+ catch(MissingResourceException e) {
+ if (e instanceof MalformedResourceException)
+ System.err.println("WARNING: " + e.getMessage());
+ return defaultValue;
+ }
+ }
+
+ /** Look up the named resource and convert to a Font */
+ public Font getFont(String key) throws MissingResourceException {
+ // Font.decode() always returns a Font object, so we can't check
+ // whether the resource value was well-formed or not.
+ return Font.decode(bundle.getString(key));
+ }
+
+ /** As above, but with a default value */
+ public Font getFont(String key, Font defaultValue) {
+ try { return getFont(key); }
+ catch (MissingResourceException e) { return defaultValue; }
+ }
+
+ /** Look up the named resource, and convert to a Color */
+ public Color getColor(String key) throws MissingResourceException {
+ try {
+ return Color.decode(bundle.getString(key));
+ }
+ catch (NumberFormatException e) {
+ // It would be useful to try to parse color names here as well
+ // as numeric color specifications
+ throw new MalformedResourceException("Color", key);
+ }
+ }
+
+ /** As above, but with a default value */
+ public Color getColor(String key, Color defaultValue) {
+ try { return getColor(key); }
+ catch(MissingResourceException e) {
+ if (e instanceof MalformedResourceException)
+ System.err.println("WARNING: " + e.getMessage());
+ return defaultValue;
+ }
+ }
+
+ /** A hashtable for mapping resource types to resource parsers */
+ static HashMap parsers = new HashMap();
+
+ /** An extension mechanism: register a parser for new resource types */
+ public static void registerResourceParser(ResourceParser parser) {
+ // Ask the ResourceParser what types it can parse
+ Class[] supportedTypes = parser.getResourceTypes();
+ // Register it in the hashtable for each of those types
+ for(int i = 0; i < supportedTypes.length; i++)
+ parsers.put(supportedTypes[i], parser);
+ }
+
+ /** Look up a ResourceParser for the specified resource type */
+ public static ResourceParser getResourceParser(Class type) {
+ return (ResourceParser) parsers.get(type);
+ }
+
+ /**
+ * Look for a ResourceParser for the named type, and if one is found,
+ * ask it to parse and return the named resource
+ **/
+ public Object getResource(String key, Class type)
+ throws MissingResourceException
+ {
+ // Get a parser for the specified type
+ ResourceParser parser = (ResourceParser)parsers.get(type);
+ if (parser == null)
+ throw new MissingResourceException(
+ "No ResourceParser registered for " +
+ type.getName() + " resources",
+ type.getName(), key);
+
+ try { // Ask the parser to parse the resource
+ return parser.parse(this, key, type);
+ }
+ catch(MissingResourceException e) {
+ throw e; // Rethrow MissingResourceException exceptions
+ }
+ catch(Exception e) {
+ // If any other type of exception occurs, convert it to
+ // a MalformedResourceException
+ String msg = "Malformed " + type.getName() + " resource: " +
+ key + ": " + e.getMessage();
+ throw new MalformedResourceException(msg, type.getName(), key);
+ }
+ }
+
+ /**
+ * Like the 2-argument version of getResource, but return a default value
+ * instead of throwing a MissingResourceException
+ **/
+ public Object getResource(String key, Class type, Object defaultValue) {
+ try { return getResource(key, type); }
+ catch (MissingResourceException e) {
+ if (e instanceof MalformedResourceException)
+ System.err.println("WARNING: " + e.getMessage());
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java
new file mode 100644
index 0000000..217542c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class GridBagLayoutPane extends JPanel {
+ public GridBagLayoutPane() {
+ // Create and specify a layout manager
+ this.setLayout(new GridBagLayout());
+
+ // Create a constraints object, and specify some default values
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH; // components grow in both dimensions
+ c.insets = new Insets(5,5,5,5); // 5-pixel margins on all sides
+
+ // Create and add a bunch of buttons, specifying different grid
+ // position, and size for each.
+ // Give the first button a resize weight of 1.0 and all others
+ // a weight of 0.0. The first button will get all extra space.
+ c.gridx = 0; c.gridy = 0; c.gridwidth = 4; c.gridheight=4;
+ c.weightx = c.weighty = 1.0;
+ this.add(new JButton("Button #1"), c);
+
+ c.gridx = 4; c.gridy = 0; c.gridwidth = 1; c.gridheight=1;
+ c.weightx = c.weighty = 0.0;
+ this.add(new JButton("Button #2"), c);
+
+ c.gridx = 4; c.gridy = 1; c.gridwidth = 1; c.gridheight=1;
+ this.add(new JButton("Button #3"), c);
+
+ c.gridx = 4; c.gridy = 2; c.gridwidth = 1; c.gridheight=2;
+ this.add(new JButton("Button #4"), c);
+
+ c.gridx = 0; c.gridy = 4; c.gridwidth = 1; c.gridheight=1;
+ this.add(new JButton("Button #5"), c);
+
+ c.gridx = 2; c.gridy = 4; c.gridwidth = 1; c.gridheight=1;
+ this.add(new JButton("Button #6"), c);
+
+ c.gridx = 3; c.gridy = 4; c.gridwidth = 2; c.gridheight=1;
+ this.add(new JButton("Button #7"), c);
+
+ c.gridx = 1; c.gridy = 5; c.gridwidth = 1; c.gridheight=1;
+ this.add(new JButton("Button #8"), c);
+
+ c.gridx = 3; c.gridy = 5; c.gridwidth = 1; c.gridheight=1;
+ this.add(new JButton("Button #9"), c);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java
new file mode 100644
index 0000000..e644d2d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class GridLayoutPane extends JPanel {
+ public GridLayoutPane() {
+ // Layout components into a grid three columns wide, with the number
+ // of rows depending on the number of components. Leave 10 pixels
+ // of horizontal and vertical space between components
+ this.setLayout(new GridLayout(0, 3, 10, 10));
+ // Add some components
+ for(int i = 1; i <= 12; i++) this.add(new JButton("Button #" + i));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java b/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java
new file mode 100644
index 0000000..ac0dd70
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import java.util.*;
+
+/**
+ * This class is a Swing component that presents a choice to the user. It
+ * allows the choice to be presented in a JList, in a JComboBox, or with a
+ * bordered group of JRadioButton components. Additionally, it displays the
+ * name of the choice with a JLabel. It allows an arbitrary value to be
+ * associated with each possible choice. Note that this component only allows
+ * one item to be selected at a time. Multiple selections are not supported.
+ **/
+public class ItemChooser extends JPanel {
+ // These fields hold property values for this component
+ String name; // The overall name of the choice
+ String[] labels; // The text for each choice option
+ Object[] values; // Arbitrary values associated with each option
+ int selection; // The selected choice
+ int presentation; // How the choice is presented
+
+ // These are the legal values for the presentation field
+ public static final int LIST = 1;
+ public static final int COMBOBOX = 2;
+ public static final int RADIOBUTTONS = 3;
+
+ // These components are used for each of the 3 possible presentations
+ JList list; // One type of presentation
+ JComboBox combobox; // Another type of presentation
+ JRadioButton[] radiobuttons; // Yet another type
+
+ // The list of objects that are interested in our state
+ ArrayList listeners = new ArrayList();
+
+ // The constructor method sets everything up
+ public ItemChooser(String name, String[] labels, Object[] values,
+ int defaultSelection, int presentation)
+ {
+ // Copy the constructor arguments to instance fields
+ this.name = name;
+ this.labels = labels;
+ this.values = values;
+ this.selection = defaultSelection;
+ this.presentation = presentation;
+
+ // If no values were supplied, use the labels
+ if (values == null) this.values = labels;
+
+ // Now create content and event handlers based on presentation type
+ switch(presentation) {
+ case LIST: initList(); break;
+ case COMBOBOX: initComboBox(); break;
+ case RADIOBUTTONS: initRadioButtons(); break;
+ }
+ }
+
+ // Initialization for JList presentation
+ void initList() {
+ list = new JList(labels); // Create the list
+ list.setSelectedIndex(selection); // Set initial state
+
+ // Handle state changes
+ list.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ ItemChooser.this.select(list.getSelectedIndex());
+ }
+ });
+
+ // Lay out list and name label vertically
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // vertical
+ this.add(new JLabel(name)); // Display choice name
+ this.add(new JScrollPane(list)); // Add the JList
+ }
+
+ // Initialization for JComboBox presentation
+ void initComboBox() {
+ combobox = new JComboBox(labels); // Create the combo box
+ combobox.setSelectedIndex(selection); // Set initial state
+
+ // Handle changes to the state
+ combobox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ ItemChooser.this.select(combobox.getSelectedIndex());
+ }
+ });
+
+ // Lay out combo box and name label horizontally
+ this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ this.add(new JLabel(name));
+ this.add(combobox);
+ }
+
+ // Initialization for JRadioButton presentation
+ void initRadioButtons() {
+ // Create an array of mutually exclusive radio buttons
+ radiobuttons = new JRadioButton[labels.length]; // the array
+ ButtonGroup radioButtonGroup = new ButtonGroup(); // used for exclusion
+ ChangeListener listener = new ChangeListener() { // A shared listener
+ public void stateChanged(ChangeEvent e) {
+ JRadioButton b = (JRadioButton)e.getSource();
+ if (b.isSelected()) {
+ // If we received this event because a button was
+ // selected, then loop through the list of buttons to
+ // figure out the index of the selected one.
+ for(int i = 0; i < radiobuttons.length; i++) {
+ if (radiobuttons[i] == b) {
+ ItemChooser.this.select(i);
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ // Display the choice name in a border around the buttons
+ this.setBorder(new TitledBorder(new EtchedBorder(), name));
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+ // Create the buttons, add them to the button group, and specify
+ // the event listener for each one.
+ for(int i = 0; i < labels.length; i++) {
+ radiobuttons[i] = new JRadioButton(labels[i]);
+ if (i == selection) radiobuttons[i].setSelected(true);
+ radiobuttons[i].addChangeListener(listener);
+ radioButtonGroup.add(radiobuttons[i]);
+ this.add(radiobuttons[i]);
+ }
+ }
+
+ // These simple property accessor methods just return field values
+ // These are read-only properties. The values are set by the constructor
+ // and may not be changed.
+ public String getName() { return name; }
+ public int getPresentation() { return presentation; }
+ public String[] getLabels() { return labels; }
+ public Object[] getValues() { return values; }
+
+ /** Return the index of the selected item */
+ public int getSelectedIndex() { return selection; }
+
+ /** Return the object associated with the selected item */
+ public Object getSelectedValue() { return values[selection]; }
+
+ /**
+ * Set the selected item by specifying its index. Calling this
+ * method changes the on-screen display but does not generate events.
+ **/
+ public void setSelectedIndex(int selection) {
+ switch(presentation) {
+ case LIST: list.setSelectedIndex(selection); break;
+ case COMBOBOX: combobox.setSelectedIndex(selection); break;
+ case RADIOBUTTONS: radiobuttons[selection].setSelected(true); break;
+ }
+ this.selection = selection;
+ }
+
+ /**
+ * This internal method is called when the selection changes. It stores
+ * the new selected index, and fires events to any registered listeners.
+ * The event listeners registered on the JList, JComboBox, or JRadioButtons
+ * all call this method.
+ **/
+ protected void select(int selection) {
+ this.selection = selection; // Store the new selected index
+ if (!listeners.isEmpty()) { // If there are any listeners registered
+ // Create an event object to describe the selection
+ Event e =
+ new Event(this, selection, values[selection]);
+ // Loop through the listeners using an Iterator
+ for(Iterator i = listeners.iterator(); i.hasNext();) {
+ Listener l = (Listener)i.next();
+ l.itemChosen(e); // Notify each listener of the selection
+ }
+ }
+ }
+
+ // These methods are for event listener registration and deregistration
+ public void addItemChooserListener(Listener l) {
+ listeners.add(l);
+ }
+ public void removeItemChooserListener(Listener l) {
+ listeners.remove(l);
+ }
+
+ /**
+ * This inner class defines the event type generated by ItemChooser objects
+ * The inner class name is Event, so the full name is ItemChooser.Event
+ **/
+ public static class Event extends EventObject {
+ int selectedIndex; // index of the selected item
+ Object selectedValue; // the value associated with it
+ public Event(ItemChooser source,
+ int selectedIndex, Object selectedValue) {
+ super(source);
+ this.selectedIndex = selectedIndex;
+ this.selectedValue = selectedValue;
+ }
+
+ public ItemChooser getItemChooser() { return (ItemChooser)getSource();}
+ public int getSelectedIndex() { return selectedIndex; }
+ public Object getSelectedValue() { return selectedValue; }
+ }
+
+ /**
+ * This inner interface must be implemented by any object that wants to be
+ * notified when the current selection in a ItemChooser component changes.
+ **/
+ public interface Listener extends EventListener {
+ public void itemChosen(Event e);
+ }
+
+ /**
+ * This inner class is a simple demonstration of the ItemChooser component
+ * It uses command-line arguments as ItemChooser labels and values.
+ **/
+ public static class Demo {
+ public static void main(String[] args) {
+ // Create a window, arrange to handle close requests
+ final JFrame frame = new JFrame("ItemChooser Demo");
+ frame.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {System.exit(0);}
+ });
+
+ // A "message line" to display results in
+ final JLabel msgline = new JLabel(" ");
+
+ // Create a panel holding three ItemChooser components
+ JPanel chooserPanel = new JPanel();
+ final ItemChooser c1 = new ItemChooser("Choice #1", args, null, 0,
+ ItemChooser.LIST);
+ final ItemChooser c2 = new ItemChooser("Choice #2", args, null, 0,
+ ItemChooser.COMBOBOX);
+ final ItemChooser c3 = new ItemChooser("Choice #3", args, null, 0,
+ ItemChooser.RADIOBUTTONS);
+
+ // An event listener that displays changes on the message line
+ Listener l = new Listener() {
+ public void itemChosen(Event e) {
+ msgline.setText(e.getItemChooser().getName() + ": " +
+ e.getSelectedIndex() + ": " +
+ e.getSelectedValue());
+ }
+ };
+ c1.addItemChooserListener(l);
+ c2.addItemChooserListener(l);
+ c3.addItemChooserListener(l);
+
+ // Instead of tracking every change with a ItemChooser.Listener,
+ // applications can also just query the current state when
+ // they need it. Here's a button that does that.
+ JButton report = new JButton("Report");
+ report.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // Note the use of multi-line italic HTML text
+ // with the JOptionPane message dialog box.
+ String msg = "" +
+ c1.getName() + ": " + c1.getSelectedValue() + " "+
+ c2.getName() + ": " + c2.getSelectedValue() + " "+
+ c3.getName() + ": " + c3.getSelectedValue() + "";
+ JOptionPane.showMessageDialog(frame, msg);
+ }
+ });
+
+ // Add the 3 ItemChooser objects, and the Button to the panel
+ chooserPanel.add(c1);
+ chooserPanel.add(c2);
+ chooserPanel.add(c3);
+ chooserPanel.add(report);
+
+ // Add the panel and the message line to the window
+ Container contentPane = frame.getContentPane();
+ contentPane.add(chooserPanel, BorderLayout.CENTER);
+ contentPane.add(msgline, BorderLayout.SOUTH);
+
+ // Set the window size and pop it up.
+ frame.pack();
+ frame.show();
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java b/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java
new file mode 100644
index 0000000..5476460
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.util.MissingResourceException;
+
+/**
+ * This subclass of MissingResourceException signals that a resource value
+ * was present, but could not be properly parsed or otherwise converted to
+ * the desired type.
+ **/
+public class MalformedResourceException extends MissingResourceException {
+ public MalformedResourceException(String msg, String type, String key){
+ super(msg, type, key);
+ }
+ // Convenience constructors: automatically generate exception message
+ public MalformedResourceException(String type, String key){
+ super("Malformed " + type + " resource: " + key, type, key);
+ }
+ public MalformedResourceException(Class type, String key){
+ this(type.getName(), key);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java b/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java
new file mode 100644
index 0000000..4a609c5
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * Parse a JMenuBar from a ResourceBundle. A menubar is represented
+ * simply as a list of menu property names. E.g.:
+ * menubar: menu.file menu.edit menu.view menu.help
+ **/
+public class MenuBarParser implements ResourceParser {
+ static final Class[] supportedTypes = new Class[] { JMenuBar.class };
+ public Class[] getResourceTypes() { return supportedTypes; }
+
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws MissingResourceException
+ {
+ // Get the value of the key as a list of strings
+ List menuList = bundle.getStringList(key);
+
+ // Create a MenuBar
+ JMenuBar menubar = new JMenuBar();
+
+ // Create a JMenu for each of the menu property names,
+ // and add it to the bar
+ int nummenus = menuList.size();
+ for(int i = 0; i < nummenus; i++) {
+ menubar.add((JMenu) bundle.getResource((String)menuList.get(i),
+ JMenu.class));
+ }
+
+ return menubar;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/MenuParser.java b/src/main/java/com/davidflanagan/examples/gui/MenuParser.java
new file mode 100644
index 0000000..a40681f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/MenuParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import com.davidflanagan.examples.reflect.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.StringTokenizer;
+
+/**
+ * This class parses a JMenu or JPopupMenu from textual descriptions found in
+ * a GUIResourceBundle. The grammar is straightforward: the menu label
+ * followed by a colon and a list of menu items. Menu items that begin with
+ * a '>' character are submenus. Menu items that begin with a '-' character
+ * are separators. All other items are action names.
+ **/
+public class MenuParser implements ResourceParser {
+ static final Class[] supportedTypes = new Class[] {
+ JMenu.class, JPopupMenu.class // This class handles two resource types
+ };
+
+ public Class[] getResourceTypes() { return supportedTypes; }
+
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws java.util.MissingResourceException
+ {
+ // Get the string value of the key
+ String menudef = bundle.getString(key);
+
+ // Break it up into words, ignoring whitespace, colons and commas
+ StringTokenizer st = new StringTokenizer(menudef, " \t:,");
+
+ // The first word is the label of the menu
+ String menuLabel = st.nextToken();
+
+ // Create either a JMenu or JPopupMenu
+ JMenu menu = null;
+ JPopupMenu popup = null;
+ if (type == JMenu.class) menu = new JMenu(menuLabel);
+ else popup = new JPopupMenu(menuLabel);
+
+ // Then loop through the rest of the words, creating a JMenuItem
+ // for each one. Accumulate these items in a list
+ while(st.hasMoreTokens()) {
+ String item = st.nextToken(); // the next word
+ char firstchar = item.charAt(0); // determines type of menu item
+ switch(firstchar) {
+ case '-': // words beginning with - add a separator to the menu
+ if (menu != null) menu.addSeparator();
+ else popup.addSeparator();
+ break;
+ case '>': // words beginning with > are submenu names
+ // strip off the > character, and recurse to parse the submenu
+ item = item.substring(1);
+ // Parse a submenu and add it to the list of items
+ JMenu submenu = (JMenu)parse(bundle, item, JMenu.class);
+ if (menu != null) menu.add(submenu);
+ else popup.add(submenu);
+ break;
+ case '!': // words beginning with ! are action names
+ item = item.substring(1); // strip off the ! character
+ /* falls through */ // fall through to the next case
+ default: // By default all other words are taken as action names
+ // Look up the named action and add it to the menu
+ Action action = (Action)bundle.getResource(item, Action.class);
+ if (menu != null) menu.add(action);
+ else popup.add(action);
+ break;
+ }
+ }
+
+ // Finally, return the menu or the popup menu
+ if (menu != null) return menu;
+ else return popup;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java
new file mode 100644
index 0000000..cf9deb2
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+
+public class NullLayoutPane extends JPanel {
+ public NullLayoutPane() {
+ // Get rid of the default layout manager.
+ // We'll arrange the components ourselves.
+ this.setLayout(null);
+
+ // Create some buttons and set their sizes and positions explicitly
+ for(int i = 1; i <= 9; i++) {
+ JButton b = new JButton("Button #" + i);
+ b.setBounds(i*30, i*20, 125, 30); // use reshape() in Java 1.0
+ this.add(b);
+ }
+ }
+
+ // Specify how big the panel should be.
+ public Dimension getPreferredSize() { return new Dimension(425, 250); }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java b/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java
new file mode 100644
index 0000000..3666e8a
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.print.*;
+import java.awt.geom.*;
+import java.awt.font.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import java.util.*;
+
+/**
+ * This class implements the Pageable and Printable interfaces and allows
+ * the contents of any JTextComponent to be printed using the java.awt.print
+ * printing API.
+ **/
+public class PrintableDocument implements Pageable, Printable {
+ View root; // The root View to be printed
+ PageFormat format; // Paper plus page orientation
+ int numPages; // How many pages in the document
+ double printX, printY; // coordinates of upper-left of print area
+ double printWidth; // Width of the printable area
+ double printHeight; // Height of the printable area
+ Rectangle drawRect; // The rectangle in which the document is painted
+
+ // How lenient are we with the bottom margin in widow/orphan prevention?
+ static final double MARGIN_ADJUST = .97;
+
+ // The font we use for printing page numbers
+ static final Font headerFont = new Font("Serif", Font.PLAIN, 12);
+
+ /**
+ * This constructor allows printing the contents of any JTextComponent
+ * using a default PageFormat
+ */
+ public PrintableDocument(JTextComponent textComponent) {
+ this(textComponent, new PageFormat());
+ }
+
+ /**
+ * This constructor allows the contents of any JTextComponent to be
+ * printed, using any specified PageFormat object
+ **/
+ public PrintableDocument(JTextComponent textComponent, PageFormat format) {
+ // Remember the page format, and ask it for the printable area
+ this.format = format;
+ this.printX = format.getImageableX();
+ this.printY = format.getImageableY();
+ this.printWidth = format.getImageableWidth();
+ this.printHeight = format.getImageableHeight();
+ double paperWidth = format.getWidth();
+
+ // Get the document and its root Element from the text component
+ Document document = textComponent.getDocument();
+ Element rootElement = document.getDefaultRootElement();
+ // Get the EditorKit and its ViewFactory from the text component
+ EditorKit editorKit =textComponent.getUI().getEditorKit(textComponent);
+ ViewFactory viewFactory = editorKit.getViewFactory();
+
+ // Use the ViewFactory to create a root View object for the document
+ // This is the object we'll print.
+ root = viewFactory.create(rootElement);
+
+ // The Swing text architecture requires us to call setParent() on
+ // our root View before we use it for anything. In order to do this,
+ // we need a View object that can serve as the parent. We use a
+ // custom implementation defined below.
+ root.setParent(new ParentView(root, viewFactory, textComponent));
+
+ // Tell the view how wide the page is; it has to format itself
+ // to fit within this width. The height doesn't really matter here
+ root.setSize((float)printWidth, (float)printHeight);
+
+ // Now that the view has formatted itself for the specified width,
+ // Ask it how tall it is.
+ double documentHeight = root.getPreferredSpan(View.Y_AXIS);
+
+ // Set up the rectangle that tells the view where to draw itself
+ // We'll use it in other methods of this class.
+ drawRect = new Rectangle((int)printX, (int)printY,
+ (int)printWidth, (int)documentHeight);
+
+ // Now if the document is taller than one page, we have to
+ // figure out where the page breaks are.
+ if (documentHeight > printHeight) paginate(root, drawRect);
+
+ // Once we've broken it into pages, figure out how man pages.
+ numPages = pageLengths.size() + 1;
+ }
+
+ // This is the starting offset of the page we're currently working on
+ double pageStart = 0;
+
+ /**
+ * This method loops through the children of the specified view,
+ * recursing as necessary, and inserts pages breaks when needed.
+ * It makes a rudimentary attempt to avoid "widows" and "orphans".
+ **/
+ protected void paginate(View v, Rectangle2D allocation) {
+ // Figure out how tall this view is, and tell it to allocate
+ // that space among its children
+ double myheight = v.getPreferredSpan(View.Y_AXIS);
+ v.setSize((float)printWidth, (float)myheight);
+
+ // Now loop through each of the children
+ int numkids = v.getViewCount();
+ for(int i = 0; i < numkids; i++) {
+ View kid = v.getView(i); // this is the child we're working with
+ // Figure out its size and location
+ Shape kidshape = v.getChildAllocation(i, allocation);
+ if (kidshape == null) continue;
+ Rectangle2D kidbox = kidshape.getBounds2D();
+
+ // This is the Y coordinate of the bottom of the child
+ double kidpos = kidbox.getY() + kidbox.getHeight() - pageStart;
+
+ // If this is the first child of a group, then we want to ensure
+ // that it doesn't get left by itself at the bottom of a page.
+ // I.e. we want to prevent "widows"
+ if ((numkids > 1) && (i == 0)) {
+ // If it is not near the end of the page, then just move
+ // on to the next child
+ if (kidpos < printY + printHeight*MARGIN_ADJUST) continue;
+
+ // Otherwise, the child is near the bottom of the page, so
+ // break the page before this child and place this child on
+ // the new page.
+ breakPage(kidbox.getY());
+ continue;
+ }
+
+ // If this is the last child of a group, we don't want it to
+ // appear by itself at the top of a new page, so allow it to
+ // squeeze past the bottom margin if necessary. This helps to
+ // prevent "orphans"
+ if ((numkids > 1) && (i == numkids-1)) {
+ // If it fits normally, just move on to the next one
+ if (kidpos < printY + printHeight) continue;
+
+ // Otherwise, if it fits with extra space, then break the
+ // at the end of the group
+ if (kidpos < printY + printHeight/MARGIN_ADJUST) {
+ breakPage(allocation.getY() + allocation.getHeight());
+ continue;
+ }
+ }
+
+ // If the child is not the first or last of a group, then we use
+ // the bottom margin strictly. If the child fits on the page,
+ // then move on to the next child.
+ if (kidpos < printY+printHeight) continue;
+
+ // If we get here, the child doesn't fit on this page. If it has
+ // no children, then break the page before this child and continue.
+ if (kid.getViewCount() == 0) {
+ breakPage(kidbox.getY());
+ continue;
+ }
+
+ // If we get here, then the child did not fit on the page, but it
+ // has kids of its own, so recurse to see if any of those kids
+ // will fit on the page.
+ paginate(kid, kidbox);
+ }
+ }
+
+ // For a document of n pages, this list stores the lengths of pages
+ // 0 through n-2. The last page is assumed to have a full length
+ ArrayList pageLengths = new ArrayList();
+
+ // For a document of n pages, this list stores the starting offset of
+ // pages 1 through n-1. The offset of page 0 is always 0
+ ArrayList pageOffsets = new ArrayList();
+
+ /**
+ * Break a page at the specified Y coordinate. Store the necessary
+ * information into the pageLengths and pageOffsets lists
+ **/
+ void breakPage(double y) {
+ double pageLength = y-pageStart-printY;
+ pageStart = y-printY;
+ pageLengths.add(new Double(pageLength));
+ pageOffsets.add(new Double(pageStart));
+ }
+
+ /** Return the number of pages. This is a Pageable method. */
+ public int getNumberOfPages() { return numPages; }
+
+ /**
+ * Return the PageFormat object for the specified page. This
+ * implementation uses the computed length of the page in the returned
+ * PageFormat object. The PrinterJob will use this as a clipping region,
+ * which will prevent extraneous parts of the document from being drawn
+ * in the top and bottom margins.
+ **/
+ public PageFormat getPageFormat(int pagenum) {
+ // On the last page, just return the user-specified page format
+ if (pagenum == numPages-1) return format;
+
+ // Otherwise, look up the height of this page and return an
+ // appropriate PageFormat.
+ double pageLength = ((Double)pageLengths.get(pagenum)).doubleValue();
+ PageFormat f = (PageFormat) format.clone();
+ Paper p = f.getPaper();
+ if (f.getOrientation() == PageFormat.PORTRAIT)
+ p.setImageableArea(printX, printY, printWidth, pageLength);
+ else
+ p.setImageableArea(printY, printX, pageLength, printWidth);
+ f.setPaper(p);
+ return f;
+ }
+
+ /**
+ * This Printable method returns the Printable object for the specified
+ * page. Since this class implements both Pageable and Printable, it just
+ * returns this.
+ **/
+ public Printable getPrintable(int pagenum) { return this; }
+
+ /**
+ * This is the basic Printable method that prints a specified page
+ **/
+ public int print(Graphics g, PageFormat format, int pageIndex) {
+ // Return an error code on attempts to print past the end of the doc
+ if (pageIndex >= numPages) return NO_SUCH_PAGE;
+
+ // Cast the Graphics object so we can use Java2D operations
+ Graphics2D g2 = (Graphics2D)g;
+
+ // Display a page number centered in the area of the top margin.
+ // Set a new clipping region so we can draw into the top margin
+ // But remember the original clipping region so we can restore it
+ Shape originalClip = g.getClip();
+ g.setClip(new Rectangle(0, 0, (int)printWidth, (int)printY));
+ // Compute the header to display, measure it, then display it
+ String numString = "- " + (pageIndex+1) + " -";
+ Rectangle2D numBounds = // Get the width and height of the string
+ headerFont.getStringBounds(numString, g2.getFontRenderContext());
+ LineMetrics metrics = // Get the ascent and descent of the font
+ headerFont.getLineMetrics(numString, g2.getFontRenderContext());
+ g.setFont(headerFont); // Set the font
+ g.setColor(Color.black); // Print with black ink
+ g.drawString(numString, // Display the string
+ (int)(printX + (printWidth-numBounds.getWidth())/2),
+ (int)((printY-numBounds.getHeight())/2 + metrics.getAscent()));
+ g.setClip(originalClip); // Restore the clipping region
+
+ // Figure out the staring position of the page within the document
+ double pageStart = 0.0;
+ if (pageIndex > 0)
+ pageStart = ((Double)pageOffsets.get(pageIndex-1)).doubleValue();
+
+ // Scroll so that the appropriate part of the document is lined up
+ // with the upper-left corner of the page
+ g2.translate(0.0, -pageStart);
+
+ // Now paint the entire document. The PrinterJob will have
+ // established a clipping region, so that only the desired portion
+ // of the document will actually be drawn on this sheet of paper.
+ root.paint(g, drawRect);
+
+ // Finally return a success code
+ return PAGE_EXISTS;
+ }
+
+ /**
+ * This inner class is a concrete implementation of View, with a
+ * couple of key method implementations. An instance of this class
+ * is used as the parent of the root View object we want to print
+ **/
+ static class ParentView extends View {
+ ViewFactory viewFactory; // The ViewFactory for the hierarchy of views
+ Container container; // The Container for the hierarchy of views
+
+ public ParentView(View v, ViewFactory viewFactory, Container container)
+ {
+ super(v.getElement());
+ this.viewFactory = viewFactory;
+ this.container = container;
+ }
+
+ // These methods return key pieces of information required by
+ // the View hierarchy.
+ public ViewFactory getViewFactory() { return viewFactory; }
+ public Container getContainer() { return container; }
+
+ // These methods are abstract in View, so we've got to provide
+ // dummy implementations of them here, even though they're never used.
+ public void paint(Graphics g, Shape allocation) {}
+ public float getPreferredSpan(int axis) { return 0.0f; }
+ public int viewToModel(float x,float y,Shape a,Position.Bias[] bias) {
+ return 0;
+ }
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException {
+ return a;
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java b/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java
new file mode 100644
index 0000000..fa0c03c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.table.*; // TableModel and other JTable-related classes
+import java.beans.*; // For JavaBean introspection
+import java.util.*; // For array sorting
+
+/**
+ * This class is a JTable subclass that displays a table of the JavaBeans
+ * properties of any specified class.
+ **/
+public class PropertyTable extends JTable {
+ /** This main method allows the class to be demonstrated standalone */
+ public static void main(String[] args) {
+ // Specify the name of the class as a command-line argument
+ Class beanClass = null;
+ try {
+ // Use reflection to get the Class from the classname
+ beanClass = Class.forName(args[0]);
+ }
+ catch (Exception e) { // Report errors
+ System.out.println("Can't find specified class: "+e.getMessage());
+ System.out.println("Usage: java TableDemo ");
+ System.exit(0);
+ }
+
+ // Create a table to display the properties of the specified class
+ JTable table = new PropertyTable(beanClass);
+
+ // Then put the table in a scrolling window, put the scrolling
+ // window into a frame, and pop it all up on to the screen
+ JScrollPane scrollpane = new JScrollPane(table);
+ JFrame frame = new JFrame("Properties of JavaBean: " + args[0]);
+ frame.getContentPane().add(scrollpane);
+ frame.setSize(500, 400);
+ frame.setVisible(true);
+ }
+
+ /**
+ * This constructor method specifies what data the table will display
+ * (the table model) and uses the TableColumnModel to customize the
+ * way that the table displays it. The hard work is done by the
+ * TableModel implementation below.
+ **/
+ public PropertyTable(Class beanClass) {
+ // Set the data model for this table
+ try {
+ setModel(new JavaBeanPropertyTableModel(beanClass));
+ }
+ catch (IntrospectionException e) {
+ System.err.println("WARNING: can't introspect: " + beanClass);
+ }
+
+ // Tweak the appearance of the table by manipulating its column model
+ TableColumnModel colmodel = getColumnModel();
+
+ // Set column widths
+ colmodel.getColumn(0).setPreferredWidth(125);
+ colmodel.getColumn(1).setPreferredWidth(200);
+ colmodel.getColumn(2).setPreferredWidth(75);
+ colmodel.getColumn(3).setPreferredWidth(50);
+
+ // Right justify the text in the first column
+ TableColumn namecol = colmodel.getColumn(0);
+ DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
+ renderer.setHorizontalAlignment(SwingConstants.RIGHT);
+ namecol.setCellRenderer(renderer);
+ }
+
+ /**
+ * This class implements TableModel and represents JavaBeans property data
+ * in a way that the JTable component can display. If you've got some
+ * type of tabular data to display, implement a TableModel class to
+ * describe that data, and the JTable component will be able to display it.
+ **/
+ static class JavaBeanPropertyTableModel extends AbstractTableModel {
+ PropertyDescriptor[] properties; // The properties to display
+
+ /**
+ * The constructor: use the JavaBeans introspector mechanism to get
+ * information about all the properties of a bean. Once we've got
+ * this information, the other methods will interpret it for JTable.
+ **/
+ public JavaBeanPropertyTableModel(Class beanClass)
+ throws IntrospectionException
+ {
+ // Use the introspector class to get "bean info" about the class.
+ BeanInfo beaninfo = Introspector.getBeanInfo(beanClass);
+ // Get the property descriptors from that BeanInfo class
+ properties = beaninfo.getPropertyDescriptors();
+ // Now do a case-insensitive sort by property name
+ // The anonymous Comparator implementation specifies how to
+ // sort PropertyDescriptor objects by name
+ Arrays.sort(properties, new Comparator() {
+ public int compare(Object p, Object q) {
+ PropertyDescriptor a = (PropertyDescriptor) p;
+ PropertyDescriptor b = (PropertyDescriptor) q;
+ return a.getName().compareToIgnoreCase(b.getName());
+ }
+ public boolean equals(Object o) { return o == this; }
+ });
+ }
+
+ // These are the names of the columns represented by this TableModel
+ static final String[] columnNames = new String[] {
+ "Name", "Type", "Access", "Bound"
+ };
+
+ // These are the types of the columns represented by this TableModel
+ static final Class[] columnTypes = new Class[] {
+ String.class, Class.class, String.class, Boolean.class
+ };
+
+ // These simple methods return basic information about the table
+ public int getColumnCount() { return columnNames.length; }
+ public int getRowCount() { return properties.length; }
+ public String getColumnName(int column) { return columnNames[column]; }
+ public Class getColumnClass(int column) { return columnTypes[column]; }
+
+ /**
+ * This method returns the value that appears at the specified row and
+ * column of the table
+ **/
+ public Object getValueAt(int row, int column) {
+ PropertyDescriptor prop = properties[row];
+ switch(column) {
+ case 0: return prop.getName();
+ case 1: return prop.getPropertyType();
+ case 2: return getAccessType(prop);
+ case 3: return new Boolean(prop.isBound());
+ default: return null;
+ }
+ }
+
+ // A helper method called from getValueAt() above
+ String getAccessType(PropertyDescriptor prop) {
+ java.lang.reflect.Method reader = prop.getReadMethod();
+ java.lang.reflect.Method writer = prop.getWriteMethod();
+ if ((reader != null) && (writer != null)) return "Read/Write";
+ else if (reader != null) return "Read-Only";
+ else if (writer != null) return "Write-Only";
+ else return "No Access"; // should never happen
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java b/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java
new file mode 100644
index 0000000..6bd4ed1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+
+/**
+ * This interface defines an extension mechanism that allows GUIResourceBundle
+ * to parse arbitrary resource types
+ **/
+public interface ResourceParser {
+ /**
+ * Return an array of classes that specify what kind of resources
+ * this parser can handle
+ **/
+ public Class[] getResourceTypes();
+
+ /**
+ * Read the property named by key from the specified bundle, convert
+ * it to the specified type, and return it. For complex resources,
+ * the parser may need to read more than one property from the bundle;
+ * typically it may a number of properties whose names begin with the
+ * specified key.
+ **/
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws Exception;
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/Scribble.java b/src/main/java/com/davidflanagan/examples/gui/Scribble.java
new file mode 100644
index 0000000..c983207
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/Scribble.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+
+/**
+ * This JFrame subclass is a simple "paint" application.
+ **/
+public class Scribble extends JFrame {
+ /**
+ * The main method instantiates an instance of the class, sets it size,
+ * and makes it visible on the screen
+ **/
+ public static void main(String[] args) {
+ Scribble scribble = new Scribble();
+ scribble.setSize(500, 300);
+ scribble.setVisible(true);
+ }
+
+ // The scribble application relies on the ScribblePane2 component developed
+ // earlier. This field holds the ScribblePane2 instance it uses.
+ ScribblePane2 scribblePane;
+
+ /**
+ * This constructor creates the GUI for this application.
+ **/
+ public Scribble() {
+ super("Scribble"); // Call superclass constructor and set window title
+
+ // Handle window close requests
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+
+ // All content of a JFrame (except for the menubar) goes in the
+ // Frame's internal "content pane", not in the frame itself.
+ // The same is true for JDialog and similar top-level containers.
+ Container contentPane = this.getContentPane();
+
+ // Specify a layout manager for the content pane
+ contentPane.setLayout(new BorderLayout());
+
+ // Create the main scribble pane component, give it a border, and
+ // a background color, and add it to the content pane
+ scribblePane = new ScribblePane2();
+ scribblePane.setBorder(new BevelBorder(BevelBorder.LOWERED));
+ scribblePane.setBackground(Color.white);
+ contentPane.add(scribblePane, BorderLayout.CENTER);
+
+ // Create a menubar and add it to this window. Note that JFrame
+ // handles menus specially and has a special method for adding them
+ // outside of the content pane.
+ JMenuBar menubar = new JMenuBar(); // Create a menubar
+ this.setJMenuBar(menubar); // Display it in the JFrame
+
+ // Create menus and add to the menubar
+ JMenu filemenu = new JMenu("File");
+ JMenu colormenu = new JMenu("Color");
+ menubar.add(filemenu);
+ menubar.add(colormenu);
+
+ // Create some Action objects for use in the menus and toolbars.
+ // An Action combines a menu title and/or icon with an ActionListener.
+ // These Action classes are defined as inner classes below.
+ Action clear = new ClearAction();
+ Action quit = new QuitAction();
+ Action black = new ColorAction(Color.black);
+ Action red = new ColorAction(Color.red);
+ Action blue = new ColorAction(Color.blue);
+ Action select = new SelectColorAction();
+
+ // Populate the menus using Action objects
+ filemenu.add(clear);
+ filemenu.add(quit);
+ colormenu.add(black);
+ colormenu.add(red);
+ colormenu.add(blue);
+ colormenu.add(select);
+
+ // Now create a toolbar, add actions to it, and add it to the
+ // top of the frame (where it appears underneath the menubar)
+ JToolBar toolbar = new JToolBar();
+ toolbar.add(clear);
+ toolbar.add(select);
+ toolbar.add(quit);
+ contentPane.add(toolbar, BorderLayout.NORTH);
+
+ // Create another toolbar for use as a color palette and add to
+ // the left side of the window.
+ JToolBar palette = new JToolBar();
+ palette.add(black);
+ palette.add(red);
+ palette.add(blue);
+ palette.setOrientation(SwingConstants.VERTICAL);
+ contentPane.add(palette, BorderLayout.WEST);
+ }
+
+ /** This inner class defines the "clear" action that clears the scribble */
+ class ClearAction extends AbstractAction {
+ public ClearAction() {
+ super("Clear"); // Specify the name of the action
+ }
+ public void actionPerformed(ActionEvent e) { scribblePane.clear(); }
+ }
+
+ /** This inner class defines the "quit" action to quit the program */
+ class QuitAction extends AbstractAction {
+ public QuitAction() { super("Quit"); }
+ public void actionPerformed(ActionEvent e) {
+ // Use JOptionPane to confirm that the user really wants to quit
+ int response =
+ JOptionPane.showConfirmDialog(Scribble.this, "Really Quit?");
+ if (response == JOptionPane.YES_OPTION) System.exit(0);
+ }
+ }
+
+ /**
+ * This inner class defines an Action that sets the current drawing color
+ * of the ScribblePane2 component. Note that actions of this type have
+ * icons rather than labels
+ **/
+ class ColorAction extends AbstractAction {
+ Color color;
+ public ColorAction(Color color) {
+ this.color = color;
+ putValue(Action.SMALL_ICON, new ColorIcon(color)); // specify icon
+ }
+ public void actionPerformed(ActionEvent e) {
+ scribblePane.setColor(color); // Set current drawing color
+ }
+ }
+
+ /**
+ * This inner class implements Icon to draw a solid 16x16 block of the
+ * specified color. Most icons are instances of ImageIcon, but since
+ * we're only using solid colors here, it is easier to implement this
+ * custom Icon type
+ **/
+ static class ColorIcon implements Icon {
+ Color color;
+ public ColorIcon(Color color) { this.color = color; }
+ // These two methods specify the size of the icon
+ public int getIconHeight() { return 16; }
+ public int getIconWidth() { return 16; }
+ // This method draws the icon
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ g.setColor(color);
+ g.fillRect(x, y, 16, 16);
+ }
+ }
+
+ /**
+ * This inner class defines an Action that uses JColorChooser to allow
+ * the user to select a drawing color
+ **/
+ class SelectColorAction extends AbstractAction {
+ public SelectColorAction() { super("Select Color..."); }
+ public void actionPerformed(ActionEvent e) {
+ Color color = JColorChooser.showDialog(Scribble.this,
+ "Select Drawing Color",
+ scribblePane.getColor());
+ if (color != null) scribblePane.setColor(color);
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java
new file mode 100644
index 0000000..c134213
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*; // For JPanel component
+import java.awt.*; // For Graphics object
+import java.awt.event.*; // For Event and Listener objects
+
+/**
+ * A simple JPanel subclass that uses event listeners to allow the user
+ * to scribble with the mouse. Note that scribbles are not saved or redrawn.
+ **/
+public class ScribblePane1 extends JPanel
+ implements MouseListener, MouseMotionListener {
+ protected int last_x, last_y; // Previous mouse coordinates
+
+ public ScribblePane1() {
+ // This component registers itself as an event listener for
+ // mouse events and mouse motion events.
+ this.addMouseListener(this);
+ this.addMouseMotionListener(this);
+
+ // Give the component a preferred size
+ setPreferredSize(new Dimension(450,200));
+ }
+
+ // A method from the MouseListener interface. Invoked when the
+ // user presses a mouse button.
+ public void mousePressed(MouseEvent e) {
+ last_x = e.getX(); // remember the coordinates of the click
+ last_y = e.getY();
+ }
+
+ // A method from the MouseMotionListener interface. Invoked when the
+ // user drags the mouse with a button pressed.
+ public void mouseDragged(MouseEvent e) {
+ int x = e.getX(); // Get the current mouse position
+ int y = e.getY();
+ // Draw a line from the saved coordinates to the current position
+ this.getGraphics().drawLine(last_x, last_y, x, y);
+ last_x = x; // Remember the current position
+ last_y = y;
+ }
+
+ // The other, unused methods of the MouseListener interface.
+ public void mouseReleased(MouseEvent e) {}
+ public void mouseClicked(MouseEvent e) {}
+ public void mouseEntered(MouseEvent e) {}
+ public void mouseExited(MouseEvent e) {}
+
+ // The other, unused, method of the MouseMotionListener interface.
+ public void mouseMoved(MouseEvent e) {}
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java
new file mode 100644
index 0000000..ee77a72
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*; // For JPanel component
+import java.awt.*; // For Graphics object
+import java.awt.event.*; // For Event and Listener objects
+
+/**
+ * A simple JPanel subclass that uses event listeners to allow the user
+ * to scribble with the mouse. Note that scribbles are not saved or redrawn.
+ **/
+public class ScribblePane2 extends JPanel {
+ public ScribblePane2() {
+ // Give the component a preferred size
+ setPreferredSize(new Dimension(450,200));
+
+ // Register a mouse event handler defined as an inner class
+ // Note the call to requestFocus(). This is required in order for
+ // the component to receive key events.
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+ moveto(e.getX(), e.getY()); // Move to click position
+ requestFocus(); // Take keyboard focus
+ }
+ });
+
+ // Register a mouse motion event handler defined as an inner class
+ // By subclassing MouseMotionAdapter rather than implementing
+ // MouseMotionListener, we only override the method we're interested
+ // in and inherit default (empty) implementations of the other methods.
+ addMouseMotionListener(new MouseMotionAdapter() {
+ public void mouseDragged(MouseEvent e) {
+ lineto(e.getX(), e.getY()); // Draw to mouse position
+ }
+ });
+
+ // Add a keyboard event handler to clear the screen on key 'C'
+ addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_C) clear();
+ }
+ });
+ }
+
+ /** These are the coordinates of the the previous mouse position */
+ protected int last_x, last_y;
+
+ /** Remember the specified point */
+ public void moveto(int x, int y) {
+ last_x = x;
+ last_y = y;
+ }
+
+ /** Draw from the last point to this point, then remember new point */
+ public void lineto(int x, int y) {
+ Graphics g = getGraphics(); // Get the object to draw with
+ g.setColor(color); // Tell it what color to use
+ g.drawLine(last_x, last_y, x, y); // Tell it what to draw
+ moveto(x, y); // Save the current point
+ }
+
+ /**
+ * Clear the drawing area, using the component background color. This
+ * method works by requesting that the component be redrawn. Since this
+ * component does not have a paintComponent() method, nothing will be
+ * drawn. However, other parts of the component, such as borders or
+ * sub-components will be drawn correctly.
+ **/
+ public void clear() { repaint(); }
+
+ /** This field holds the current drawing color property */
+ Color color = Color.black;
+ /** This is the property "setter" method for the color property */
+ public void setColor(Color color) { this.color = color; }
+ /** This is the property "getter" method for the color property */
+ public Color getColor() { return color; }
+
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java
new file mode 100644
index 0000000..0a88aa2
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*; // For Graphics object and colors
+import javax.swing.*; // For JPanel component
+import java.awt.event.*; // For ActionListener interface
+import javax.swing.event.*; // For ListSelectionListener interface
+
+/**
+ * This scribble component includes a JButton to clear the screen, and
+ * a JList that lets the user select a drawing color. It uses
+ * event listener objects to handle events from those sub-components.
+ **/
+public class ScribblePane3 extends ScribblePane2 {
+ // These are colors the user can choose from
+ Color[] colors = new Color[] { Color.black, Color.red, Color.blue };
+ // These are names for those colors
+ String[] colorNames = new String[] { "Black", "Red", "Blue" };
+
+ // Add JButton and JList components to the panel.
+ public ScribblePane3() {
+ // Implicit super() call here invokes the superclass constructor
+
+ // Add a "Clear" button to the panel.
+ // Handle button events with an action listener
+ JButton clear = new JButton("Clear");
+ clear.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { clear(); }
+ });
+ this.add(clear);
+
+ // Add a JList to allow color choices.
+ // Handle list selection events with a ListSelectionListener.
+ final JList colorList = new JList(colorNames);
+ colorList.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ setColor(colors[colorList.getSelectedIndex()]);
+ }
+ });
+ this.add(colorList);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java
new file mode 100644
index 0000000..8d51214
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*; // For JPanel component
+import java.awt.*; // For Graphics object
+import java.awt.event.*; // For Event and Listener objects
+
+/**
+ * Another scribble class. This one overrides the low-level event processing
+ * methods of the component instead of registering event listeners.
+ **/
+public class ScribblePane4 extends JPanel {
+ public ScribblePane4() {
+ // Give the component a preferred size
+ setPreferredSize(new Dimension(450,200));
+
+ // Tell the system what kind of events the component is interested in
+ enableEvents(AWTEvent.MOUSE_EVENT_MASK |
+ AWTEvent.MOUSE_MOTION_EVENT_MASK |
+ AWTEvent.KEY_EVENT_MASK);
+ }
+
+ public void processMouseEvent(MouseEvent e) {
+ if (e.getID() == MouseEvent.MOUSE_PRESSED) {
+ moveto(e.getX(), e.getY());
+ requestFocus();
+ }
+ else super.processMouseEvent(e); // pass unhandled events to superclass
+ }
+
+ public void processMouseMotionEvent(MouseEvent e) {
+ if (e.getID() == MouseEvent.MOUSE_DRAGGED) lineto(e.getX(), e.getY());
+ else super.processMouseMotionEvent(e);
+ }
+
+ public void processKeyEvent(KeyEvent e) {
+ if ((e.getID() == KeyEvent.KEY_PRESSED) &&
+ (e.getKeyCode() == KeyEvent.VK_C)) clear();
+ else super.processKeyEvent(e); // Give superclass a chance to handle
+ }
+
+ /** These are the coordinates of the the previous mouse position */
+ protected int last_x, last_y;
+
+ /** Remember the specified point */
+ public void moveto(int x, int y) {
+ last_x = x;
+ last_y = y;
+ }
+
+ /** Draw from the last point to this point, then remember new point */
+ public void lineto(int x, int y) {
+ getGraphics().drawLine(last_x, last_y, x, y);
+ moveto(x, y);
+ }
+
+ /** Clear the drawing area, using the component background color */
+ public void clear() { repaint(); }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java b/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java
new file mode 100644
index 0000000..dcc9b13
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.Vector;
+
+/**
+ * This class is a program that uses reflection and JavaBeans introspection to
+ * create a set of named components, set named properties on those components,
+ * and display them. It allows the user to view the components using any
+ * installed look-and-feel. It is intended as a simple way to experiment with
+ * AWT and Swing components, and to view a number of the other examples
+ * developed in this chapter. It also demonstrates frames, menus, and the
+ * JTabbedPane component.
+ **/
+public class ShowComponent {
+ // The main program
+ public static void main(String[] args) {
+ // Process the command line to get the components to display
+ Vector components = getComponentsFromArgs(args);
+
+ // Create a frame (a window) to display them in
+ JFrame frame = new JFrame("ShowComponent");
+
+ // Handle window close requests by exiting the VM
+ frame.addWindowListener(new WindowAdapter() { // Anonymous inner class
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+
+ // Set up a menu system that allows the user to select the
+ // look-and-feel of the component from a list of installed PLAFs
+ JMenuBar menubar = new JMenuBar(); // Create a menubar
+ frame.setJMenuBar(menubar); // Tell the frame to display it
+ JMenu plafmenu = createPlafMenu(frame); // Create a menu
+ menubar.add(plafmenu); // Add the menu to the menubar
+
+ // Create a JTabbedPane to display each of the components
+ JTabbedPane pane = new JTabbedPane();
+
+ // Now add each component as a tab of the tabbed pane
+ // Use the unqualified component classname as the tab text
+ for(int i = 0; i < components.size(); i++) {
+ Component c = (Component)components.elementAt(i);
+ String classname = c.getClass().getName();
+ String tabname = classname.substring(classname.lastIndexOf('.')+1);
+ pane.addTab(tabname, c);
+ }
+
+ // Add the tabbed pane to the frame. Note the call to getContentPane()
+ // This is required for JFrame, but not for most Swing components
+ frame.getContentPane().add(pane);
+
+ // Set the frame size and pop it up
+ frame.pack(); // Make frame as big as its kids need
+ frame.setVisible(true); // Make the frame visible on the screen
+
+ // The main() method exits now but the Java VM keeps running because
+ // all AWT programs automatically start an event-handling thread.
+ }
+
+ /**
+ * This static method queries the system to find out what Pluggable
+ * Look-and-Feel (PLAF) implementations are available. Then it creates a
+ * JMenu component that lists each of the implementations by name and
+ * allows the user to select one of them using JRadioButtonMenuItem
+ * components. When the user selects one, the selected menu item
+ * traverses the component hierarchy and tells all components to use the
+ * new PLAF.
+ **/
+ public static JMenu createPlafMenu(final JFrame frame) {
+ // Create the menu
+ JMenu plafmenu = new JMenu("Look and Feel");
+
+ // Create an object used for radio button mutual exclusion
+ ButtonGroup radiogroup = new ButtonGroup();
+
+ // Look up the available look and feels
+ UIManager.LookAndFeelInfo[] plafs =
+ UIManager.getInstalledLookAndFeels();
+
+ // Loop through the plafs, and add a menu item for each one
+ for(int i = 0; i < plafs.length; i++) {
+ String plafName = plafs[i].getName();
+ final String plafClassName = plafs[i].getClassName();
+
+ // Create the menu item
+ JMenuItem item = plafmenu.add(new JRadioButtonMenuItem(plafName));
+
+ // Tell the menu item what to do when it is selected
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ // Set the new look and feel
+ UIManager.setLookAndFeel(plafClassName);
+ // Tell each component to change its look-and-feel
+ SwingUtilities.updateComponentTreeUI(frame);
+ // Tell the frame to resize itself to the its
+ // children's new desired sizes
+ frame.pack();
+ }
+ catch(Exception ex) { System.err.println(ex); }
+ }
+
+ });
+
+ // Only allow one menu item to be selected at once
+ radiogroup.add(item);
+ }
+ return plafmenu;
+ }
+
+ /**
+ * This method loops through the command line arguments looking for
+ * class names of components to create and property settings for those
+ * components in the form name=value. This method demonstrates
+ * reflection and JavaBeans introspection as they can be applied to
+ * dynamically created GUIs
+ **/
+ public static Vector getComponentsFromArgs(String[] args) {
+ Vector components = new Vector(); // List of components to return
+ Component component = null; // The current component
+ PropertyDescriptor[] properties = null; // Properties of the component
+ Object[] methodArgs = new Object[1]; // We'll use this below
+
+ nextarg: // This is a labeled loop
+ for(int i = 0; i < args.length; i++) { // Loop through all arguments
+ // If the argument does not contain an equal sign, then it is
+ // a component class name. Otherwise it is a property setting
+ int equalsPos = args[i].indexOf('=');
+ if (equalsPos == -1) { // Its the name of a component
+ try {
+ // Load the named component class
+ Class componentClass = Class.forName(args[i]);
+ // Instantiate it to create the component instance
+ component = (Component)componentClass.newInstance();
+ // Use JavaBeans to introspect the component
+ // And get the list of properties it supports
+ BeanInfo componentBeanInfo =
+ Introspector.getBeanInfo(componentClass);
+ properties = componentBeanInfo.getPropertyDescriptors();
+ }
+ catch(Exception e) {
+ // If any step failed, print an error and exit
+ System.out.println("Can't load, instantiate, " +
+ "or introspect: " + args[i]);
+ System.exit(1);
+ }
+
+ // If we succeeded, store the component in the vector
+ components.addElement(component);
+ }
+ else { // The arg is a name=value property specification
+ String name =args[i].substring(0, equalsPos); // property name
+ String value =args[i].substring(equalsPos+1); // property value
+
+ // If we don't have a component to set this property on, skip!
+ if (component == null) continue nextarg;
+
+ // Now look through the properties descriptors for this
+ // component to find one with the same name.
+ for(int p = 0; p < properties.length; p++) {
+ if (properties[p].getName().equals(name)) {
+ // Okay, we found a property of the right name.
+ // Now get its type, and the setter method
+ Class type = properties[p].getPropertyType();
+ Method setter = properties[p].getWriteMethod();
+
+ // Check if property is read-only!
+ if (setter == null) {
+ System.err.println("Property " + name+
+ " is read-only");
+ continue nextarg; // continue with next argument
+ }
+
+ // Try to convert the property value to the right type
+ // We support a small set of common property types here
+ // Store the converted value in an Object[] so it can
+ // be easily passed when we invoke the property setter
+ try {
+ if (type == String.class) { // no conversion needed
+ methodArgs[0] = value;
+ }
+ else if (type == int.class) { // String to int
+ methodArgs[0] = Integer.valueOf(value);
+ }
+ else if (type == boolean.class) { // to boolean
+ methodArgs[0] = Boolean.valueOf(value);
+ }
+ else if (type == Color.class) { // to Color
+ methodArgs[0] = Color.decode(value);
+ }
+ else if (type == Font.class) { // String to Font
+ methodArgs[0] = Font.decode(value);
+ }
+ else {
+ // If we can't convert, ignore the property
+ System.err.println("Property " + name +
+ " is of unsupported type " +
+ type.getName());
+ continue nextarg;
+ }
+ }
+ catch (Exception e) {
+ // If conversion failed, continue with the next arg
+ System.err.println("Can't convert '" + value +
+ "' to type " + type.getName() +
+ " for property " + name);
+ continue nextarg;
+ }
+
+ // Finally, use reflection to invoke the property
+ // setter method of the component we created, and pass
+ // in the converted property value.
+ try { setter.invoke(component, methodArgs); }
+ catch (Exception e) {
+ System.err.println("Can't set property: " + name);
+ }
+
+ // Now go on to next command-line arg
+ continue nextarg;
+ }
+ }
+
+ // If we get here, we didn't find the named property
+ System.err.println("Warning: No such property: " + name);
+ }
+ }
+
+ return components;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java b/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java
new file mode 100644
index 0000000..aea333e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+import javax.swing.plaf.metal.DefaultMetalTheme;
+
+/**
+ * This class reads theme descriptions from a GUIResourceBundle and uses them
+ * to specify colors and fonts for the Metal look-and-feel.
+ **/
+public class ThemeManager {
+ JFrame frame; // The frame which themes are applied to
+ GUIResourceBundle resources; // Properties describing the themes
+
+ /**
+ * Build a ThemeManager for the frame and resource bundle. If there
+ * is a default theme specified, apply it to the frame
+ **/
+ public ThemeManager(JFrame frame, GUIResourceBundle resources) {
+ this.frame = frame;
+ this.resources = resources;
+ String defaultName = getDefaultThemeName();
+ if (defaultName != null) setTheme(defaultName);
+ }
+
+ /** Look up the named theme, and apply it to the frame */
+ public void setTheme(String themeName) {
+ // Look up the theme in the resource bundle
+ Theme theme = new Theme(resources, themeName);
+ // Make it the current theme
+ MetalLookAndFeel.setCurrentTheme(theme);
+ // Re-apply the Metal look-and-feel to install new theme
+ try { UIManager.setLookAndFeel(new MetalLookAndFeel()); }
+ catch(UnsupportedLookAndFeelException e) {}
+ // Propagate the new l&f across the entire component tree of the frame
+ SwingUtilities.updateComponentTreeUI(frame);
+ }
+
+ /** Get the "display name" or label of the named theme */
+ public String getDisplayName(String themeName) {
+ return resources.getString(themeName + ".name", null);
+ }
+
+ /** Get the name of the default theme, or null */
+ public String getDefaultThemeName() {
+ return resources.getString("defaultTheme", null);
+ }
+
+ /**
+ * Get the list of all known theme names. The returned values are
+ * theme property names, not theme display names.
+ **/
+ public String[] getAllThemeNames() {
+ java.util.List names = resources.getStringList("themelist");
+ return (String[]) names.toArray(new String[names.size()]);
+ }
+
+ /**
+ * Get a JMenu that lists all known themes by display name and
+ * installs any selected theme.
+ **/
+ public JMenu getThemeMenu() {
+ String[] names = getAllThemeNames();
+ String defaultName = getDefaultThemeName();
+ JMenu menu = new JMenu("Themes");
+ ButtonGroup buttongroup = new ButtonGroup();
+ for(int i = 0; i < names.length; i++) {
+ final String themeName = names[i];
+ String displayName = getDisplayName(themeName);
+ JMenuItem item = menu.add(new JRadioButtonMenuItem(displayName));
+ buttongroup.add(item);
+ if (themeName.equals(defaultName)) item.setSelected(true);
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ setTheme(themeName);
+ }
+ });
+ }
+ return menu;
+ }
+
+ /**
+ * This class extends the DefaultMetalTheme class to return Color and
+ * Font values read from a GUIResourceBundle
+ **/
+ public static class Theme extends DefaultMetalTheme {
+ // These fields are the values returned by this Theme
+ String displayName;
+ FontUIResource controlFont, menuFont, smallFont;
+ FontUIResource systemFont, userFont, titleFont;
+ ColorUIResource primary1, primary2, primary3;
+ ColorUIResource secondary1, secondary2, secondary3;
+
+ /**
+ * This constructor reads all the values it needs from the
+ * GUIResourceBundle. It uses intelligent defaults if properties
+ * are not specified.
+ **/
+ public Theme(GUIResourceBundle resources, String name) {
+ // Use this theme object to get default font values from
+ DefaultMetalTheme defaultTheme = new DefaultMetalTheme();
+
+ // Look up the display name of the theme
+ displayName = resources.getString(name + ".name", null);
+
+ // Look up the fonts for the theme
+ Font control = resources.getFont(name + ".controlFont", null);
+ Font menu = resources.getFont(name + ".menuFont", null);
+ Font small = resources.getFont(name + ".smallFont", null);
+ Font system = resources.getFont(name + ".systemFont", null);
+ Font user = resources.getFont(name + ".userFont", null);
+ Font title = resources.getFont(name + ".titleFont", null);
+
+ // Convert fonts to FontUIResource, or get defaults
+ if (control != null) controlFont = new FontUIResource(control);
+ else controlFont = defaultTheme.getControlTextFont();
+ if (menu != null) menuFont = new FontUIResource(menu);
+ else menuFont = defaultTheme.getMenuTextFont();
+ if (small != null) smallFont = new FontUIResource(small);
+ else smallFont = defaultTheme.getSubTextFont();
+ if (system != null) systemFont = new FontUIResource(system);
+ else systemFont = defaultTheme.getSystemTextFont();
+ if (user != null) userFont = new FontUIResource(user);
+ else userFont = defaultTheme.getUserTextFont();
+ if (title != null) titleFont = new FontUIResource(title);
+ else titleFont = defaultTheme.getWindowTitleFont();
+
+ // Look up primary and secondary colors
+ Color primary = resources.getColor(name + ".primary", null);
+ Color secondary = resources.getColor(name + ".secondary", null);
+
+ // Derive all six colors from these two, using defaults if needed
+ if (primary != null) primary1 = new ColorUIResource(primary);
+ else primary1 = new ColorUIResource(102, 102, 153);
+ primary2 = new ColorUIResource(primary1.brighter());
+ primary3 = new ColorUIResource(primary2.brighter());
+ if (secondary != null) secondary1 = new ColorUIResource(secondary);
+ else secondary1 = new ColorUIResource(102, 102, 102);
+ secondary2 = new ColorUIResource(secondary1.brighter());
+ secondary3 = new ColorUIResource(secondary2.brighter());
+ }
+
+ // These methods override DefaultMetalTheme and return the property
+ // values we looked up and computed for this theme
+ public String getName() { return displayName; }
+ public FontUIResource getControlTextFont() { return controlFont;}
+ public FontUIResource getSystemTextFont() { return systemFont;}
+ public FontUIResource getUserTextFont() { return userFont;}
+ public FontUIResource getMenuTextFont() { return menuFont;}
+ public FontUIResource getWindowTitleFont() { return titleFont;}
+ public FontUIResource getSubTextFont() { return smallFont;}
+ protected ColorUIResource getPrimary1() { return primary1; }
+ protected ColorUIResource getPrimary2() { return primary2; }
+ protected ColorUIResource getPrimary3() { return primary3; }
+ protected ColorUIResource getSecondary1() { return secondary1; }
+ protected ColorUIResource getSecondary2() { return secondary2; }
+ protected ColorUIResource getSecondary3() { return secondary3; }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java b/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java
new file mode 100644
index 0000000..7ee333c
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * Parse a JToolBar from a ResourceBundle. A toolbar is represented
+ * simply as a list of action property names. E.g.:
+ * toolbar: action.save, action.print, action.quit
+ **/
+public class ToolBarParser implements ResourceParser {
+ static final Class[] supportedTypes = new Class[] { JToolBar.class };
+ public Class[] getResourceTypes() { return supportedTypes; }
+
+ public Object parse(GUIResourceBundle bundle, String key, Class type)
+ throws MissingResourceException
+ {
+ // Get the value of the key as a list of strings
+ List toolList = bundle.getStringList(key);
+
+ // Create a ToolBar
+ JToolBar toolbar = new JToolBar();
+
+ // Create a JTool for each of the tool property names,
+ // and add it to the bar
+ int numtools = toolList.size();
+ for(int i = 0; i < numtools; i++) {
+ // Get the action name
+ String tool = (String)toolList.get(i);
+ // Get the Action object associated with that name
+ Action action = (Action) bundle.getResource(tool, Action.class);
+ // Add the action to the toolbar, and get the JButton it creates
+ JButton button = toolbar.add(action);
+ // If the action contains a description, use it as the tooltip
+ String tooltip = (String)action.getValue(Action.SHORT_DESCRIPTION);
+ if (tooltip != null) button.setToolTipText(tooltip);
+ }
+
+ return toolbar;
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java b/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java
new file mode 100644
index 0000000..cde73f7
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.gui;
+import java.awt.*; // LayoutManager stuff
+import javax.swing.*; // Swing components
+import java.awt.event.*; // AWT event handlers
+import javax.swing.event.*; // Swing event handlers
+import java.beans.*; // JavaBeans event handlers
+import java.awt.print.*; // Printing functionality
+import java.io.*; // Input/output
+import java.net.*; // Networking with URLs
+import java.util.*; // Hashtables and other utilities
+// Import this class by name. JFileChooser uses it, and its name conflicts
+// with java.io.FileFilter
+import javax.swing.filechooser.FileFilter;
+// Import a class for printing Swing documents. See printing chapter.
+import com.davidflanagan.examples.print.PrintableDocument;
+
+/**
+ * This class implements a simple web browser using the HTML
+ * display capabilities of the JEditorPane component.
+ **/
+public class WebBrowser extends JFrame
+ implements HyperlinkListener, PropertyChangeListener
+{
+ /**
+ * A simple main() method that allows the WebBrowser class to be used
+ * as a stand-alone application.
+ **/
+ public static void main(String[] args) throws IOException {
+ // End the program when there are no more open browser windows
+ WebBrowser.setExitWhenLastWindowClosed(true);
+ WebBrowser browser = new WebBrowser(); // Create a browser window
+ browser.setSize(800, 600); // Set its size
+ browser.setVisible(true); // Make it visible.
+
+ // Tell the browser what to display. This method is defined below.
+ browser.displayPage((args.length > 0) ? args[0] : browser.getHome());
+ }
+
+ // This class uses GUIResourceBundle to create its menubar and toolbar
+ // This static initializer performs one-time registration of the
+ // required ResourceParser classes.
+ static {
+ GUIResourceBundle.registerResourceParser(new MenuBarParser());
+ GUIResourceBundle.registerResourceParser(new MenuParser());
+ GUIResourceBundle.registerResourceParser(new ActionParser());
+ GUIResourceBundle.registerResourceParser(new CommandParser());
+ GUIResourceBundle.registerResourceParser(new ToolBarParser());
+ }
+
+ // These are the Swing components that the browser uses
+ JEditorPane textPane; // Where the HTML is displayed
+ JLabel messageLine; // Displays one-line messages
+ JTextField urlField; // Displays and edits the current URL
+ JFileChooser fileChooser; // Allows the user to select a local file
+
+ // These are Actions that are used in the menubar and toolbar.
+ // We obtain explicit references to them from the GUIResourceBundle
+ // so we can enable and disable them.
+ Action backAction, forwardAction;
+
+ // These fields are used to maintain the browsing history of the window
+ java.util.List history = new ArrayList(); // The history list
+ int currentHistoryPage = -1; // Current location in it
+ public static final int MAX_HISTORY = 50; // Trim list when over this size
+
+ // These static fields control the behavior of the close() action
+ static int numBrowserWindows = 0;
+ static boolean exitWhenLastWindowClosed = false;
+
+ // This is where the "home()" method takes us. See also setHome()
+ String home = "http://www.davidflanagan.com"; // A default value
+
+ /** Create and initialize a new WebBrowser window */
+ public WebBrowser() {
+ super(); // Chain to JFrame constructor
+
+ textPane = new JEditorPane(); // Create HTML window
+ textPane.setEditable(false); // Don't allow the user to edit it
+
+ // Register action listeners. The first is to handle hyperlinks.
+ // The second is to receive property change notifications, which tell
+ // us when a document is done loading. This class implements these
+ // EventListener interfaces, and the methods are defined below
+ textPane.addHyperlinkListener(this);
+ textPane.addPropertyChangeListener(this);
+
+ // Put the text pane in a JScrollPane in the center of the window
+ this.getContentPane().add(new JScrollPane(textPane),
+ BorderLayout.CENTER);
+
+ // Now create a message line and place it at the bottom of the window
+ messageLine = new JLabel(" ");
+ this.getContentPane().add(messageLine, BorderLayout.SOUTH);
+
+ // Read the file WebBrowserResources.properties (and any localized
+ // variants appropriate for the current Locale) to create a
+ // GUIResourceBundle from which we'll get our menubar and toolbar.
+ GUIResourceBundle resources =
+ new GUIResourceBundle(this,"com.davidflanagan.examples.gui." +
+ "WebBrowserResources");
+
+ // Read a menubar from the resource bundle and display it
+ JMenuBar menubar = (JMenuBar) resources.getResource("menubar",
+ JMenuBar.class);
+ this.setJMenuBar(menubar);
+
+ // Read a toolbar from the resource bundle. Don't display it yet.
+ JToolBar toolbar =
+ (JToolBar) resources.getResource("toolbar", JToolBar.class);
+
+ // Create a text field that the user can enter a URL in.
+ // Set up an action listener to respond to the ENTER key in that field
+ urlField = new JTextField();
+ urlField.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ displayPage(urlField.getText());
+ }
+ });
+
+ // Add the URL field and a label for it to the end of the toolbar
+ toolbar.add(new JLabel(" URL:"));
+ toolbar.add(urlField);
+
+ // And add the toolbar to the top of the window
+ this.getContentPane().add(toolbar, BorderLayout.NORTH);
+
+ // Read cached copies of two Action objects from the resource bundle
+ // These actions are used by the menubar and toolbar, and enabling and
+ // disabling them enables and disables the menu and toolbar items.
+ backAction = (Action)resources.getResource("action.back",Action.class);
+ forwardAction =
+ (Action)resources.getResource("action.forward", Action.class);
+
+ // Start off with both actions disabled
+ backAction.setEnabled(false);
+ forwardAction.setEnabled(false);
+
+ // Create a ThemeManager for this frame,
+ // and add a Theme menu to the menubar
+ ThemeManager themes = new ThemeManager(this, resources);
+ menubar.add(themes.getThemeMenu());
+
+ // Keep track of how many web browser windows are open
+ WebBrowser.numBrowserWindows++;
+ }
+
+ /** Set the static property that controls the behavior of close() */
+ public static void setExitWhenLastWindowClosed(boolean b) {
+ exitWhenLastWindowClosed = b;
+ }
+
+ /** These are accessor methods for the home property. */
+ public void setHome(String home) { this.home = home; }
+ public String getHome() { return home; }
+
+ /**
+ * This internal method attempts to load and display the specified URL.
+ * It is called from various places throughout the class.
+ **/
+ boolean visit(URL url) {
+ try {
+ String href = url.toString();
+ // Start animating. Animation is stopped in propertyChanged()
+ startAnimation("Loading " + href + "...");
+ textPane.setPage(url); // Load and display the URL
+ this.setTitle(href); // Display URL in window titlebar
+ urlField.setText(href); // Display URL in text input field
+ return true; // Return success
+ }
+ catch (IOException ex) { // If page loading fails
+ stopAnimation();
+ messageLine.setText("Can't load page: " + ex.getMessage());
+ return false; // Return failure
+ }
+ }
+
+ /**
+ * Ask the browser to display the specified URL, and put it in the
+ * history list.
+ **/
+ public void displayPage(URL url) {
+ if (visit(url)) { // go to the specified url, and if we succeed:
+ history.add(url); // Add the url to the history list
+ int numentries = history.size();
+ if (numentries > MAX_HISTORY+10) { // Trim history when too large
+ history = history.subList(numentries-MAX_HISTORY, numentries);
+ numentries = MAX_HISTORY;
+ }
+ currentHistoryPage = numentries-1; // Set current history page
+ // If we can go back, then enable the Back action
+ if (currentHistoryPage > 0) backAction.setEnabled(true);
+ }
+ }
+
+ /** Like displayPage(URL), but takes a string instead */
+ public void displayPage(String href) {
+ try {
+ displayPage(new URL(href));
+ }
+ catch (MalformedURLException ex) {
+ messageLine.setText("Bad URL: " + href);
+ }
+ }
+
+ /** Allow the user to choose a local file, and display it */
+ public void openPage() {
+ // Lazy creation: don't create the JFileChooser until it is needed
+ if (fileChooser == null) {
+ fileChooser = new JFileChooser();
+ // This javax.swing.filechooser.FileFilter displays only HTML files
+ FileFilter filter = new FileFilter() {
+ public boolean accept(File f) {
+ String fn = f.getName();
+ if (fn.endsWith(".html") || fn.endsWith(".htm"))
+ return true;
+ else return false;
+ }
+ public String getDescription() { return "HTML Files"; }
+ };
+ fileChooser.setFileFilter(filter);
+ fileChooser.addChoosableFileFilter(filter);
+ }
+
+ // Ask the user to choose a file.
+ int result = fileChooser.showOpenDialog(this);
+ if (result == JFileChooser.APPROVE_OPTION) {
+ // If they didn't click "Cancel", then try to display the file.
+ File selectedFile = fileChooser.getSelectedFile();
+ String url = "file://" + selectedFile.getAbsolutePath();
+ displayPage(url);
+ }
+ }
+
+ /** Go back to the previously displayed page. */
+ public void back() {
+ if (currentHistoryPage > 0) // go back, if we can
+ visit((URL)history.get(--currentHistoryPage));
+ // Enable or disable actions as appropriate
+ backAction.setEnabled((currentHistoryPage > 0));
+ forwardAction.setEnabled((currentHistoryPage < history.size()-1));
+ }
+
+ /** Go forward to the next page in the history list */
+ public void forward() {
+ if (currentHistoryPage < history.size()-1) // go forward, if we can
+ visit((URL)history.get(++currentHistoryPage));
+ // Enable or disable actions as appropriate
+ backAction.setEnabled((currentHistoryPage > 0));
+ forwardAction.setEnabled((currentHistoryPage < history.size()-1));
+ }
+
+ /** Reload the current page in the history list */
+ public void reload() {
+ if (currentHistoryPage != -1)
+ visit((URL)history.get(currentHistoryPage));
+ }
+
+ /** Display the page specified by the "home" property */
+ public void home() { displayPage(getHome()); }
+
+ /** Open a new browser window */
+ public void newBrowser() {
+ WebBrowser b = new WebBrowser();
+ b.setSize(this.getWidth(), this.getHeight());
+ b.setVisible(true);
+ }
+
+ /**
+ * Close this browser window. If this was the only open window,
+ * and exitWhenLastBrowserClosed is true, then exit the VM
+ **/
+ public void close() {
+ this.setVisible(false); // Hide the window
+ this.dispose(); // Destroy the window
+ synchronized(WebBrowser.class) { // Synchronize for thread-safety
+ WebBrowser.numBrowserWindows--; // There is one window fewer now
+ if ((numBrowserWindows==0) && exitWhenLastWindowClosed)
+ System.exit(0); // Exit if it was the last one
+ }
+ }
+
+ /**
+ * Exit the VM. If confirm is true, ask the user if they are sure.
+ * Note that showConfirmDialog() displays a dialog, waits for the user,
+ * and returns the user's response (i.e. the button the user selected).
+ **/
+ public void exit(boolean confirm) {
+ if (!confirm ||
+ (JOptionPane.showConfirmDialog(this, // dialog parent
+ /* message to display */ "Are you sure you want to quit?",
+ /* dialog title */ "Really Quit?",
+ /* dialog buttons */ JOptionPane.YES_NO_OPTION) ==
+ JOptionPane.YES_OPTION)) // If Yes button was clicked
+ System.exit(0);
+ }
+
+ /**
+ * Print the contents of the text pane using the java.awt.print API
+ * Note that this API does not work efficiently in Java 1.2
+ * All the hard work is done by the PrintableDocument class.
+ **/
+ public void print() {
+ // Get a PrinterJob object from the system
+ PrinterJob job = PrinterJob.getPrinterJob();
+ // This is the object that we are going to print
+ PrintableDocument pd = new PrintableDocument(textPane);
+ // Tell the PrinterJob what we want to print
+ job.setPageable(pd);
+ // Display a print dialog, asking the user what pages to print, what
+ // printer to print to, and giving the user a chance to cancel.
+ if (job.printDialog()) { // If the user did not cancel
+ try { job.print(); } // Start printing!
+ catch(PrinterException ex) { // display errors nicely
+ messageLine.setText("Couldn't print: " + ex.getMessage());
+ }
+ }
+ }
+
+ /**
+ * This method implements HyperlinkListener. It is invoked when the user
+ * clicks on a hyperlink, or move the mouse onto or off of a link
+ **/
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ HyperlinkEvent.EventType type = e.getEventType(); // what happened?
+ if (type == HyperlinkEvent.EventType.ACTIVATED) { // Click!
+ displayPage(e.getURL()); // Follow the link; display new page
+ }
+ else if (type == HyperlinkEvent.EventType.ENTERED) { // Mouse over!
+ // When mouse goes over a link, display it in the message line
+ messageLine.setText(e.getURL().toString());
+ }
+ else if (type == HyperlinkEvent.EventType.EXITED) { // Mouse out!
+ messageLine.setText(" "); // Clear the message line
+ }
+ }
+
+ /**
+ * This method implements java.beans.PropertyChangeListener. It is
+ * invoked whenever a bound property changes in the JEditorPane object.
+ * The property we are interested in is the "page" property, because it
+ * tells us when a page has finished loading.
+ **/
+ public void propertyChange(PropertyChangeEvent e) {
+ if (e.getPropertyName().equals("page")) // If the page property changed
+ stopAnimation(); // Then stop the loading... animation
+ }
+
+ /**
+ * The fields and methods below implement a simple animation in the
+ * web browser message line; they are used to provide user feedback
+ * while web pages are loading.
+ **/
+ String animationMessage; // The "loading..." message to display
+ int animationFrame = 0; // What "frame" of the animation are we on
+ String[] animationFrames = new String[] { // The content of each "frame"
+ "-", "\\", "|", "/", "-", "\\", "|", "/",
+ ",", ".", "o", "0", "O", "#", "*", "+"
+ };
+
+ /** This object calls the animate() method 8 times a second */
+ javax.swing.Timer animator =
+ new javax.swing.Timer(125, new ActionListener() {
+ public void actionPerformed(ActionEvent e) { animate(); }
+ });
+
+ /** Display the next frame. Called by the animator timer */
+ void animate() {
+ String frame = animationFrames[animationFrame++]; // Get next frame
+ messageLine.setText(animationMessage + " " + frame); // Update msgline
+ animationFrame = animationFrame % animationFrames.length;
+ }
+
+ /** Start the animation. Called by the visit() method. */
+ void startAnimation(String msg) {
+ animationMessage = msg; // Save the message to display
+ animationFrame = 0; // Start with frame 0 of the animation
+ animator.start(); // Tell the timer to start firing.
+ }
+
+ /** Stop the animation. Called by propertyChanged() method. */
+ void stopAnimation() {
+ animator.stop(); // Tell the timer to stop firing events
+ messageLine.setText(" "); // Clear the message line
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties b/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties
new file mode 100644
index 0000000..d370992
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties
@@ -0,0 +1,70 @@
+menubar: menu.file menu.go
+menu.file: File: action.new action.open action.print - action.close action.exit
+menu.go: Go: action.back action.forward action.reload action.home
+
+toolbar: action.back action.forward action.reload action.home action.oreilly
+
+action.back: back();
+action.back.label: Back
+action.back.description: View previous page in history
+
+action.forward: forward();
+action.forward.label: Forward
+action.forward.description: View next page in history
+
+action.reload: reload();
+action.reload.label: Reload
+action.reload.description: Reload the current page
+
+action.home: home();
+action.home.label: Home
+action.home.description: Go to home page
+
+action.oreilly: displayPage("http://www.oreilly.com");
+action.oreilly.label: O'Reilly
+action.oreilly.description: O'Reilly & Associates home page
+
+action.new: newBrowser();
+action.new.label: New Web Browser
+action.new.description: Create a new Web Browser window
+
+action.open: openPage();
+action.open.label: Open...
+action.open.description: Display a local HTML file
+
+action.print: print();
+action.print.label: Print...
+action.print.description: Print the current page
+
+action.close: close();
+action.close.label: Close
+action.close.description: Close this window
+
+action.exit: exit(true);
+action.exit.label: Exit
+action.exit.description: Close all windows and exit
+
+themelist: theme.metal theme.rose, theme.lime, theme.primary, theme.bigfont
+defaultTheme: theme.metal
+
+theme.metal.name: Default Metal
+
+theme.rose.name: Rose
+theme.rose.primary: #905050
+theme.rose.secondary: #906050
+
+theme.lime.name: Lime
+theme.lime.primary: #509050
+theme.lime.secondary: #506060
+
+theme.primary.name: Primary Colors
+theme.primary.primary: #202090
+theme.primary.secondary: #209020
+
+theme.bigfont.name: Big Fonts
+theme.bigfont.controlFont: sanserif-bold-18
+theme.bigfont.menuFont: sanserif-bold-18
+theme.bigfont.smallFont: sansserif-plain-14
+theme.bigfont.systemFont: sanserif-plain-14
+theme.bigfont.userFont: sanserif-plain-14
+theme.bigfont.titleFont: sansserif-bold-18
diff --git a/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java b/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java
new file mode 100644
index 0000000..3f94019
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.i18n;
+import java.io.*;
+
+/** A program to convert from one character encoding to another */
+public class ConvertEncoding {
+ public static void main(String[] args) {
+ String from = null, to = null;
+ String infile = null, outfile = null;
+ for(int i = 0; i < args.length; i++) { // Parse command-line arguments.
+ if (i == args.length-1) usage(); // All args require another.
+ if (args[i].equals("-from")) from = args[++i];
+ else if (args[i].equals("-to")) to = args[++i];
+ else if (args[i].equals("-in")) infile = args[++i];
+ else if (args[i].equals("-out")) outfile = args[++i];
+ else usage();
+ }
+
+ try { convert(infile, outfile, from, to); } // Attempt conversion.
+ catch (Exception e) { // Handle exceptions.
+ LocalizedError.display(e); // Defined at the end of this chapter.
+ System.exit(1);
+ }
+ }
+
+ public static void usage() {
+ System.err.println("Usage: java ConvertEncoding \n" +
+ "Options:\n\t-from \n\t" +
+ "-to \n\t" +
+ "-in \n\t-out ");
+ System.exit(1);
+ }
+
+ public static void convert(String infile, String outfile,
+ String from, String to)
+ throws IOException, UnsupportedEncodingException
+ {
+ // Set up byte streams.
+ InputStream in;
+ if (infile != null) in = new FileInputStream(infile);
+ else in = System.in;
+ OutputStream out;
+ if (outfile != null) out = new FileOutputStream(outfile);
+ else out = System.out;
+
+ // Use default encoding if no encoding is specified.
+ if (from == null) from = System.getProperty("file.encoding");
+ if (to == null) to = System.getProperty("file.encoding");
+
+ // Set up character streams.
+ Reader r = new BufferedReader(new InputStreamReader(in, from));
+ Writer w = new BufferedWriter(new OutputStreamWriter(out, to));
+
+ // Copy characters from input to output. The InputStreamReader
+ // converts from the input encoding to Unicode, and the
+ // OutputStreamWriter converts from Unicode to the output encoding.
+ // Characters that cannot be represented in the output encoding are
+ // output as '?'
+ char[] buffer = new char[4096];
+ int len;
+ while((len = r.read(buffer)) != -1) // Read a block of input.
+ w.write(buffer, 0, len); // And write it out.
+ r.close(); // Close the input.
+ w.close(); // Flush and close output.
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/i18n/Errors.properties b/src/main/java/com/davidflanagan/examples/i18n/Errors.properties
new file mode 100644
index 0000000..efcb6eb
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/Errors.properties
@@ -0,0 +1,22 @@
+#
+# This is the file Errors.properties
+# One property for each class of exceptions that our program might
+# report. Note the use of backslashes to continue long lines onto the
+# next. Also note the use of \n and \t for newlines and tabs
+#
+java.io.FileNotFoundException: \
+Error: File "{0}" not found\n\t\
+Error occurred at line {3} of file "{2}"\n\tat {4}
+
+java.io.UnsupportedEncodingException: \
+Error: Specified encoding not supported\n\t\
+Error occurred at line {3} of file "{2}"\n\tat {4,time} on {4,date}
+
+java.io.CharConversionException:\
+Error: Character conversion failure. Input data is not in specified format.
+
+# A generic resource. Display a message for any error or exception that
+# is not handled by a more specific resource.
+java.lang.Throwable:\
+Error: {1}: {0}\n\t\
+Error occurred at line {3} of file "{2}"\n\t{4,time,long} {4,date,long}
diff --git a/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java b/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java
new file mode 100644
index 0000000..23c5674
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.i18n;
+import java.text.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * A convenience class that can display a localized exception message
+ * depending on the class of the exception. It uses a MessageFormat,
+ * and passes five arguments that the localized message may include:
+ * {0}: the message included in the exception or error.
+ * {1}: the full class name of the exception or error.
+ * {2}: a guess at what file the exception was caused by.
+ * {3}: a line number in that file.
+ * {4}: the current date and time.
+ * Messages are looked up in a ResourceBundle with the basename
+ * "Errors", using a the full class name of the exception object as
+ * the resource name. If no resource is found for a given exception
+ * class, the superclasses are checked.
+ **/
+public class LocalizedError {
+ public static void display(Throwable error) {
+ ResourceBundle bundle;
+ // Try to get the resource bundle.
+ // If none, print the error in a non-localized way.
+ try { bundle = ResourceBundle.getBundle("Errors"); }
+ catch (MissingResourceException e) {
+ error.printStackTrace(System.err);
+ return;
+ }
+
+ // Look up a localized message resource in that bundle, using the
+ // classname of the error (or its superclasses) as the resource name.
+ // If no resource was found, display the error without localization.
+ String message = null;
+ Class c = error.getClass();
+ while((message == null) && (c != Object.class)) {
+ try { message = bundle.getString(c.getName()); }
+ catch (MissingResourceException e) { c = c.getSuperclass(); }
+ }
+ if (message == null) { error.printStackTrace(System.err); return; }
+
+ // Try to figure out the filename and line number of the
+ // exception. Output the error's stack trace into a string, and
+ // use the heuristic that the first line number that appears in
+ // the stack trace is after the first or second colon. We assume that
+ // this stack frame is the first one the programmer has any control
+ // over, and so report it as the location of the exception.
+ // Note that this is implementation-dependent and not robust...
+ String filename = "";
+ int linenum = 0;
+ try {
+ StringWriter sw = new StringWriter(); // Output stream to a string.
+ PrintWriter out=new PrintWriter(sw); // PrintWriter wrapper.
+ error.printStackTrace(out); // Print stacktrace.
+ String trace = sw.toString(); // Get it as a string.
+ int pos = trace.indexOf(':'); // Look for first colon.
+ if (error.getMessage() != null) // If the error has a message
+ pos = trace.indexOf(':', pos+1); // look for second colon.
+ int pos2 = trace.indexOf(')', pos); // Look for end of line #
+ linenum = Integer.parseInt(trace.substring(pos+1,pos2)); // line #
+ pos2 = trace.lastIndexOf('(', pos); // Back to start of filename.
+ filename = trace.substring(pos2+1, pos); // Get filename.
+ }
+ catch (Exception e) { ; } // Ignore exceptions.
+
+ // Set up an array of arguments to use with the message
+ String errmsg = error.getMessage();
+ Object[] args = {
+ ((errmsg!= null)?errmsg:""), error.getClass().getName(),
+ filename, new Integer(linenum), new Date()
+ };
+ // Finally, display the localized error message, using
+ // MessageFormat.format() to substitute the arguments into the message.
+ System.out.println(MessageFormat.format(message, args));
+ }
+
+ /**
+ * This is a simple test program that demonstrates the display() method.
+ * You can use it to generate and display a FileNotFoundException or an
+ * ArrayIndexOutOfBoundsException
+ **/
+ public static void main(String[] args) {
+ try { FileReader in = new FileReader(args[0]); }
+ catch(Exception e) { LocalizedError.display(e); }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus.properties
new file mode 100644
index 0000000..968b4b1
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/Menus.properties
@@ -0,0 +1,9 @@
+# The file Menus.properties is the default "Menus" resource bundle.
+# As an American programmer, I made my own locale the default.
+colors.label=Colors
+colors.red.label=Red
+colors.red.accelerator=alt R
+colors.green.label=Green
+colors.green.accelerator=alt G
+colors.blue.label=Blue
+colors.blue.accelerator=alt B
diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties
new file mode 100644
index 0000000..ba75f22
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties
@@ -0,0 +1,4 @@
+# This is the file Menus_en_GB.properties. It is the resource bundle for
+# British English. Note that it overrides only a single resource definition
+# and simply inherits the rest from the default (American) bundle.
+colors.label=Colours
diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties
new file mode 100644
index 0000000..7297b76
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties
@@ -0,0 +1,8 @@
+# This is the file Menus_fr.properties. It is the resource bundle for all
+# French-speaking locales. It overrides most, but not all, of the resources
+# in the default bundle.
+colors.label=Couleurs
+colors.red.label=Rouge
+colors.green.label=Vert
+colors.green.accelerator=control shift V
+colors.blue.label=Bleu
diff --git a/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java b/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java
new file mode 100644
index 0000000..68aa1eb
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.i18n;
+import java.text.*;
+import java.util.*;
+import java.io.*;
+
+/**
+ * A partial implementation of a hypothetical stock portfolio class.
+ * We use it only to demonstrate number and date internationalization.
+ **/
+public class Portfolio {
+ EquityPosition[] positions;
+ Date lastQuoteTime = new Date();
+
+ public Portfolio(EquityPosition[] positions, Date lastQuoteTime) {
+ this.positions = positions;
+ this.lastQuoteTime = lastQuoteTime;
+ }
+
+ public void print(PrintWriter out) {
+ // Obtain NumberFormat and DateFormat objects to format our data.
+ NumberFormat number = NumberFormat.getInstance();
+ NumberFormat price = NumberFormat.getCurrencyInstance();
+ NumberFormat percent = NumberFormat.getPercentInstance();
+ DateFormat shortdate = DateFormat.getDateInstance(DateFormat.MEDIUM);
+ DateFormat fulldate = DateFormat.getDateTimeInstance(DateFormat.LONG,
+ DateFormat.LONG);
+
+ // Print some introductory data.
+ out.println("Portfolio value at " +
+ fulldate.format(lastQuoteTime) + ":");
+ out.println("Symbol\tShares\tPurchased\tAt\t" +
+ "Quote\tChange");
+
+ // Display the table using the format() methods of the Format objects.
+ for(int i = 0; i < positions.length; i++) {
+ out.print(positions[i].name + "\t");
+ out.print(number.format(positions[i].shares) + "\t");
+ out.print(shortdate.format(positions[i].purchased) + "\t");
+ out.print(price.format(positions[i].bought) + "\t");
+ out.print(price.format(positions[i].current) + "\t");
+ double change =
+ (positions[i].current-positions[i].bought)/positions[i].bought;
+ out.println(percent.format(change));
+ out.flush();
+ }
+ }
+
+ static class EquityPosition {
+ String name; // Name of the stock.
+ int shares; // Number of shares held.
+ Date purchased; // When purchased.
+ double bought; // Purchase price per share
+ double current; // Current price per share
+ EquityPosition(String n, int s, Date when, double then, double now) {
+ name = n; shares = s; purchased = when;
+ bought = then; current = now;
+ }
+ }
+
+ /**
+ * This is a test program that demonstrates the class
+ **/
+ public static void main(String[] args) {
+ // This is the portfolio to display. Note we use a deprecated
+ // Date() constructor here for convenience. It represents the year
+ // offset from 1900, and will cause a warning message when compiling.
+ EquityPosition[] positions = new EquityPosition[] {
+ new EquityPosition("XXX", 400, new Date(100,1,3),11.90,13.00),
+ new EquityPosition("YYY", 1100, new Date(100,2,2),71.09,27.25),
+ new EquityPosition("ZZZ", 6000, new Date(100,4,17),23.37,89.12)
+ };
+
+ // Create the portfolio from these positions
+ Portfolio portfolio = new Portfolio(positions, new Date());
+
+ // Set the default locale using the language code and country code
+ // specified on the command line.
+ if (args.length == 2) Locale.setDefault(new Locale(args[0], args[1]));
+
+ // Now print the portfolio
+ portfolio.print(new PrintWriter(System.out));
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java b/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java
new file mode 100644
index 0000000..375d358
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.i18n;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.MissingResourceException;
+
+/** A convenience class to automatically create localized menu panes */
+public class SimpleMenu {
+ /** The convenience method that creates menu panes */
+ public static JMenu create(ResourceBundle bundle,
+ String menuname, String[] itemnames,
+ ActionListener listener)
+ {
+ // Get the menu title from the bundle. Use name as default label.
+ String menulabel;
+ try { menulabel = bundle.getString(menuname + ".label"); }
+ catch(MissingResourceException e) { menulabel = menuname; }
+
+ // Create the menu pane.
+ JMenu menu = new JMenu(menulabel);
+
+ // For each named item in the menu.
+ for(int i = 0; i < itemnames.length; i++) {
+ // Look up the label for the item, using name as default.
+ String itemlabel;
+ try {
+ itemlabel =
+ bundle.getString(menuname+"."+itemnames[i]+".label");
+ }
+ catch (MissingResourceException e) { itemlabel = itemnames[i]; }
+
+ JMenuItem item = new JMenuItem(itemlabel);
+
+ // Look up an accelerator for the menu item
+ try {
+ String acceleratorText =
+ bundle.getString(menuname+"."+itemnames[i]+".accelerator");
+ item.setAccelerator(KeyStroke.getKeyStroke(acceleratorText));
+ }
+ catch (MissingResourceException e) {}
+
+ // Register an action listener and command for the item.
+ if (listener != null) {
+ item.addActionListener(listener);
+ item.setActionCommand(itemnames[i]);
+ }
+
+ // Add the item to the menu.
+ menu.add(item);
+ }
+
+ // Return the automatically created localized menu.
+ return menu;
+ }
+
+ /** A simple test program for the above code */
+ public static void main(String[] args) {
+ // Get the locale: default, or specified on command-line
+ Locale locale;
+ if (args.length == 2) locale = new Locale(args[0], args[1]);
+ else locale = Locale.getDefault();
+
+ // Get the resource bundle for that Locale. This will throw an
+ // (unchecked) MissingResourceException if no bundle is found.
+ ResourceBundle bundle =
+ ResourceBundle.getBundle("com.davidflanagan.examples.i18n.Menus",
+ locale);
+
+ // Create a simple GUI window to display the menu with
+ final JFrame f = new JFrame("SimpleMenu: " + // Window title
+ locale.getDisplayName(Locale.getDefault()));
+ JMenuBar menubar = new JMenuBar(); // Create a menubar.
+ f.setJMenuBar(menubar); // Add menubar to window
+
+ // Define an action listener for that our menu will use.
+ ActionListener listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String s = e.getActionCommand();
+ Component c = f.getContentPane();
+ if (s.equals("red")) c.setBackground(Color.red);
+ else if (s.equals("green")) c.setBackground(Color.green);
+ else if (s.equals("blue")) c.setBackground(Color.blue);
+ }
+ };
+
+ // Now create a menu using our convenience routine with the resource
+ // bundle and action listener we've created
+ JMenu menu = SimpleMenu.create(bundle, "colors",
+ new String[] {"red", "green", "blue"},
+ listener);
+
+ // Finally add the menu to the GUI, and pop it up
+ menubar.add(menu); // Add the menu to the menubar
+ f.setSize(300, 150); // Set the window size.
+ f.setVisible(true); // Pop the window up.
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java b/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java
new file mode 100644
index 0000000..01eb46d
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.i18n;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * This program displays Unicode glyphs using user-specified fonts
+ * and font styles.
+ **/
+public class UnicodeDisplay extends JFrame implements ActionListener {
+ int page = 0;
+ UnicodePanel p;
+ JScrollBar b;
+ String fontfamily = "Serif";
+ int fontstyle = Font.PLAIN;
+
+ /**
+ * This constructor creates the frame, menubar, and scrollbar
+ * that work along with the UnicodePanel class, defined below
+ **/
+ public UnicodeDisplay(String name) {
+ super(name);
+ p = new UnicodePanel(); // Create the panel
+ p.setBase((char)(page * 0x100)); // Initialize it
+ getContentPane().add(p, "Center"); // Center it
+
+ // Create and set up a scrollbar, and put it on the right
+ b = new JScrollBar(Scrollbar.VERTICAL, 0, 1, 0, 0xFF);
+ b.setUnitIncrement(1);
+ b.setBlockIncrement(0x10);
+ b.addAdjustmentListener(new AdjustmentListener() {
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ page = e.getValue();
+ p.setBase((char)(page * 0x100));
+ }
+ });
+ getContentPane().add(b, "East");
+
+ // Set things up so we respond to window close requests
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { System.exit(0); }
+ });
+
+ // Handle Page Up and Page Down and the up and down arrow keys
+ this.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ int code = e.getKeyCode();
+ int oldpage = page;
+ if ((code == KeyEvent.VK_PAGE_UP) ||
+ (code == KeyEvent.VK_UP)) {
+ if (e.isShiftDown()) page -= 0x10;
+ else page -= 1;
+ if (page < 0) page = 0;
+ }
+ else if ((code == KeyEvent.VK_PAGE_DOWN) ||
+ (code == KeyEvent.VK_DOWN)) {
+ if (e.isShiftDown()) page += 0x10;
+ else page += 1;
+ if (page > 0xff) page = 0xff;
+ }
+ if (page != oldpage) { // if anything has changed...
+ p.setBase((char) (page * 0x100)); // update the display
+ b.setValue(page); // and update scrollbar to match
+ }
+ }
+ });
+
+ // Set up a menu system to change fonts. Use a convenience method.
+ JMenuBar menubar = new JMenuBar();
+ this.setJMenuBar(menubar);
+ menubar.add(makemenu("Font Family",
+ new String[] {"Serif", "SansSerif", "Monospaced"},
+ this));
+ menubar.add(makemenu("Font Style",
+ new String[]{
+ "Plain","Italic","Bold","BoldItalic"
+ }, this));
+ }
+
+ /** This method handles the items in the menubars */
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("Serif")) fontfamily = "Serif";
+ else if (cmd.equals("SansSerif")) fontfamily = "SansSerif";
+ else if (cmd.equals("Monospaced")) fontfamily = "Monospaced";
+ else if (cmd.equals("Plain")) fontstyle = Font.PLAIN;
+ else if (cmd.equals("Italic")) fontstyle = Font.ITALIC;
+ else if (cmd.equals("Bold")) fontstyle = Font.BOLD;
+ else if (cmd.equals("BoldItalic")) fontstyle = Font.BOLD + Font.ITALIC;
+ p.setFont(fontfamily, fontstyle);
+ }
+
+ /** A convenience method to create a Menu from an array of items */
+ private JMenu makemenu(String name, String[] itemnames,
+ ActionListener listener)
+ {
+ JMenu m = new JMenu(name);
+ for(int i = 0; i < itemnames.length; i++) {
+ JMenuItem item = new JMenuItem(itemnames[i]);
+ item.addActionListener(listener);
+ item.setActionCommand(itemnames[i]); // okay here, though
+ m.add(item);
+ }
+ return m;
+ }
+
+ /** The main() program just create a window, packs it, and shows it */
+ public static void main(String[] args) {
+ UnicodeDisplay f = new UnicodeDisplay("Unicode Displayer");
+ f.pack();
+ f.show();
+ }
+
+ /**
+ * This nested class is the one that displays one "page" of Unicode
+ * glyphs at a time. Each "page" is 256 characters, arranged into 16
+ * rows of 16 columns each.
+ **/
+ public static class UnicodePanel extends JComponent {
+ protected char base; // What character we start the display at
+ protected Font font = new Font("serif", Font.PLAIN, 18);
+ protected Font headingfont = new Font("monospaced", Font.BOLD, 18);
+ static final int lineheight = 25;
+ static final int charspacing = 20;
+ static final int x0 = 65;
+ static final int y0 = 40;
+
+ /** Specify where to begin displaying, and re-display */
+ public void setBase(char base) { this.base = base; repaint(); }
+
+ /** Set a new font name or style, and redisplay */
+ public void setFont(String family, int style) {
+ this.font = new Font(family, style, 18);
+ repaint();
+ }
+
+ /**
+ * The paintComponent() method actually draws the page of glyphs
+ **/
+ public void paintComponent(Graphics g) {
+ int start = (int)base & 0xFFF0; // Start on a 16-character boundary
+
+ // Draw the headings in a special font
+ g.setFont(headingfont);
+
+ // Draw 0..F on top
+ for(int i=0; i < 16; i++) {
+ String s = Integer.toString(i, 16);
+ g.drawString(s, x0 + i*charspacing, y0-20);
+ }
+
+ // Draw column down left.
+ for(int i = 0; i < 16; i++) {
+ int j = start + i*16;
+ String s = Integer.toString(j, 16);
+ g.drawString(s, 10, y0+i*lineheight);
+ }
+
+ // Now draw the characters
+ g.setFont(font);
+ char[] c = new char[1];
+ for(int i = 0; i < 16; i++) {
+ for(int j = 0; j < 16; j++) {
+ c[0] = (char)(start + j*16 + i);
+ g.drawChars(c, 0, 1, x0 + i*charspacing, y0+j*lineheight);
+ }
+ }
+ }
+
+ /** Custom components like this one should always have this method */
+ public Dimension getPreferredSize() {
+ return new Dimension(x0 + 16*charspacing,
+ y0 + 16*lineheight);
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/Compress.java b/src/main/java/com/davidflanagan/examples/io/Compress.java
new file mode 100644
index 0000000..e67f472
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/Compress.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.io.*;
+import java.util.zip.*;
+
+/**
+ * This class defines two static methods for gzipping files and zipping
+ * directories. It also defines a demonstration program as a nested class.
+ **/
+public class Compress {
+ /** Gzip the contents of the from file and save in the to file. */
+ public static void gzipFile(String from, String to) throws IOException {
+ // Create stream to read from the from file
+ FileInputStream in = new FileInputStream(from);
+ // Create stream to compress data and write it to the to file.
+ GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(to));
+ // Copy bytes from one stream to the other
+ byte[] buffer = new byte[4096];
+ int bytes_read;
+ while((bytes_read = in.read(buffer)) != -1)
+ out.write(buffer, 0, bytes_read);
+ // And close the streams
+ in.close();
+ out.close();
+ }
+
+ /** Zip the contents of the directory, and save it in the zipfile */
+ public static void zipDirectory(String dir, String zipfile)
+ throws IOException, IllegalArgumentException {
+ // Check that the directory is a directory, and get its contents
+ File d = new File(dir);
+ if (!d.isDirectory())
+ throw new IllegalArgumentException("Compress: not a directory: " +
+ dir);
+ String[] entries = d.list();
+ byte[] buffer = new byte[4096]; // Create a buffer for copying
+ int bytes_read;
+
+ // Create a stream to compress data and write it to the zipfile
+ ZipOutputStream out =
+ new ZipOutputStream(new FileOutputStream(zipfile));
+
+ // Loop through all entries in the directory
+ for(int i = 0; i < entries.length; i++) {
+ File f = new File(d, entries[i]);
+ if (f.isDirectory()) continue; // Don't zip sub-directories
+ FileInputStream in = new FileInputStream(f); // Stream to read file
+ ZipEntry entry = new ZipEntry(f.getPath()); // Make a ZipEntry
+ out.putNextEntry(entry); // Store entry
+ while((bytes_read = in.read(buffer)) != -1) // Copy bytes
+ out.write(buffer, 0, bytes_read);
+ in.close(); // Close input stream
+ }
+ // When we're done with the whole loop, close the output stream
+ out.close();
+ }
+
+ /**
+ * This nested class is a test program that demonstrates the use of the
+ * static methods defined above.
+ **/
+ public static class Test {
+ /**
+ * Compress a specified file or directory. If no destination name is
+ * specified, append .gz to a file name or .zip to a directory name
+ **/
+ public static void main(String args[]) throws IOException {
+ if ((args.length != 1)&& (args.length != 2)) { // check arguments
+ System.err.println("Usage: java Compress$Test []");
+ System.exit(0);
+ }
+ String from = args[0], to;
+ File f = new File(from);
+ boolean directory = f.isDirectory(); // Is it a file or directory?
+ if (args.length == 2) to = args[1];
+ else { // If destination not specified
+ if (directory) to = from + ".zip"; // use a .zip suffix
+ else to = from + ".gz"; // or a .gz suffix
+ }
+
+ if ((new File(to)).exists()) { // Make sure not to overwrite
+ System.err.println("Compress: won't overwrite existing file: "+
+ to);
+ System.exit(0);
+ }
+
+ // Finally, call one of the methods defined above to do the work.
+ if (directory) Compress.zipDirectory(from, to);
+ else Compress.gzipFile(from, to);
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/Delete.java b/src/main/java/com/davidflanagan/examples/io/Delete.java
new file mode 100644
index 0000000..777ddfd
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/Delete.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.io.*;
+
+/**
+ * This class is a static method delete() and a standalone program that
+ * deletes a specified file or directory.
+ **/
+public class Delete {
+ /**
+ * This is the main() method of the standalone program. After checking
+ * it arguments, it invokes the Delete.delete() method to do the deletion
+ **/
+ public static void main(String[] args) {
+ if (args.length != 1) { // Check command-line arguments
+ System.err.println("Usage: java Delete ");
+ System.exit(0);
+ }
+ // Call delete() and display any error messages it throws.
+ try { delete(args[0]); }
+ catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ /**
+ * The static method that does the deletion. Invoked by main(), and
+ * designed for use by other programs as well. It first makes sure that
+ * the specified file or directory is deleteable before attempting to
+ * delete it. If there is a problem, it throws an
+ * IllegalArgumentException.
+ **/
+ public static void delete(String filename) {
+ // Create a File object to represent the filename
+ File f = new File(filename);
+
+ // Make sure the file or directory exists and isn't write protected
+ if (!f.exists()) fail("Delete: no such file or directory: " +filename);
+ if (!f.canWrite()) fail("Delete: write protected: " + filename);
+
+ // If it is a directory, make sure it is empty
+ if (f.isDirectory()) {
+ String[] files = f.list();
+ if (files.length > 0)
+ fail("Delete: directory not empty: " + filename);
+ }
+
+ // If we passed all the tests, then attempt to delete it
+ boolean success = f.delete();
+
+ // And throw an exception if it didn't work for some (unknown) reason.
+ // For example, because of a bug with Java 1.1.1 on Linux,
+ // directory deletion always fails
+ if (!success) fail("Delete: deletion failed");
+ }
+
+ /** A convenience method to throw an exception */
+ protected static void fail(String msg) throws IllegalArgumentException {
+ throw new IllegalArgumentException(msg);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/FileCopy.java b/src/main/java/com/davidflanagan/examples/io/FileCopy.java
new file mode 100644
index 0000000..bcdda97
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/FileCopy.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.io.*;
+
+/**
+ * This class is a standalone program to copy a file, and also defines a
+ * static copy() method that other programs can use to copy files.
+ **/
+public class FileCopy {
+ /** The main() method of the standalone program. Calls copy(). */
+ public static void main(String[] args) {
+ if (args.length != 2) // Check arguments
+ System.err.println("Usage: java FileCopy ");
+ else {
+ // Call copy() to do the copy; display any error messages
+ try { copy(args[0], args[1]); }
+ catch (IOException e) { System.err.println(e.getMessage()); }
+ }
+ }
+
+ /**
+ * The static method that actually performs the file copy.
+ * Before copying the file, however, it performs a lot of tests to make
+ * sure everything is as it should be.
+ */
+ public static void copy(String from_name, String to_name)
+ throws IOException
+ {
+ File from_file = new File(from_name); // Get File objects from Strings
+ File to_file = new File(to_name);
+
+ // First make sure the source file exists, is a file, and is readable.
+ if (!from_file.exists())
+ abort("no such source file: " + from_name);
+ if (!from_file.isFile())
+ abort("can't copy directory: " + from_name);
+ if (!from_file.canRead())
+ abort("source file is unreadable: " + from_name);
+
+ // If the destination is a directory, use the source file name
+ // as the destination file name
+ if (to_file.isDirectory())
+ to_file = new File(to_file, from_file.getName());
+
+ // If the destination exists, make sure it is a writeable file
+ // and ask before overwriting it. If the destination doesn't
+ // exist, make sure the directory exists and is writeable.
+ if (to_file.exists()) {
+ if (!to_file.canWrite())
+ abort("destination file is unwriteable: " + to_name);
+ // Ask whether to overwrite it
+ System.out.print("Overwrite existing file " + to_file.getName() +
+ "? (Y/N): ");
+ System.out.flush();
+ // Get the user's response.
+ BufferedReader in=
+ new BufferedReader(new InputStreamReader(System.in));
+ String response = in.readLine();
+ // Check the response. If not a Yes, abort the copy.
+ if (!response.equals("Y") && !response.equals("y"))
+ abort("existing file was not overwritten.");
+ }
+ else {
+ // If file doesn't exist, check if directory exists and is
+ // writeable. If getParent() returns null, then the directory is
+ // the current dir. so look up the user.dir system property to
+ // find out what that is.
+ String parent = to_file.getParent(); // The destination directory
+ if (parent == null) // If none, use the current directory
+ parent = System.getProperty("user.dir");
+ File dir = new File(parent); // Convert it to a file.
+ if (!dir.exists())
+ abort("destination directory doesn't exist: "+parent);
+ if (dir.isFile())
+ abort("destination is not a directory: " + parent);
+ if (!dir.canWrite())
+ abort("destination directory is unwriteable: " + parent);
+ }
+
+ // If we've gotten this far, then everything is okay.
+ // So we copy the file, a buffer of bytes at a time.
+ FileInputStream from = null; // Stream to read from source
+ FileOutputStream to = null; // Stream to write to destination
+ try {
+ from = new FileInputStream(from_file); // Create input stream
+ to = new FileOutputStream(to_file); // Create output stream
+ byte[] buffer = new byte[4096]; // To hold file contents
+ int bytes_read; // How many bytes in buffer
+
+ // Read a chunk of bytes into the buffer, then write them out,
+ // looping until we reach the end of the file (when read() returns
+ // -1). Note the combination of assignment and comparison in this
+ // while loop. This is a common I/O programming idiom.
+ while((bytes_read = from.read(buffer)) != -1) // Read until EOF
+ to.write(buffer, 0, bytes_read); // write
+ }
+ // Always close the streams, even if exceptions were thrown
+ finally {
+ if (from != null) try { from.close(); } catch (IOException e) { ; }
+ if (to != null) try { to.close(); } catch (IOException e) { ; }
+ }
+ }
+
+ /** A convenience method to throw an exception */
+ private static void abort(String msg) throws IOException {
+ throw new IOException("FileCopy: " + msg);
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/FileLister.java b/src/main/java/com/davidflanagan/examples/io/FileLister.java
new file mode 100644
index 0000000..b0aea5f
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/FileLister.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * This class creates and displays a window containing a list of
+ * files and sub-directories in a specified directory. Clicking on an
+ * entry in the list displays more information about it. Double-clicking
+ * on an entry displays it, if a file, or lists it if a directory.
+ * An optionally-specified FilenameFilter filters the displayed list.
+ **/
+public class FileLister extends Frame implements ActionListener, ItemListener {
+ private List list; // To display the directory contents in
+ private TextField details; // To display detail info in.
+ private Panel buttons; // Holds the buttons
+ private Button up, close; // The Up and Close buttons
+ private File currentDir; // The directory currently listed
+ private FilenameFilter filter; // An optional filter for the directory
+ private String[] files; // The directory contents
+ private DateFormat dateFormatter = // To display dates and time correctly
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+
+ /**
+ * Constructor: create the GUI, and list the initial directory.
+ **/
+ public FileLister(String directory, FilenameFilter filter) {
+ super("File Lister"); // Create the window
+ this.filter = filter; // Save the filter, if any
+
+ // Destroy the window when the user requests it
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { dispose(); }
+ });
+
+ list = new List(12, false); // Set up the list
+ list.setFont(new Font("MonoSpaced", Font.PLAIN, 14));
+ list.addActionListener(this);
+ list.addItemListener(this);
+
+ details = new TextField(); // Set up the details area
+ details.setFont(new Font("MonoSpaced", Font.PLAIN, 12));
+ details.setEditable(false);
+
+ buttons = new Panel(); // Set up the button box
+ buttons.setLayout(new FlowLayout(FlowLayout.RIGHT, 15, 5));
+ buttons.setFont(new Font("SansSerif", Font.BOLD, 14));
+
+ up = new Button("Up a Directory"); // Set up the two buttons
+ close = new Button("Close");
+ up.addActionListener(this);
+ close.addActionListener(this);
+
+ buttons.add(up); // Add buttons to button box
+ buttons.add(close);
+
+ this.add(list, "Center"); // Add stuff to the window
+ this.add(details, "North");
+ this.add(buttons, "South");
+ this.setSize(500, 350);
+
+ listDirectory(directory); // And now list initial directory.
+ }
+
+ /**
+ * This method uses the list() method to get all entries in a directory
+ * and then displays them in the List component.
+ **/
+ public void listDirectory(String directory) {
+ // Convert the string to a File object, and check that the dir exists
+ File dir = new File(directory);
+ if (!dir.isDirectory())
+ throw new IllegalArgumentException("FileLister: no such directory");
+
+ // Get the (filtered) directory entries
+ files = dir.list(filter);
+
+ // Sort the list of filenames. Prior to Java 1.2, you could use
+ // com.davidflanagan.examples.classes.Sorter.sort() to sort instead.
+ java.util.Arrays.sort(files);
+
+ // Remove any old entries in the list, and add the new ones
+ list.removeAll();
+ list.add("[Up to Parent Directory]"); // A special case entry
+ for(int i = 0; i < files.length; i++) list.add(files[i]);
+
+ // Display directory name in window titlebar and in the details box
+ this.setTitle(directory);
+ details.setText(directory);
+
+ // Remember this directory for later.
+ currentDir = dir;
+ }
+
+ /**
+ * This ItemListener method uses various File methods to obtain information
+ * about a file or directory. Then it displays that info.
+ **/
+ public void itemStateChanged(ItemEvent e) {
+ int i = list.getSelectedIndex() - 1; // minus 1 for Up To Parent entry
+ if (i < 0) return;
+ String filename = files[i]; // Get the selected entry
+ File f = new File(currentDir, filename); // Convert to a File
+ if (!f.exists()) // Confirm that it exists
+ throw new IllegalArgumentException("FileLister: " +
+ "no such file or directory");
+
+ // Get the details about the file or directory, concatenate to a string
+ String info = filename;
+ if (f.isDirectory()) info += File.separator;
+ info += " " + f.length() + " bytes ";
+ info += dateFormatter.format(new Date(f.lastModified()));
+ if (f.canRead()) info += " Read";
+ if (f.canWrite()) info += " Write";
+
+ // And display the details string
+ details.setText(info);
+ }
+
+ /**
+ * This ActionListener method is invoked when the user double-clicks on an
+ * entry or clicks on one of the buttons. If they double-click on a file,
+ * create a FileViewer to display that file. If they double-click on a
+ * directory, call the listDirectory() method to display that directory
+ **/
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == close) this.dispose();
+ else if (e.getSource() == up) { up(); }
+ else if (e.getSource() == list) { // Double click on an item
+ int i = list.getSelectedIndex(); // Check which item
+ if (i == 0) up(); // Handle first Up To Parent item
+ else { // Otherwise, get filename
+ String name = files[i-1];
+ File f = new File(currentDir, name); // Convert to a File
+ String fullname = f.getAbsolutePath();
+ if (f.isDirectory()) listDirectory(fullname); // List dir
+ else new FileViewer(fullname).show(); // display file
+ }
+ }
+ }
+
+ /** A convenience method to display the contents of the parent directory */
+ protected void up() {
+ String parent = currentDir.getParent();
+ if (parent == null) return;
+ listDirectory(parent);
+ }
+
+ /** A convenience method used by main() */
+ public static void usage() {
+ System.out.println("Usage: java FileLister [directory_name] " +
+ "[-e file_extension]");
+ System.exit(0);
+ }
+
+ /**
+ * A main() method so FileLister can be run standalone.
+ * Parse command line arguments and create the FileLister object.
+ * If an extension is specified, create a FilenameFilter for it.
+ * If no directory is specified, use the current directory.
+ **/
+ public static void main(String args[]) throws IOException {
+ FileLister f;
+ FilenameFilter filter = null; // The filter, if any
+ String directory = null; // The specified dir, or the current dir
+
+ // Loop through args array, parsing arguments
+ for(int i = 0; i < args.length; i++) {
+ if (args[i].equals("-e")) {
+ if (++i >= args.length) usage();
+ final String suffix = args[i]; // final for anon. class below
+
+ // This class is a simple FilenameFilter. It defines the
+ // accept() method required to determine whether a specified
+ // file should be listed. A file will be listed if its name
+ // ends with the specified extension, or if it is a directory.
+ filter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ if (name.endsWith(suffix)) return true;
+ else return (new File(dir, name)).isDirectory();
+ }
+ };
+ }
+ else {
+ if (directory != null) usage(); // If already specified, fail.
+ else directory = args[i];
+ }
+ }
+
+ // if no directory specified, use the current directory
+ if (directory == null) directory = System.getProperty("user.dir");
+ // Create the FileLister object, with directory and filter specified.
+ f = new FileLister(directory, filter);
+ // Arrange for the application to exit when the window is closed
+ f.addWindowListener(new WindowAdapter() {
+ public void windowClosed(WindowEvent e) { System.exit(0); }
+ });
+ // Finally, pop the window up up.
+ f.show();
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/FileViewer.java b/src/main/java/com/davidflanagan/examples/io/FileViewer.java
new file mode 100644
index 0000000..5f4aa53
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/FileViewer.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+
+/**
+ * This class creates and displays a window containing a TextArea,
+ * in which the contents of a text file are displayed.
+ **/
+public class FileViewer extends Frame implements ActionListener {
+ String directory; // The default directory to display in the FileDialog
+ TextArea textarea; // The area to display the file contents into
+
+ /** Convenience constructor: file viewer starts out blank */
+ public FileViewer() { this(null, null); }
+ /** Convenience constructor: display file from current directory */
+ public FileViewer(String filename) { this(null, filename); }
+
+ /**
+ * The real constructor. Create a FileViewer object to display the
+ * specified file from the specified directory
+ **/
+ public FileViewer(String directory, String filename) {
+ super(); // Create the frame
+
+ // Destroy the window when the user requests it
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) { dispose(); }
+ });
+
+ // Create a TextArea to display the contents of the file in
+ textarea = new TextArea("", 24, 80);
+ textarea.setFont(new Font("MonoSpaced", Font.PLAIN, 12));
+ textarea.setEditable(false);
+ this.add("Center", textarea);
+
+ // Create a bottom panel to hold a couple of buttons in
+ Panel p = new Panel();
+ p.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 5));
+ this.add(p, "South");
+
+ // Create the buttons and arrange to handle button clicks
+ Font font = new Font("SansSerif", Font.BOLD, 14);
+ Button openfile = new Button("Open File");
+ Button close = new Button("Close");
+ openfile.addActionListener(this);
+ openfile.setActionCommand("open");
+ openfile.setFont(font);
+ close.addActionListener(this);
+ close.setActionCommand("close");
+ close.setFont(font);
+ p.add(openfile);
+ p.add(close);
+
+ this.pack();
+
+ // Figure out the directory, from filename or current dir, if necessary
+ if (directory == null) {
+ File f;
+ if ((filename != null)&& (f = new File(filename)).isAbsolute()) {
+ directory = f.getParent();
+ filename = f.getName();
+ }
+ else directory = System.getProperty("user.dir");
+ }
+
+ this.directory = directory; // Remember the directory, for FileDialog
+ setFile(directory, filename); // Now load and display the file
+ }
+
+ /**
+ * Load and display the specified file from the specified directory
+ **/
+ public void setFile(String directory, String filename) {
+ if ((filename == null) || (filename.length() == 0)) return;
+ File f;
+ FileReader in = null;
+ // Read and display the file contents. Since we're reading text, we
+ // use a FileReader instead of a FileInputStream.
+ try {
+ f = new File(directory, filename); // Create a file object
+ in = new FileReader(f); // And a char stream to read it
+ char[] buffer = new char[4096]; // Read 4K characters at a time
+ int len; // How many chars read each time
+ textarea.setText(""); // Clear the text area
+ while((len = in.read(buffer)) != -1) { // Read a batch of chars
+ String s = new String(buffer, 0, len); // Convert to a string
+ textarea.append(s); // And display them
+ }
+ this.setTitle("FileViewer: " + filename); // Set the window title
+ textarea.setCaretPosition(0); // Go to start of file
+ }
+ // Display messages if something goes wrong
+ catch (IOException e) {
+ textarea.setText(e.getClass().getName() + ": " + e.getMessage());
+ this.setTitle("FileViewer: " + filename + ": I/O Exception");
+ }
+ // Always be sure to close the input stream!
+ finally { try { if (in!=null) in.close(); } catch (IOException e) {} }
+ }
+
+ /**
+ * Handle button clicks
+ **/
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("open")) { // If user clicked "Open" button
+ // Create a file dialog box to prompt for a new file to display
+ FileDialog f = new FileDialog(this, "Open File", FileDialog.LOAD);
+ f.setDirectory(directory); // Set the default directory
+
+ // Display the dialog and wait for the user's response
+ f.show();
+
+ directory = f.getDirectory(); // Remember new default directory
+ setFile(directory, f.getFile()); // Load and display selection
+ f.dispose(); // Get rid of the dialog box
+ }
+ else if (cmd.equals("close")) // If user clicked "Close" button
+ this.dispose(); // then close the window
+ }
+
+ /**
+ * The FileViewer can be used by other classes, or it can be
+ * used standalone with this main() method.
+ **/
+ static public void main(String[] args) throws IOException {
+ // Create a FileViewer object
+ Frame f = new FileViewer((args.length == 1)?args[0]:null);
+ // Arrange to exit when the FileViewer window closes
+ f.addWindowListener(new WindowAdapter() {
+ public void windowClosed(WindowEvent e) { System.exit(0); }
+ });
+ // And pop the window up
+ f.show();
+ }
+}
+
+
diff --git a/src/main/java/com/davidflanagan/examples/io/GrepReader.java b/src/main/java/com/davidflanagan/examples/io/GrepReader.java
new file mode 100644
index 0000000..875424e
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/GrepReader.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.io.*;
+
+/**
+ * This class is a BufferedReader that filters out all lines that
+ * do not contain the specified pattern.
+ **/
+public class GrepReader extends BufferedReader {
+ String pattern; // The string we are going to be matching.
+
+ /** Pass the stream to our superclass, and remember the pattern ourself */
+ public GrepReader(Reader in, String pattern) {
+ super(in);
+ this.pattern = pattern;
+ }
+
+ /**
+ * This is the filter: call our superclass's readLine() to get the
+ * actual lines, but only return lines that contain the pattern.
+ * When the superclass readLine() returns null (EOF), we return null.
+ **/
+ public final String readLine() throws IOException {
+ String line;
+ do { line = super.readLine(); }
+ while ((line != null)&& line.indexOf(pattern) == -1);
+ return line;
+ }
+
+ /**
+ * This class demonstrates the use of the GrepReader class.
+ * It prints the lines of a file that contain a specified substring.
+ **/
+ public static class Test {
+ public static void main(String args[]) {
+ try {
+ if (args.length != 2)
+ throw new IllegalArgumentException("Wrong number of args");
+ GrepReader in=new GrepReader(new FileReader(args[1]), args[0]);
+ String line;
+ while((line = in.readLine()) != null) System.out.println(line);
+ in.close();
+ }
+ catch (Exception e) {
+ System.err.println(e);
+ System.out.println("Usage: java GrepReader$Test" +
+ " ");
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java b/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java
new file mode 100644
index 0000000..c6c9805
--- /dev/null
+++ b/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2000 David Flanagan. All rights reserved.
+ * This code is from the book Java Examples in a Nutshell, 2nd Edition.
+ * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
+ * You may study, use, and modify it for any non-commercial purpose.
+ * You may distribute it non-commercially as long as you retain this notice.
+ * For a commercial use license, or to purchase the book (recommended),
+ * visit http://www.davidflanagan.com/javaexamples2.
+ */
+package com.davidflanagan.examples.io;
+import java.io.*;
+import java.net.*;
+import java.applet.Applet;
+import netscape.javascript.JSObject; // A special class we need
+
+/**
+ * An output stream that sends HTML text to a newly created web browser window.
+ * It relies on the netscape.javascript.JSObject class to send JavaScript
+ * commands to the Web browser, and only works for applets running in
+ * the Netscape Navigator Web browser.
+ **/
+public class HTMLWriter extends Writer {
+ JSObject main_window; // the initial browser window
+ JSObject window; // the new window we create
+ JSObject document; // the document of that new window
+ static int window_num = 0; // used to give each new window a unique name
+
+ /**
+ * When you create a new HTMLWriter, it pops up a new, blank, Web browser
+ * window to display the output in. You must specify the applet
+ * (this specifies the main browser window) and the desired size
+ * for the new window.
+ **/
+ public HTMLWriter(Applet applet, int width, int height) {
+ // Verify that we can find the JSObject class we need. Warn if not.
+ try { Class c = Class.forName("netscape.javascript.JSObject"); }
+ catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError("HTMLWriter requires " +
+ "Netscape Navigator 4.0 or higher " +
+ "or a browser that supports LiveConnect technology");
+ }
+
+ // Get a reference to the main browser window from the applet.
+ main_window = JSObject.getWindow(applet);
+
+ // Create a new window to display output in. This command sends a
+ // string of JavaScript to the web browser
+ window = (JSObject)
+ main_window.eval("self.open(''," +
+ "'HTMLWriter" + window_num++ + "'," +
+ "'menubar,status,resizable,scrollbars," +
+ "width=" + width + ",height=" + height + "')");
+
+ // Obtain the Document object of this new window, and open it.
+ document = (JSObject) window.getMember("document");
+ document.call("open", null);
+ }
+
+ /**
+ * This is the write() method required for all Writer subclasses.
+ * Writer defines all its other write() methods in terms of this one.
+ **/
+ public void write(char[] buf, int offset, int length) {
+ // If no window or document, do nothing. This occurs if the stream
+ // has been closed, or if the code is not running in Navigator.
+ if ((window == null) || (document == null)) return;
+ // If the window has been closed by the user, do nothing
+ if (((Boolean)window.getMember("closed")).booleanValue()) return;
+ // Otherwise, create a string from the specified bytes
+ String s = new String(buf, offset, length);
+ // And pass it to the JS document.write() method to output the HTML
+ document.call("write", new String[] { s });
+ }
+
+ /**
+ * There is no general way to force JavaScript to flush all pending output,
+ * so this method does nothing. To flush, output a
tag or some other
+ * HTML tag that forces a line break in the output.
+ **/
+ public void flush() {}
+
+ /**
+ * When the stream is closed, close the JavaScript Document object
+ * (But don't close the window yet.)
+ **/
+ public void close() { document.call("close", null); document = null; }
+
+ /**
+ * If the browser window is still open, close it.
+ * This method is unique to HTMLWriter.
+ **/
+ public void closeWindow() {
+ if (document != null) close();
+ if (!((Boolean)window.getMember("closed")).booleanValue())
+ window.call("close", null);
+ window = null;
+ }
+
+ /** A finalizer method to close the window in case we forget. */
+ public void finalize() { closeWindow(); }
+
+ /**
+ * This nested class is an applet that demonstrates the use of HTMLWriter.
+ * It reads the contents of the URL specified in its url parameter and
+ * writes them out to an HTMLWriter stream. It will only work in
+ * Netscape 4.0 or later. It requires an