Bruce Eckel’s Thinking in Java | Contents | Prev | Next |
1.1
has also added some important new functionality, including focus traversal,
desktop color access, printing “inside the sandbox,” and the
beginnings of clipboard support.
traversal is quite easy, since it’s transparently present in the AWT
library components and you don’t have to do anything to make it work. If
you make your own components and want them to handle focus traversal, you
override isFocusTraversable( )
to return
true.
If you want to capture the keyboard focus on a mouse click, you catch the mouse
down event and call requestFocus( ).
Desktop
colors
desktop
colors provide a way for you to know what the various color choices are on the
current user’s desktop. This way, you can use those colors in your
program if you desire. The colors are automatically initialized and placed in
static
members of class
SystemColor,
so all you need to do is read the member you’re interested in. The names
are intentionally self-explanatory:
desktop,
activeCaption,
activeCaptionText,
activeCaptionBorder,
inactiveCaption,
inactiveCaptionText,
inactiveCaptionBorder,
window,
windowBorder,
windowText,
menu,
menuText,
text,
textText,
textHighlight,
textHighlightText,
textInactiveText,
control,
controlText,
controlHighlight,
controlLtHighlight,
controlShadow,
controlDkShadow,
scrollbar,
info
(for
help), and
infoText
(for
help text).
Printing
there isn’t much that’s automatic with printing. Instead you must
go through a number of mechanical, non-OO steps in order to print. Printing a
component graphically can be slightly more automatic: by default, the print( )
method calls paint( )
to do its work. There are times when this is satisfactory, but if you want to
do anything more specialized you must know that you’re printing so you
can in particular find out the page dimensions.
following example demonstrates the printing of both text and graphics, and the
different approaches you can use for printing graphics. In addition, it tests
the printing support:
//: PrintDemo.java // Printing with Java 1.1 import java.awt.*; import java.awt.event.*; public class PrintDemo extends Frame { Button printText = new Button("Print Text"), printGraphics = new Button("Print Graphics"); TextField ringNum = new TextField(3); Choice faces = new Choice(); Graphics g = null; Plot plot = new Plot3(); // Try different plots Toolkit tk = Toolkit.getDefaultToolkit(); public PrintDemo() { ringNum.setText("3"); ringNum.addTextListener(new RingL()); Panel p = new Panel(); p.setLayout(new FlowLayout()); printText.addActionListener(new TBL()); p.add(printText); p.add(new Label("Font:")); p.add(faces); printGraphics.addActionListener(new GBL()); p.add(printGraphics); p.add(new Label("Rings:")); p.add(ringNum); setLayout(new BorderLayout()); add(p, BorderLayout.NORTH); add(plot, BorderLayout.CENTER); String[] fontList = tk.getFontList(); for(int i = 0; i < fontList.length; i++) faces.add(fontList[i]); faces.select("Serif"); } class PrintData { public PrintJob pj; public int pageWidth, pageHeight; PrintData(String jobName) { pj = getToolkit().getPrintJob( PrintDemo.this, jobName, null); if(pj != null) { pageWidth = pj.getPageDimension().width; pageHeight= pj.getPageDimension().height; g = pj.getGraphics(); } } void end() { pj.end(); } } class ChangeFont { private int stringHeight; ChangeFont(String face, int style,int point){ if(g != null) { g.setFont(new Font(face, style, point)); stringHeight = g.getFontMetrics().getHeight(); } } int stringWidth(String s) { return g.getFontMetrics().stringWidth(s); } int stringHeight() { return stringHeight; } } class TBL implements ActionListener { public void actionPerformed(ActionEvent e) { PrintData pd = new PrintData("Print Text Test"); // Null means print job canceled: if(pd == null) return; String s = "PrintDemo"; ChangeFont cf = new ChangeFont( faces.getSelectedItem(), Font.ITALIC,72); g.drawString(s, (pd.pageWidth - cf.stringWidth(s)) / 2, (pd.pageHeight - cf.stringHeight()) / 3); s = "A smaller point size"; cf = new ChangeFont( faces.getSelectedItem(), Font.BOLD, 48); g.drawString(s, (pd.pageWidth - cf.stringWidth(s)) / 2, (int)((pd.pageHeight - cf.stringHeight())/1.5)); g.dispose(); pd.end(); } } class GBL implements ActionListener { public void actionPerformed(ActionEvent e) { PrintData pd = new PrintData("Print Graphics Test"); if(pd == null) return; plot.print(g); g.dispose(); pd.end(); } } class RingL implements TextListener { public void textValueChanged(TextEvent e) { int i = 1; try { i = Integer.parseInt(ringNum.getText()); } catch(NumberFormatException ex) { i = 1; } plot.rings = i; plot.repaint(); } } public static void main(String[] args) { Frame pdemo = new PrintDemo(); pdemo.setTitle("Print Demo"); pdemo.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); pdemo.setSize(500, 500); pdemo.setVisible(true); } } class Plot extends Canvas { public int rings = 3; } class Plot1 extends Plot { // Default print() calls paint(): public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } class Plot2 extends Plot { // To fit the picture to the page, you must // know whether you're printing or painting: public void paint(Graphics g) { int w, h; if(g instanceof PrintGraphics) { PrintJob pj = ((PrintGraphics)g).getPrintJob(); w = pj.getPageDimension().width; h = pj.getPageDimension().height; } else { w = getSize().width; h = getSize().height; } int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } class Plot3 extends Plot { // Somewhat better. Separate // printing from painting: public void print(Graphics g) { // Assume it's a PrintGraphics object: PrintJob pj = ((PrintGraphics)g).getPrintJob(); int w = pj.getPageDimension().width; int h = pj.getPageDimension().height; doGraphics(g, w, h); } public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; doGraphics(g, w, h); } private void doGraphics( Graphics g, int w, int h) { int xc = w / 2; int yc = w / 2; int x = 0, y = 0; for(int i = 0; i < rings; i++) { if(x < xc && y < yc) { g.drawOval(x, y, w, h); x += 10; y += 10; w -= 20; h -= 20; } } } } ///:~
program allows you to select fonts from a
Choice
list (and you’ll see that the number of fonts available in Java 1.1
is still extremely limited, and has nothing to do with any extra fonts you
install on your machine). It uses these to print out text in bold, italic, and
in different sizes. In addition, a new type of component called a
Plot
is created to demonstrate graphics. A
Plot
has rings that it will display on the screen and print onto paper, and the
three derived classes
Plot1,
Plot2,
and
Plot3
perform these tasks in different ways so that you can see your alternatives
when printing graphics. Also, you can change the number of rings in a plot
– this is interesting because it shows the printing fragility in Java 1.1.
On my system, the printer gave error messages and didn’t print correctly
when the ring count got “too high” (whatever that means), but
worked fine when the count was “low enough.” You will notice, too,
that the page dimensions produced when printing do not seem to correspond to
the actual dimensions of the page. This might be fixed in a future release of
Java, and you can use this program to test it.
program encapsulates functionality inside inner classes whenever possible, to
facilitate reuse. For example, whenever you want to begin a print job (whether
for graphics or text), you must create a PrintJob
object, which has its own
Graphics
object
along with the width and height of the page. The creation of a
PrintJob
and extraction of page dimensions is encapsulated in the
PrintData
class.
printing
text is straightforward: you choose a typeface and size, decide where the
string should go on the page, and draw it with
Graphics.drawString( ).
This means, however, that you must perform the calculations of exactly where
each line will go on the page to make sure it doesn’t run off the end of
the page or collide with other lines. If you want to make a word processor,
your work is cut out for you.
encapsulates a little of the process of changing from one font to another by
automatically creating a new Font
object with your desired typeface, style (
Font.BOLD
or
Font.ITALIC
– there’s no support for underline, strikethrough, etc.), and point
size. It also simplifies the calculation of the width and height of a string.
you press the “Print text” button, the
TBL
listener
is activated. You can see that it goes through two iterations of creating a
ChangeFont
object and calling
drawString( )
to print out the string in a calculated position, centered, one-third, and
two-thirds down the page, respectively. Notice whether these calculations
produce the expected results. (They didn’t with the version I used.)
you press the “Print graphics” button the
GBL
listener is activated. The creation of a
PrintData
object initializes
g,
and then you simply call
print( )
for the component you want to print. To force printing you must call dispose( )
for the Graphics
object and end( )
for the
PrintData
object (which turns around and calls
end( )
for the
PrintJob).
work is going on inside the
Plot
object. You can see that the base-class
Plot
is simple – it extends
Canvas
and contains an
int
called
rings
to indicate how many concentric rings to draw on this particular
Canvas.
The three derived classes show different approaches to accomplishing the same
goal: drawing on both the screen and on the printed page.
takes
the simplest approach to coding: ignore the fact that there are differences in
painting and printing, and just override paint( ).
The reason this works is that the default
print( )
method simply turns around and calls
paint( ).
However, you’ll notice that the size of the output depends on the size of
the on-screen canvas, which makes sense since the
width
and
height
are determined by calling
Canvas.getSize( ).
The other situation in which this is acceptable is if your image is always a
fixed size.
the size of the drawing surface is important, then you must discover the
dimensions. Unfortunately, this turns out to be awkward, as you can see in
Plot2.
For some possibly good reason that I don’t know, you cannot simply ask the
Graphics
object the dimensions of its drawing surface. This would have made the whole
process quite elegant. Instead, to see if you’re printing rather than
painting, you must detect the PrintGraphics
using the RTTI
instanceof
keyword (described in Chapter 11), then downcast and call the sole
PrintGraphics
method: getPrintJob( ).
Now you have a handle to the
PrintJob
and you can find out the width and height of the paper. This is a hacky
approach, but perhaps there is some rational reason for it. (On the other hand,
you’ve seen some of the other library designs by now so you might get the
impression that the designers were, in fact, just hacking around…)
can see that
paint( )
in
Plot2
goes
through both possibilities of printing or painting. But since the
print( )
method should be called when printing, why not use that? This approach is used
in
Plot3,
and it eliminates the need to use
instanceof
since inside
print( )
you can assume that you can cast to a
PrintGraphics
object. This is a little better. The situation is improved by placing the
common drawing code (once the dimensions have been detected) inside a separate
method
doGraphics( ).
if you’d like to print from within an applet? Well, to print anything you
must get a
PrintJob
object through a Toolkit
object’s getPrintJob( )
method, which takes only a
Frame
object and not an
Applet.
Thus it would seem that it’s possible to print from within an
application, but not an applet. However, it turns out that you can create
a
Frame
from within an applet (which is the reverse of what I’ve been doing for
the applet/application examples so far, which has been making an applet and
putting inside a
Frame).
This is a useful technique since it allows you to use many applications
within applets (as long as they don’t violate applet security). When the
application window comes up within an applet, however, you’ll notice that
the Web browser sticks a little caveat on it, something to the effect of
“Warning: Applet Window.”
can see that it’s quite straightforward to put a
Frame
inside an applet. The only thing that you must add is code to dispose( )
of the
Frame
when the user closes it (instead of calling
System.exit( )):
//: PrintDemoApplet.java // Creating a Frame from within an Applet import java.applet.*; import java.awt.*; import java.awt.event.*; public class PrintDemoApplet extends Applet { public void init() { Button b = new Button("Run PrintDemo"); b.addActionListener(new PDL()); add(b); } class PDL implements ActionListener { public void actionPerformed(ActionEvent e) { final PrintDemo pd = new PrintDemo(); pd.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e){ pd.dispose(); } }); pd.setSize(500, 500); pd.show(); } } } ///:~
some confusion involved with Java 1.1
printing
support. Some of the publicity seemed to claim that you’d be able to
print from within an applet. However, the Java security system contains a
feature that could lock out an applet from initiating its own print job,
requiring that the initiation be done via a Web browser or applet viewer. At
the time of this writing, this seemed to remain an unresolved issue. When I ran
this program from within a Web browser, the
PrintDemo
window came up just fine, but it wouldn’t print from the browser.
The
clipboard
1.1
supports limited operations with the system
clipboard (in the
java.awt.datatransfer
package).
You can copy
String
objects to the clipboard as text, and you can paste text from the clipboard into
String
objects. Of course, the clipboard is designed to hold any type of data, but how
this data is represented on the clipboard is up to the program doing the
cutting and pasting. Although it currently supports only string data, the Java
clipboard API provides for extensibility through the concept of a
“flavor.” When data comes off the clipboard, it has an associated
set of flavors
that it can be converted to (for example, a graph might be represented as a
string of numbers or as an image) and you can see if that particular clipboard
data supports the flavor you’re interested in.
following program is a simple demonstration of cut, copy, and paste with
String
data in a TextArea.
One thing you’ll notice is that the keyboard sequences you normally use
for cutting, copying, and pasting also work. But if you look at any
TextField
or
TextArea
in any other program you’ll find that they also automatically support the
clipboard key sequences. This example simply adds programmatic control of the
clipboard, and you could use these techniques if you want to capture clipboard
text into some non-
TextComponent.
//: CutAndPaste.java // Using the clipboard from Java 1.1 import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; public class CutAndPaste extends Frame { MenuBar mb = new MenuBar(); Menu edit = new Menu("Edit"); MenuItem cut = new MenuItem("Cut"), copy = new MenuItem("Copy"), paste = new MenuItem("Paste"); TextArea text = new TextArea(20,20); Clipboard clipbd = getToolkit().getSystemClipboard(); public CutAndPaste() { cut.addActionListener(new CutL()); copy.addActionListener(new CopyL()); paste.addActionListener(new PasteL()); edit.add(cut); edit.add(copy); edit.add(paste); mb.add(edit); setMenuBar(mb); add(text, BorderLayout.CENTER); } class CopyL implements ActionListener { public void actionPerformed(ActionEvent e) { String selection = text.getSelectedText(); StringSelection clipString = new StringSelection(selection); clipbd.setContents(clipString, clipString); } } class CutL implements ActionListener { public void actionPerformed(ActionEvent e) { String selection = text.getSelectedText(); StringSelection clipString = new StringSelection(selection); clipbd.setContents(clipString, clipString); text.replaceRange("", text.getSelectionStart(), text.getSelectionEnd()); } } class PasteL implements ActionListener { public void actionPerformed(ActionEvent e) { Transferable clipData = clipbd.getContents(CutAndPaste.this); try { String clipString = (String)clipData. getTransferData( DataFlavor.stringFlavor); text.replaceRange(clipString, text.getSelectionStart(), text.getSelectionEnd()); } catch(Exception ex) { System.out.println("not String flavor"); } } } public static void main(String[] args) { CutAndPaste cp = new CutAndPaste(); cp.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); cp.setSize(300,200); cp.setVisible(true); } } ///:~
creation and addition of the menu and
TextArea
should by now seem a pedestrian activity. What’s different is the
creation of the
Clipboard
field
clipbd,
which is done through the
Toolkit.
the action takes place in the listeners. The
CopyL
and
CutL
listeners are the same except for the last line of
CutL,
which erases the line that’s been copied. The special two lines are the
creation of a StringSelection
object from the
String
and the call to setContents( )
with this
StringSelection.
That’s all there is to putting a
String
on the clipboard.
PasteL,
data is pulled off the clipboard using getContents( ).
What comes back is a fairly anonymous Transferable
object, and you don’t really know what it contains. One way to find out
is to call getTransferDataFlavors( ),
which returns an array of DataFlavor
objects indicating which flavors are supported by this particular object. You
can also ask it directly with isDataFlavorSupported( ),
passing in the flavor you’re interested in. Here, however, the bold
approach is taken: getTransferData( )
is called assuming that the contents supports the
String
flavor, and if it doesn’t the problem is sorted out in the exception
handler.