JETZT ONLINE BESTELLEN
Add to Cart
Swing Hacks
Tips and Tools for Killer GUIs

First Edition Juli 2005
ISBN 978-0-596-00907-6
542 Seiten
EUR24.50

Weitere Informationen zu diesem Buch

Inhaltsverzeichnis | Kolophon | Rezensionen |


Inhaltsverzeichnis

	
Chapter 1: Basic JComponents
Inhaltsvorschau
Swing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest JButton to the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into the JTree or Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.
This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.
This hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.
Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.
To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 1–12: Introduction
Inhaltsvorschau
Swing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest JButton to the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into the JTree or Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.
This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create Image-Themed Components
Inhaltsvorschau
This hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.
Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.
To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.
Figure 1-1: A component rendered with images
The first step toward image nirvana is the background. Because this type of component is quite reusable, I built a subclass of JPanel called ImagePanel, shown in Example 1-1.
Example 1-1. A Custom subclass of JPanel
	public class ImagePanel extends JPanel {

		private Image img;



		public ImagePanel(Image img) {

			this.img = img;

			Dimension size = new Dimension(img.getWidth(null),

							   img.getHeight(null));setSize(size);

			setPreferredSize(size);

			setMinimumSize(size);

			setMaximumSize(size);

			setLayout(null);

		}



	}
The constructor takes the image to draw and saves it for later use in the img variable. Then it calls setSize() and setPreferredSize() with the size of the image. This ensures that the panel will be the size of the image exactly. I had to set the preferred, maximum, and minimum sizes as well—this is because the panel's parent and children may not be using absolute layouts.
Absolute layout means that there is no layout manager to position the components appropriately (which can be set by calling
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Don't Settle for Boring Text Labels
Inhaltsvorschau
JLabel is a Swing staple; but it's easy to spruce up boring labels with drop shadows, outlines, and even 3D text.
When you want to draw non-editable text, Swing provides only the JLabel. You can change the font, size, color, and even add an icon. By using HTML in your components [Hack #52] , you can even add things like underline and bullets. This is fine for most jobs, but sometimes you need more. What if you want a drop shadow or an embossed effect? The JLabel is simply inadequate for richer interfaces. Fortunately, the Swing Team made it very easy to extend the JLabel and add these features yourself.
A great many text effects can be achieved with two simple features. First, you can draw text multiple times, with each iteration slightly offset or in a different color, to create effects like drop shadows and embossing. Second, you can adjust the spacing between letters in a word (a feature known as tracking in text-processing circles). Tracking is always specified in addition to the default tracking specified by a font. Thus, a tracking of +1 would be drawn as one extra pixel between each letter. A tracking of 0 would have the same spacing as no extra tracking at all.
To implement all of this, you must override both the sizing and the painting code in JLabel, which of course calls for a subclass; see Example 1-5 for details.
Example 1-5. Defining a richer JLabel
	public class RichJLabel extends JLabel {



		private int tracking;

		public RichJLabel(Stringtext, int tracking) {

			super(text);

			this.tracking = tracking;

		}



		private int left_x, left_y, right_x, right_y;

		private Color left_color, right_color;

		public void setLeftShadow(int x, int y, Color color) {

			left_x = x;

			left_y = y;

			left_color = color;

		}

		public void setRightShadow(int x, int y, Color color) {

			right_x = x;

			right_y = y;

			right_color = color;

		}
RichJLabel extends the standard javax.swing.JLabel and adds a tracking argument to the constructor. Next, it adds two methods for the right and left shadow. These are called shadows because they will be drawn below the main text, but whether they actually look like shadows depends on the color, as well as the x- and y-offsets passed into each method.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Fill Your Borders with Pretty Pictures
Inhaltsvorschau
Swing comes with a set of customizable borders, but sometimes you want more than they provide. This hack shows how to create a completely imagebased border that can be resized.
Swing has a prefabricated border, called the MatteBorder, which can accept an image in its constructor. For simple tiled backgrounds, such as a checkerboard pattern, this works fine. However, if you want to have particular images in each corner, creating a fully resizable image border, then you'll need something more powerful. Fortunately, Swing makes it very easy to create custom border classes. The image border in this hack will produce a border that looks like Figure 1-12.
Figure 1-12: An image-based border
The first step to any custom border is to subclass AbstractBorder and implement the paintBorder() method. The class will take eight images in the constructor, one for each corner and each side; all the code is shown in Example 1-6.
Example 1-6. Building an image-based border
	public class ImageBorder extends AbstractBorder {



		Image top_center, top_left, top_right;

		Image left_center, right_center;

		Image bottom_center, bottom_left, bottom_right;

		Insets insets;



		public ImageBorder(Image top_left, Image top_center, Image top_right,

			Image left_center, Image right_center,

			Image bottom_left, Image bottom_center, Image bottom_right) {



			this.top_left = top_left;

			this.top_center = top_center;

			this.top_right = top_right;

			this.left_center = left_center;

			this.right_center = right_center;

			this.bottom_left = bottom_left;

			this.bottom_center = bottom_center;

			this.bottom_right = bottom_right;

		}



	public voidsetInsets(Insets insets) {

				this.insets = insets;

			}



			public Insets getBorderInsets(Component c) {

				if(insets != null) {

				return insets;

				} else {

				return new Insets(top_center.getHeight(null),

				left_center.getWidth(null),

				bottom_center.getHeight(null), right_center.getWidth(null));

				}

			}
The two methods after the constructor control the border insets. These are the gaps between the panel's outer edge (and its parent) and the inner edge of the panel where the panel's children are drawn.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Display Dates in a Custom Calendar
Inhaltsvorschau
You can download calendar components from third parties, but real hackers can use Swing to build a custom calendar widget on their own.
When you design an application, you'll often want to use standard widgets to display information. Swing doesn't always give you what you need, though. Consider the calendar component: Swing doesn't come with one, so most users have to download widgets to integrate into their application. However, why not go with a cool and hip teen-friendly application with an attractive, image-based component, as shown in Figure 1-15?
Figure 1-15: Custom calendar component
That would be a bit more fun, wouldn't it? This hack will show you how to build a completely custom calendar component using java.util.Calendar and a few images.
First, consider what you'll need. You've got to have pretty images, a component to paint them on, and then some logic to handle the different parts of the date, including what day of the week starts off the current month. You should also provide a setDate() method, so that MVC frameworks can play well with your calendar. Let's get started.
I created three images in Photoshop: one for the background, one for each day, and one for the current day. These are shown in Figures 1-16, 1-17, and 1-18.
Figure 1-16: calendar.png for the general background
Figure 1-17: day.png for the day backgrounds
Figure 1-18: highlight.png for the current day
I could have separated the day names and the title, but since they don't change, it was simpler to make them part of the image.
The easiest way to create a custom component with fancy drawing is to start off with a JPanel and override the paintComponent() method, as shown in Example 1-9.
Example 1-9. A Calendar base component
	public class CalendarHack extends JPanel {

		protected Image background, highlight, day_img;

		protected SimpleDateFormat month = new SimpleDateFormat("MMMM");

protected SimpleDateFormat year = new SimpleDateFormat("yyyy");

		protected SimpleDateFormat day = new SimpleDateFormat("d");

		protected Date date = new Date();



		public void setDate(Date date) {

			this.date = date;

		}



		publicCalendarHack() {

			background = new ImageIcon("calendar.png").getImage();

			highlight = new ImageIcon("highlight.png").getImage();

			day_img = new ImageIcon("day.png").getImage();

			this.setPreferredSize(new Dimension(300,280));



		}

		

		public void paintComponent(Graphics g) {



			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,

				RenderingHints.VALUE_ANTIALIAS_ON);

			g.drawImage(background,0,0,null);

			g.setColor(Color.black);

			g.setFont(new Font("SansSerif",Font.PLAIN,18));

			g.drawString(month.format(date),34,36);

			g.setColor(Color.white);

			g.drawString(year.format(date),235,36);



		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add a Watermark to a Text Component
Inhaltsvorschau
This hack will show how to create a custom image background for the JTextField, a complex Swing component that does not already support backgrounds or icons by default.
One of Swing's most underused features is the ability to partially override drawing code. Most programs enhance widgets by using renderers or completely overriding the paint code. By only partially overriding the drawing, however, you can create some very interesting effects that blend both new and existing drawing commands.
Some components, like JList and JTable, use renderers to customize their look. To put a background in a JTextField, however, requires more. The plan is to subclass JTextField, prepare the resources for drawing a background (loading the image, etc.), and then draw a new background while preserving the normal JTextField drawing code for the text and cursor.
The actual drawing will be done with a TexturePaint. Java2D allows you to fill any area with instances of the Paint interface. Typically you use a color, which is an implementation of Paint, but it is possible to use something else, such as a texture or gradient. This class will use a TexturePaint to tile an image across the component's background.
The first step is to create a JTextField subclass (shown in Example 1-10).
Example 1-10. Preparing a field for watermarking
	public class WatermarkTextField extends JTextField {

		BufferedImage img;

		TexturePaint texture;



		public WatermarkTextField(File file) throws IOException {

			super();

			img = ImageIO.read(file);

			Rectangle rect = new Rectangle(0,0,

				img.getWidth(null),img.getHeight(null));

			texture = new TexturePaint(img, rect);

			setOpaque(false);

		}

	}
Example 1-10 creates a class called WatermarkTextField. It is a subclass of JTextField with a custom constructor that accepts a File object containing an image. It also defines two member variables: img and texture. After the obligatory call to super(), the constructor reads the file into the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Watermark Your Scroll Panes
Inhaltsvorschau
This hack creates a text area with a tiled background image that is fixed, even when the text area scrolls, and also a fixed foreground image that appears above the text, much like the station badges now affixed to the lower-righthand corner of most TV broadcasts.
The Swing framework was designed to let developers override portions of every component, both the visual appearance (the view) and the behavior (the model and controller). This design gives developers great flexibility. One of my favorites is the JScrollPane. Its nested composite design allows developers to create some stunning effects.
Once again, the idea is to override the drawing code of a standard component to create the visual effects [Hack #5] . The difference here is that you must deal with a composite object, the JScrollPane. A JScrollPane is not a single Swing component—it's actually a wrapper around two scrollbars and the component that does the real scrolling is a JViewport. This viewport is the actual target component; you will subclass it to draw both above and below the View component (as seen in Example 1-12). The View is the Swing widget being scrolled; in this case, it is a JTextArea.
Example 1-12. Modifying the viewport for watermarking
    public class ScrollPaneWatermark extends JViewport {

       BufferedImage fgimage, bgimage;

       TexturePaint texture;

       public void setBackgroundTexture(URL url) throws IOException {

       bgimage = ImageIO.read(url);

       Rectangle rect = new Rectangle(0,0,

               bgimage.getWidth(null),bgimage.getHeight(null));

       texture = new TexturePaint(bgimage, rect);

    }



    public void setForegroundBadge(URL url) throws IOException {

        fgimage = ImageIO.read(url);

    }
The ScrollPaneWatermark class inherits from JViewport, adding two methods: setBackgroundTexture() and setForegroundBadge(). Each takes a URL instead of a File to allow for images loaded from places other than the local disk, such as a web server or JAR file.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Put a NASA Photo into the Background of a Text Area
Inhaltsvorschau
This hack will repurpose an existing web page, one of NASA's photo sites, by pulling their "Astronomy Picture of the Day" into the background of a text area.
You've already learned how to draw a watermark image in the background of a text area [Hack #6] using a ScrollPaneWatermark. This hack will pull a photo down from the Web and reuse that class to put the photo in the background. The photo itself comes from NASA's "Astronomy Picture of the Day" page: http://antwrp.gsfc.nasa.gov/apod/. The URL to the image changes each day, but the page itself does not. To pull the image down you will load the page, find the image URL, then load the image itself and put it into the ScrollPaneWatermark. Depending on the day, it may look something like Figure 1-22.
Figure 1-22: Text area with a background image
The code in Example 1-14 defines a class called BackgroundLoader, which implements Runnable so it can be placed on its own thread. The constructor takes as an argument the ScrollPaneWatermark, which the loader will put the image into. The run() method contains a loop that will run every two hours, loading the page, finding the SRC URL, then loading the image into the watermark.
Example 1-14. A thread to load a background image
    public class BackgroundLoader implements Runnable {

	    private ScrollPaneWatermark watermark;

		public BackgroundLoader(ScrollPaneWatermark watermark) {

		    this.watermark = watermark;

		}	



		public void run() {

			while(true) {

			try {String base_url = "http://antwrp.gsfc.nasa.gov/apod/"; 

				URL url = new URL(base_url);

				Reader input = new InputStreamReader(url.openStream());

				char buf[] = new char[1024];

				StringBuffer page_buffer = new StringBuffer();

				while(true) {



				int n = input.read(buf); 

				if(n < 0) { break; } 

				page_buffer.append(buf,0,n);

			}

			// Locate the Image URL (see next section)

			} catch (Exception ex) {

				System.out.println("exception: " + ex);

				ex.printStackTrace();



			} 

		} 

	} 

  }
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Animate Transitions Between Tabs
Inhaltsvorschau
This hack shows how to create animated transitions that play whenever the user switches tabs on a JTabbedPane.
One of Swing's great strengths is that you can hack into virtually anything. In particular, I love making changes to a component's painting code. The ability to do this is one of the reasons I prefer Swing over SWT. Swing gives me the freedom to create completely new UI concepts, such as transitions.
With the standard paint methods, Swing provides most of what you will need to build the transitions. You will have to put together three additional things, however. First, you need to find out when the user actually clicked on a tab to start a transition. Next, you need a thread to control the animation. Finally, since some animations might fade between the old and new tabs, you need a way to provide images of both tabs at the same time. With those three things, you can build any animation you desire.
To keep things tidy, I have implemented this hack as a subclass of JTabbedPane, except for the actual animation drawing, which will be delegated to a further subclass. By putting all of the heavy lifting into the parent class, you will be able to create new animations easily.
Example 1-16 is the basic skeleton of the parent class.
Example 1-16. A skeleton for the transition manager
	public class TransitionTabbedPane extends JTabbedPane 

		implements ChangeListener, Runnable {

		protected int animation_length = 20;

public TransitionTabbedPane() {

			super();this.addChangeListener(this);



		}

		public int getAnimationLength() {

			return this.animation_length;

		}

		

		public void setAnimationLength(int length) {

			this.animation_length = length;

		}
TransitionTabbedPane extends the standard JTabbedPane and also implements ChangeListener and Runnable. ChangeListener allows you to learn when the user has switched between tabs. Since the event is propagated before the new tab is painted, inserting the animation is very easy.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Blur Disabled Components
Inhaltsvorschau
This hack explores creating how to perform a blur transformation on a Swing component.
Every Swing component draws to the screen via the paintComponent() method. This is true even for components that offload the actual drawing to Look and Feel UI objects. Because all drawing goes through the paintComponent() method at some point, this point is where you can do some interesting things by manipulating the graphics object during the paint process.
Swing components draw to the Graphics object passed in through the paintComponent() method. This means that if you replace the Graphics object with a custom version, you can capture a component's drawing into a bitmap instead of going straight to the screen.
Blurring is a pixel-level operation, meaning the actual blurring is done pixel-by-pixel in a bitmap. By drawing the component to a bitmap, blurring that bitmap, and then drawing the bitmap in the place of the component, you can effectively have a blurred component without disturbing the rest of the Swing painting routines. The particular implementation in this hack uses a blurred effect to replace the normal graying of a component when it is disabled.
The first step is to capture the button into a bitmap, as shown in Example 1-20.
Example 1-20. Creating a blurrable button
	public classBlurJButton extends JButton {

		public BlurJButton(String text) {

			super(text);

		}



		public void paintComponent(Graphics g) {

			if(isEnabled()) {

			super.paintComponent(g);

			return;



		}

		BufferedImage buf = new BufferedImage(getWidth(),getHeight(),

				BufferedImage.TYPE_INT_RGB);        

		super.paintComponent(buf.getGraphics());

		// Blur the buffered image (see next section)

		} 

	}
The BlurJButton class extends a normal JButton and overrides the paintComponent() method. If the button is enabled (neither disabled nor grayed out), then it calls the superclass's normal version of paintComponent() and returns. If the button is disabled, however, then
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Building a Drop-Down Menu Button
Inhaltsvorschau
This hack shows how to build a color chooser as a proper drop-down component. It will behave like JComboBox but without the extension headaches of Sun's version of the class.
Most custom Swing components are created with simple subclasses of the standard base classes in javax.swing. This works fine most of the time, but every now and then you need to build something where there is no easy standard component to start with. Even worse, sometimes the obvious choice for your starting point is a component so convoluted that you can't figure out where to start. Still, you'd rather not reimplement the wheel. No, I'm not talking about JTree or JTable—I'm referring to the JComboBox. It seems like such a simple component, but the implementation is fiendishly complex.
Most large applications use components that feel like the JComboBox, but do something entirely different, like select a color or show a history list. A quick search through the JComboBox API doesn't turn up any obvious extension points. You could customize it with some cell renderers, but if you need a component that doesn't show a list of data, you are pretty much out of luck. The source to JComboBox is not very helpful either. The work is spread out over several UI classes in the various Look and Feel (L&F) packages. If you did customize one of those, your component would look out of place when used in a different L&F. The only real option is to write your own combo box, which is pretty easy except for the actual drop-down part. You need to show a component on top of the others, poking out of the frame occasionally, but without any decorations of its own. It should be just a borderless floating box. Digging through Swing's source code reveals the secret ingredient: a JWindow.
JWindow is a subclass of Window but not of Frame. This means it has no decorations on the side, and it is hidden from the Dock and Taskbar. This is exactly what you want from a pop up. Care must be taken when creating it, however, as you must ensure the window appears only on top of the existing components, and that it disappears when something else gains focus or the window moves. Fortunately, you can do all of this with one composite component and a few event listeners.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create Menus with Drop Shadows
Inhaltsvorschau
This hack explores a simple way to create drop shadows on menus throughout an entire application with minimal code changes.
Many modern operating systems provide menus with interesting effects to make them jump off the screen. One of the most common is the drop shadow. Some programs even provide shadows themselves when the host operating system does not. For years, a lack of low-level graphics support has denied Swing programs access to these kinds of cool effects. But not any more! Most of the effects can be duplicated with Swing's robust theming ability.
Most custom effects require either subclassing a component or messing with graphics overlays. I tried a variety of techniques to create this hack, but I kept coming across the same problem over and over. If I wanted to draw a shadow, I had to change the sizing of each menu item, plus its background, plus the pop-up frame itself. That is a lot of components to manage. It would be a lot simpler if I could tell the components to make themselves a little bit bigger and give me the extra slice of screen real estate to draw in. The solution was right under my nose: the border. Every Swing component can use a custom border, without subclassing, and the border will automatically resize the component to fit. If the border is lopsided, then it will create a kind of shadow effect. Perfect!
Every standard Swing component is actually drawn by a UI helper class, and pop-up menus are no exception. I took the BasicPopupMenuUI in the javax. swing.plaf.basic package and created a subclass called CustomPopupMenuUI (shown in Example 1-25). It only does two things special: adds a custom border to the pop up's parent panel and sets the panel to be transparent.
Example 1-25. Extending the pop-up menu's UI
	public class CustomPopupMenuUI extends BasicPopupMenuUI { 

		public static ComponentUI createUI(JComponent c) {        

			return new CustomPopupMenuUI(); 

		}

		public Popup getPopup(JPopupMenu popup, int x, int y) {

			Popup pp = super.getPopup(popup,x,y);

			JPanel panel = (JPanel)popup.getParent();

			panel.setBorder(newShadowBorder(3,3));

			panel.setOpaque(false);

			return pp;



		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Translucence to Menus
Inhaltsvorschau
In this hack I will show you how to add true translucency to your menus with only a slight modification to your program.
Computer interfaces are pretty sophisticated these days. Years ago, we considered ourselves lucky to simply have menu bars at all; now, we need menus with sophisticated effects like animation, shadows, and translucency.
You've already seen how to achieve visual effects by overriding the paint() method of a parent component and then rendering the children into a buffer [Hack #9] . It would be nice to do the same thing here, but there's just one small problem. Overriding the paint() method of the JMenu wouldn't do any good because the JMenu doesn't draw what we think of as a menu—a list of menu items that pop up when you click on the menu's title. The JMenu actually only draws the title at the top of a menu. The rest of the menu is drawn by a JPopupMenu created as a member of the JMenu. Unfortunately this member is marked private, which means you can't substitute your own JPopupMenu subclass for the standard version.
Fortunately there is a way out. Like all Swing components, the menu components delegate their actual drawing to a separate set of Look and Feel classes in the javax.swing.plaf package. If you override the right plaf classes for the menu items and pop-up menu, then you should be able to create the desired translucent effect. It just takes a little subclassing.
All MenuItems are implemented by some form of the javax.swing.plaf. MenuItemUI class. When creating custom UI classes, it is always best to start by subclassing something in the javax.swing.plaf.basic package (in this case, BasicMenuItemUI) because it handles most of the heavy lifting for you, as shown in Example 1-28.
Example 1-28. Extending the basic UI
	public class CustomMenuItemUI extends BasicMenuItemUI {

		public static ComponentUI createUI(JComponent c) {

			return new CustomMenuItemUI();

		}



		public void paint(Graphics g, JComponent comp) {

			// paint to the buffered image

			BufferedImage bufimg = new BufferedImage(



				comp.getWidth(),

				comp.getHeight(),

				BufferedImage.TYPE_INT_ARGB);



			Graphics2D g2 = bufimg.createGraphics();

			// restore the foreground color in case the superclass needs it

			g2.setColor(g.getColor());

			super.paint(g2,comp);

			// do an alpha composite

			Graphics2D gx = (Graphics2D) g;

			gx.setComposite(AlphaComposite.getInstance(

				AlphaComposite.SRC_OVER,0.8f));

			gx.drawImage(bufimg,0,0,null);

		}



	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 2: Lists and Combos
Inhaltsvorschau
Lists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.
Make your 1,000-item list a lot more manageable.
One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.
The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.
The implementation in this hack, FilteredJList, is a single class with two inner subclasses: FilterModel and FilterField. The list owns the field, so a caller can create the JList fairly typically and then just ask for the field and add it wherever it makes sense in the layout.
Start by declaring FilteredJList as a subclass of JList, and provide a constructor and some convenience methods, as seen in Example 2-1.
Example 2-1. FilterList constructor and convenience methods
public classFilteredJList extends JList {



    private FilterField filterField;

    private int DEFAULT_FIELD_WIDTH = 20;

   

    public FilteredJList() {

        super();

        setModel (new FilterModel());

        filterField = new FilterField (DEFAULT_FIELD_WIDTH);



    }



    public void setModel (ListModel m) {

        if (! (m instanceof FilterModel))

            throw new IllegalArgumentException();

        super.setModel (m);

    }



    public void addItem (Object o) {

        (FilterModel)getModel()).addElement (o);

    }



    public JTextField getFilterField() {

        return filterField;

    }
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 13–20: Introduction
Inhaltsvorschau
Lists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Filter JLists
Inhaltsvorschau
Make your 1,000-item list a lot more manageable.
One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.
The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.
The implementation in this hack, FilteredJList, is a single class with two inner subclasses: FilterModel and FilterField. The list owns the field, so a caller can create the JList fairly typically and then just ask for the field and add it wherever it makes sense in the layout.
Start by declaring FilteredJList as a subclass of JList, and provide a constructor and some convenience methods, as seen in Example 2-1.
Example 2-1. FilterList constructor and convenience methods
public classFilteredJList extends JList {



    private FilterField filterField;

    private int DEFAULT_FIELD_WIDTH = 20;

   

    public FilteredJList() {

        super();

        setModel (new FilterModel());

        filterField = new FilterField (DEFAULT_FIELD_WIDTH);



    }



    public void setModel (ListModel m) {

        if (! (m instanceof FilterModel))

            throw new IllegalArgumentException();

        super.setModel (m);

    }



    public void addItem (Object o) {

        (FilterModel)getModel()).addElement (o);

    }



    public JTextField getFilterField() {

        return filterField;

    }
Notice that along with holding onto the FilterField, the JList also creates its own FilterModel in the constructor, and overrides setModel() to ensure that you can't push in an incompatible model. It also contains an addItem() method, which really just delegates to the FilterModel.
FilterModel, shown in Example 2-2, is where the magic happens.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add a Filter History
Inhaltsvorschau
Remember previous searches and research with one click.
Chances are good that if you've searched for something once, it's important enough that you might well search for it again. In Apple's Safari browser, a search widget at the upper right has a little magnifying glass that remembers your last 10 searches. Click the magnifying glass and a pop up appears with the previous searches. Select one and it populates the field and does the search immediately.
Here's an implementation of the same idea, grafted onto the previous hack. In other words, this remembers previous filters. It doesn't remember every keystroke—why bother remembering the searches "J" and "Jo" when you're really just interested in "Joe"—and only adds a search term to the filter when the user presses return.
In the previous hack, you just needed to have a text field and a JList. Now a JButton needs to be attached to the text field, so the two are bundled together in the inner class FilterField. This class is responsible for:
  • Telling the model to refilter on each keystroke in the JTextField, as before.
  • Remembering the JTextField's contents as a saved search anytime the Return or Enter key is pressed.
  • Catching clicks on the JButton and popping up a menu with previous searches.
  • Populating the JTextField with a previous search when one is selected from the list. It doesn't need to explicitly tell the model to refilter because changing the text area will fire a DocumentEvent that is already accounted for by the JTextField's DocumentListener.
Example 2-5 shows the new FilterField class.
Example 2-5. List filtering component with text field and history button
class FilterField extends JComponent

    implements DocumentListener, ActionListener {

    LinkedList prevSearches;

    JTextField textField;

    JButton prevSearchButton;

    JPopupMenu prevSearchMenu;

    public FilterField (int width) {

        super();

        setLayout(new BorderLayout());

        textField = new JTextField (width);

        textField.getDocument().addDocumentListener (this);

        textField.addActionListener (this); 

prevSearchButton =

             new JButton (new ImageIcon ("mag-glass.png"));

        prevSearchButton.setBorder(null);prevSearchButton.addMouseListener (new MouseAdapter() {

                public void mousePressed (MouseEvent me) {                               popMenu (me.getX(), me.getY()); 

                }

             });

        add (prevSearchButton, BorderLayout.WEST);

        add (textField, BorderLayout.CENTER);

        prevSearches = new LinkedList ();



  }

  public void popMenu (int x, int y) {

      prevSearchMenu = new JPopupMenu();

      Iterator it = prevSearches.iterator();

      while (it.hasNext())

          prevSearchMenu.add (               

             new PrevSearchAction(it.next().toString()));

      prevSearchMenu.show (prevSearchButton, x, y);

  }

  public void actionPerformed (ActionEvent e) {

      // called on return/enter, adds term to prevSearches

      if (e.getSource() == textField) {

          prevSearches.addFirst (textField.getText());

          if (prevSearches.size() > 10)

              prevSearches.removeLast();

      }

  }

  public void changedUpdate (DocumentEvent e) {

         ((FilterModel)getModel()).refilter();

  }

  public void insertUpdate (DocumentEvent e) {

         ((FilterModel)getModel()).refilter();

  }

  public void removeUpdate (DocumentEvent e) {

         ((FilterModel)getModel()).refilter();

  }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make JLists Checkable
Inhaltsvorschau
Avoid losing 50 selections to an unshifted click.
One horrible UI problem is dealing with vast collections of things that need to be presented to the user and made selectable. If, like me, you've ever had 1,000 emails in your inbox, you know what I mean. Worse, what if you pick a bunch of items to delete, but your finger slips off the key used for multi-selection (Alt on Windows, Command on the Mac, etc.) and you lose all of your previous selections? Overriding the native selection behavior can make this situation somewhat more palatable.
Because a list like this behaves differently than a normal list, it should look different, too, so I've opted for a checkbox metaphor. Each item is shown with a checkbox, and as you click more items, they get checked, and if you select an already-checked item, it gets unchecked.
This turns out to be a little harder than expected. I once did it without JList, creating my own scrolling layout of JPanels and faking the list behavior. It turned up a funny Swing bug because I was using GridBagLayout for the fake list, and it started totally bombing out after about 500 items were added to the list. This was because GridBagLayout has a bug where it can't have more than 512 rows. Considering the bug (number 4254022 on the Java Bug Parade) was filed in 1999 and is still open, I'm figuring it won't get fixed by the time you read this.
The basis of the checkable list is a JList. The tricky part here is that there isn't a way (that I've found) to steal the mouse clicks from the JList and consume them before the normal calls to the ListSelectionModel are made. Instead, the strategy is to set up a ListSelectionListener and just fix everything after JList has done its thing.
To implement the checkbox functionality, subclass JList and give it a custom ListSelectionListener and a ListCellRenderer. Acomplete listing is shown in Example 2-6.
Example 2-6. A checkbox-metaphor JList
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Different List Items Look Different
Inhaltsvorschau
An in-progress download shouldn't look like a completed one.
What made me love lists again were the OmniWeb browser and (later) Safari—particularly, their download managers. By way of negative example, take the download manager for Internet Explorer 5 for Mac…please. This GUI was a table of filenames, URLs, sizes, etc., with columns not even intelligently resized for their widths. OmniWeb, on the other hand, showed a running download with a progress bar, and a finished download with the file location and file size. Safari goes a step further with context-appropriate buttons: an X to cancel an in-progress download, a magnifying glass to locate an already-downloaded file, etc. But it's the same idea: different things shouldn't look the same.
To do this in Swing, you need a hack that goes against everything in all the other Swing books: you need to stop subclassing JComponent when you write a ListCellRenderer. Instead, delegate the getListCellRendererComponent( ) call to one of several components, choosing whichever best represents the item to be rendered.
In fact, the whole tradition of subclassing JComponent for ListCellRenderers is a pretty hateful practice because they're not really used as Components anyway! They're certainly not added to the JList. Instead, a list cell is rendered off screen and those pixels are blitted to the JList. So, provided that what you return in getListCellRendererComponent is what you want the cell to look like, it really doesn't matter how you get there.
By way of demonstration, this hack shows the items in a given directory with different layouts, depending on the file type. All of the cells use an icon on the left, with a name in bold at the top of a two-line layout. However, if the item is a folder, the bottom line contains a count of the children in that folder. If the item is a text file—it ends with one of the various extensions associated with text files (e.g., .txt, .html, .java
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Reorder a JList with Drag-and-Drop
Inhaltsvorschau
Let users put things where they want.
You may be so used to immutable lists that the idea of reordering a list with drag-and-drop seems unnatural. The first time I saw it—rearranging the order of network devices in Mac OS X to establish a priority (e.g., try Ethernet, then wireless, then modem)—I thought it was kind of odd. In fact, Apple felt it necessary to put a label on the list to tell users they could drag-and-drop the list items to rearrange them. Now that I'm used to it, it's totally cool, and I'd like to see it done in more places.
To implement this functionality in a JList, you basically just have to implement the full set of AWT drag-and-drop interfaces because the list will be both the source of the drag and the target of the drop. The other thing you need to do is to use some cell rendering tricks to provide a visual cue as to where the drop will occur.
The ReorderableJList, shown in Example 2-12, is a JList that uses a DefaultListModel, which is mutable for the obvious reason that it will need to change in response to drag-and-drops. The bulk of it is concerned with implementing the drag-and-drop interfaces DragSourceListener, DropTargetListener, and DragGestureListener. It has an inner class implementing Tranferable to hold the item being dropped, although this isn't absolutely necessary. I could have just held the dragged item in an instance variable and nulled the Transferable in the drag-and-drop calls, but it doesn't hurt to do it the nice way.
Example 2-12. A JList that can be reordered with drag-and-drop
public class ReorderableJList extends JList 

    implements DragSourceListener, DropTargetListener, DragGestureListener {

	

    static DataFlavor localObjectFlavor;

    static {

        try {

            localObjectFlavor =

                new DataFlavor (DataFlavor.javaJVMLocalObjectMimeType);

        } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } 

    } 

    static DataFlavor[] supportedFlavors = { localObjectFlavor }; 

    DragSource dragSource; 

    DropTarget dropTarget; 

    Object dropTargetCell; 

    int draggedIndex = -1;



    public ReorderableJList () {

        super();

        setCellRenderer (new ReorderableListCellRenderer());

        setModel (new DefaultListModel());

        dragSource = new DragSource();

        DragGestureRecognizer dgr =

            dragSource.createDefaultDragGestureRecognizer (this,

                                       DnDConstants.ACTION_MOVE,

                                                           this);

        dropTarget = new DropTarget (this, this);

    }



    // DragGestureListener

    public void dragGestureRecognized (DragGestureEvent dge) {

        System.out.println ("dragGestureRecognized");

        // find object at this x,y

        Point clickPoint = dge.getDragOrigin();

        int index = locationToIndex(clickPoint);

        if (index == -1)

            return;

        Object target = getModel().getElementAt(index);

        Transferable trans = new RJLTransferable (target);

        draggedIndex = index;

        dragSource.startDrag (dge,Cursor.getDefaultCursor(),

                              trans, this);

    }

    // DragSourceListener events

    public void dragDropEnd (DragSourceDropEvent dsde) {

       System.out.println ("dragDropEnd()");

       dropTargetCell = null;

       draggedIndex = -1;

       repaint();

    }

public void dragEnter (DragSourceDragEvent dsde) {}

    public void dragExit (DragSourceEvent dse) {}

    public void dragOver (DragSourceDragEvent dsde) {}

    public void dropActionChanged (DragSourceDragEvent dsde) {}

    // DropTargetListener events

    public void dragEnter (DropTargetDragEvent dtde) {

        System.out.println ("dragEnter");

        if (dtde.getSource() != dropTarget)

            dtde.rejectDrag();

        else {

            dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);

            System.out.println ("accepted dragEnter");

        }

    }

    public void dragExit (DropTargetEvent dte) {}

    // dragOver() listed below

    // drop() listed below

    public void dropActionChanged (DropTargetDragEvent dtde) {}

 

    // main() method to test - listed below



    // RJLTransferable listing below



    // ReorderableListCellRendering listing below 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Animate Your JList Selections
Inhaltsvorschau
Fading in and catching the eye.
Not every GUI involves windows and mouse pointers, and the visual language of a GUI can be very different depending on what is provided by the environment. Typically, GUIs for things like console video games and settop boxes don't use a mouse metaphor, so there's no onscreen pointer that the user is tracking. As a result, these systems often give the user more profound feedback when they move around a list—highlights slide from one item to another, selected items fade in while deselected items fade out, etc.— so there's something the eye can track. You can do the same thing in Swing, with more cell-rendering hackery. You might not need it now, but it'll be handy if you ever design a kiosk with Swing.
One way to show a changed selection is to show a brief animation of the cell selection. Instead of just being highlighted instantly, you fade the selected cell from its unselected background and foreground colors to its selected colors over the course of a short time (really short, like a half-second, so it isn't annoying).
To do this, you'll need to create an animator thread that kicks off every time the selection changes. This short-lived thread repeatedly updates a highlight color and calls repaint(). The cell renderer can then use the updated highlight color as it redraws the cells in the list. Example 2-18 shows this technique.
Example 2-18. Animating the JList cell selection
import java.awt.*; 

import javax.swing.*; 

import javax.swing.event.*; 

import java.util.*;

public classAnimatedJList extends JList 

    implements ListSelectionListener {



    static java.util.Random rand = new java.util.Random();





    static Color listForeground, listBackground, 

        listSelectionForeground, listSelectionBackground; 

    static float[] foregroundComps, backgroundComps, 

        foregroundSelectionComps, backgroundSelectionComps;



    static {        

        UIDefaults uid = UIManager.getLookAndFeel().getDefaults(); 

        listForeground = uid.getColor ("List.foreground"); 

        listBackground = uid.getColor ("List.background");

        listSelectionForeground = uid.getColor ("List.selectionForeground");

        listSelectionBackground = uid.getColor ("List.selectionBackground");

        foregroundComps =

            listForeground.getRGBColorComponents(null);

        foregroundSelectionComps =

            listSelectionForeground.getRGBColorComponents(null);

backgroundComps =

            listBackground.getRGBColorComponents(null);

backgroundSelectionComps =

            listSelectionBackground.getRGBColorComponents(null); 

    } 

    public Color colorizedSelectionForeground,

        colorizedSelectionBackground;



    public static final int ANIMATION_DURATION = 1000; 

    public static final int ANIMATION_REFRESH = 50;

  

    public AnimatedJList() {        

        super(); 

        addListSelectionListener (this);        

        setCellRenderer (new AnimatedCellRenderer());

    }



    public void valueChanged (ListSelectionEvent lse) {

        if (! lse.getValueIsAdjusting()) {

            HashSet selections = new HashSet();

            for (int i=0; i < getModel().getSize(); i++) {

                if (getSelectionModel().isSelectedIndex(i))

                    selections.add (new Integer(i)); 

            }            

            CellAnimator animator = new CellAnimator (selections.toArray());            

            animator.start();

        } 

    }

public static void main (String[] args) {

        JList list = new AnimatedJList ();

        DefaultListModel defModel = new DefaultListModel();

        list.setModel (defModel);

        String[] listItems = {

            "Chris", "Joshua", "Daniel", "Michael",

            "Don", "Kimi", "Kelly", "Keagan"

    };

    Iterator it = Arrays.asList(listItems).iterator();

    while (it.hasNext())

        defModel.addElement (it.next());

    // show list

    JScrollPane scroller =

        new JScrollPane (list, 

                         ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 

                         ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

    JFrame frame = new JFrame ("Checkbox JList");

    frame.getContentPane().add (scroller);

    frame.pack();

    frame.setVisible(true);

     }



 class CellAnimator extends Thread {

     Object[] selections;

     long startTime;

     long stopTime;

     public CellAnimator (Object[] s) {

         selections = s;

     }

     public void run() {

         startTime = System.currentTimeMillis(); 

         stopTime = startTime + ANIMATION_DURATION;            

         while (System.currentTimeMillis() < stopTime) {

              colorizeSelections();

              repaint();

              try { Thread.sleep (ANIMATION_REFRESH); }

              catch (InterruptedException ie) {}

         }

         // one more, at 100% selected color

         colorizeSelections();

         repaint();

     }



   // colorizeSelections() listing below



      // AnimatedCellRenderer listing below 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Turn Methods into List Renderers
Inhaltsvorschau
By using a little bit of reflection, you can make a generic ListCellRenderer that can render data using any method at runtime.
JLists, like JTable and JTree, use a decorator pattern to customize how they look. This system of cell renderers works well, but it can require you to build a unique renderer class for each type of object you want to put into your lists. Often, all you really want to do is call a particular method on the objects in your list, but writing a complete class to just call one method is a lot of work for such a small task. This hack shows you how to use reflection to create a generic cell renderer that can be reused on any object without subclassing.
The default JList cell renderer will just call toString() on the objects in the list and draw the resulting string to the screen. This is fine for simple uses where you really are just looking at a list of strings or objects with appropriate toString() methods. More complicated applications—and they all become more complicated eventually—require more complicated objects, and those objects might not have a convenient or useful toString() method. Eventually, you have to write a custom renderer for the particular object you wish to store. But there is another way: reflection.
Reflection lets you programmatically discover and access methods and fields in a java class at runtime. For this hack, you will use reflection to call an arbitrary method. This will allow the programmer using your generic renderer to specify a method using a string. This method will be used to render the component. Because you will be using reflection, you don't need to know the kind of objects in the list. As long as a method with the requested name exists, you can call it and get a value out. This will work even if some of the objects in the JList have different types. But let's not get ahead of ourselves. First, you need a basic cell renderer, as seen in Example 2-21.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create a Collections-Aware JComboBox
Inhaltsvorschau
You've moved on from Vector; your combo boxes should, too.
JComboBox is one of Swing's oldest components. Unfortunately, it accepts arrays of objects and Vectors only. Now that Collections objects like List have been part of the JDK for years, it would be nice to use them directly in a combo box without shuffling objects in and out of arrays. Fortunately, the JComboBox uses an MVC (Model-View-Controller) architecture, so you can solve this problem with a simple implementation of a ComboBoxModel.
To start, you need to figure out what the custom model should do. For our purposes, it needs to accept a List in the constructor and preserve any ordering supplied. Another nifty feature would be automatic updates. If you add or delete values to the List, the combo box should update itself automatically. Example 2-22 is a good start.
Example 2-22. A basic combo box to accept lists
public class ListComboBoxModel implements ComboBoxModel {

	protected List data;

	

	public ListComboBoxModel(List list) {

		this.listeners = new ArrayList();

		this.data = list;

		if(list.size() > 0) {

			selected = list.get(0);

		}

	}

	

	protected Object selected;

	public void setSelectedItem(Object item) {

		this.selected = item;

	}

	public Object getSelectedItem() {

		return this.selected;

	}



	public Object getElementAt(int index) {

		return data.get(index);

	}

	public int getSize() {

		return data.size();

	}



	protected List listeners;

	public void addListDataListener(ListDataListener l) {

		listeners.add(l);

	}

	public void removeListDataListener(ListDataListener l) {

		this.listeners.remove(l);

	}

}
This implementation is pretty much what you'd expect. Each method in ComboBoxModel is implemented (along with its parent interface, ListDataModel). The constructor saves a reference to the List and selects the first element if there is one. The selectedItem accessor works as expected, using the selected variable. getElementAt() and getSize() both pass the work on to the underlying
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 3: Tables and Trees
Inhaltsvorschau
A table component was one of the most obvious missing features in AWT, and among the most welcome additions when Swing came out. However, the JTable may be used too much—it's easy to throw an Object[][] at the constructor and get a full-blown GUI table, and some developers don't question the wisdom of this sort of coding.
But despite the generosity of the Swing JTable API, there are a few things still missing. Wouldn't it be nice if the table model keep itself sorted, or if the column widths had a non-ugly default that takes their contents into account? Well, you didn't buy this book to argue API theory—the point here is to hack things into shape. So, let's get started.
A one-digit column does not need 100 pixels of dead space. You know this; your JTables should, too.
Does Figure 3-1 look like your typical JTable?
Figure 3-1: JTable with default column sizing
If it does, then we have a usability problem to discuss. By default, the columns of a JTable are all the same size. For this data, that's obviously a terrible decision—there's far too much space reserved for the numbers in the count column, and not nearly enough in the URL column. So, how are you going to fix this?
If you said "turn on the horizontal scrollbar," please close this book, grasp it with both hands, and firmly smack yourself in the head with it. No, you are not turning on the horizontal scrollbar! Use the pixels available to you before you resort to the user-annoying desperation of horizontal scrolling. In this case, the count column has lots of pixels to spare; you just need to reallocate this extra space to the URL column.
What makes programmatic column resizing difficult for many Swing programmers is that they can't even find the right methods to use. If all you ever work with is JTable (and maybe a few custom cell renderers), you'll notice that the JavaDoc for those classes says nothing about column widths. The problem may be that the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 21–27: Introduction
Inhaltsvorschau
A table component was one of the most obvious missing features in AWT, and among the most welcome additions when Swing came out. However, the JTable may be used too much—it's easy to throw an Object[][] at the constructor and get a full-blown GUI table, and some developers don't question the wisdom of this sort of coding.
But despite the generosity of the Swing JTable API, there are a few things still missing. Wouldn't it be nice if the table model keep itself sorted, or if the column widths had a non-ugly default that takes their contents into account? Well, you didn't buy this book to argue API theory—the point here is to hack things into shape. So, let's get started.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Size Your Columns to Suit Your JTable's Contents
Inhaltsvorschau
A one-digit column does not need 100 pixels of dead space. You know this; your JTables should, too.
Does Figure 3-1 look like your typical JTable?
Figure 3-1: JTable with default column sizing
If it does, then we have a usability problem to discuss. By default, the columns of a JTable are all the same size. For this data, that's obviously a terrible decision—there's far too much space reserved for the numbers in the count column, and not nearly enough in the URL column. So, how are you going to fix this?
If you said "turn on the horizontal scrollbar," please close this book, grasp it with both hands, and firmly smack yourself in the head with it. No, you are not turning on the horizontal scrollbar! Use the pixels available to you before you resort to the user-annoying desperation of horizontal scrolling. In this case, the count column has lots of pixels to spare; you just need to reallocate this extra space to the URL column.
What makes programmatic column resizing difficult for many Swing programmers is that they can't even find the right methods to use. If all you ever work with is JTable (and maybe a few custom cell renderers), you'll notice that the JavaDoc for those classes says nothing about column widths. The problem may be that the JTable is so generous with helpful methods that you'd never even notice that it's made up of TableColumn objects. Take a look at that TableColumn's JavaDoc, and you'll find getters and setters for minimum, maximum, and preferred widths for columns.
As with components that defer to layout managers, the right property to reset is the preferred width—let the column tell the JTable how wide it would like to be, but let the JTable make the final decision based on the information available to it (after all, there could be other columns contending for space, there might not be enough space for all the columns'preferred size, etc.).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Column Selection to JTables
Inhaltsvorschau
So, why can't I select a column by clicking on its header?
Here's something that seems strange about JTables: you can click on the column headers, but only for the purpose of reordering columns—not for selecting the contents of that column. I don't know about you, but considering that I almost never reorder columns, it seems like the default behavior is backward. And if your users have had their expectations set by working with Excel or other spreadsheets, they'll surely expect the ability to select an entire column.
The to-do list for adding column selectability to a JTable consists of two items:
  • Change which kinds of multiselection are allowed.
  • Wire up a MouseListener.
Example 3-3 shows a very simple implementation.
Example 3-3. A JTable that allows column selection by clicking on column headers
	public class ColumnSelectableJTable extends JTable {

		public ColumnSelectableJTable (Object[][] items, Object[] headers) {

			super (items, headers);

			setColumnSelectionAllowed (true);

			setRowSelectionAllowed (false);

			// set up action listener on table header

			finalJTableHeader header = getTableHeader();

			header.addMouseListener (new MouseAdapter() {

			public void mouseReleased (MouseEvent e) {

				if (! e.isShiftDown())

				clearSelection();

				int pick = header.columnAtPoint(e.getPoint());

				addColumnSelectionInterval (pick, pick);

			}

		});



			}

		}
The constructor is deliberately simple, taking only a two-dimensional array of contents and a one-dimensional array of headers. Of course, JTable has many more constructor signatures than this, but this is the one that will be easiest to expose to a test class (do I have to mention that building out the other constructors is left to the reader as an exercise?).
The next step is changing the defaults for multi-cell selection. The default is to allow row selection—exactly what you don't want. So, enable column selection and disable row selection. Next, you want to catch clicks on the headers so you can select columns in response to them. Unfortunately, the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Let Your JTables Do the Sorting
Inhaltsvorschau
Why doesn't Swing already offer this? Oh well, here's how to do it yourself.
It's hard to imagine you'll do much serious work with JTables without needing to sort the contents by one of the columns, or support changing between columns to use as the sort criteria. In fact, given how generous the Swing API usually is, it's kind of surprising that it doesn't already offer it. Oh well, it's not that hard to do for yourself.
There are a couple of approaches you could take to solve this problem. You could create a subclass of TableModel, one that keeps an internal Comparator to do the sorting and resorts every time an add() or remove() type method is called. The drawback to this approach is choosing which of the model classes to subclass. If you go too high up the hierarchy by implementing TableModel or subclassing AbstractTableModel, you would miss some typical Swing functionality that developers expect, like the ability to add and remove rows provided by DefaultTableModel. On the other hand, if you subclass DefaultTableModel, other developers will be unhappy because subclassing your class requires them to pick up public add() and delete() type methods that expose their data in ways they don't want.
So, consider an alternative: two table models, one that the JTable sees and another that the developer sees. Specifically, the developer will pass her TableModel to the constructor of the sorting model, which will wire up for events on the model. Then, the developer will set the sorting model as the JTable's model. Changes in the base model will force the sorting model to resort its contents and then fire off events to JTable to drive updates to the onscreen representation.
There are more details in the actual implementation of course, particularly when it comes to doing the sorting. Example 3-5 shows the code for the SortableTableModel.
Example 3-5. Self-sorting TableModel
	public class SortableTableModel implements TableModel, 

		TableModelListener {

		EventListenerList listenerList = new EventListenerList();

		TableModel delegatedModel;

		int[] sortedIndicies;

		int sortColumn;

		Comparator comparator;

		Comparator[] comparators;



		public SortableTableModel (TableModel tm) {

			delegatedModel = tm;

			delegatedModel.addTableModelListener (this);

			comparators = new Comparator [tm.getColumnCount()];

			sortedIndicies = new int [0];

			setSortColumn (0);



		}

		// listener stuff

		public void addTableModelListener (TableModelListener l) {

			listenerList.add (TableModelListener.class, l);

		}



		public void removeTableModelListener (TableModelListener l) {

			listenerList.remove (TableModelListener.class, l);

		}



		public void fireTableModelEvent (TableModelEvent e) {

			Object[] listeners = listenerList.getListenerList();

			for (int i = listeners.length-2; i>=0; i-=2) {



		if (listeners[i] == TableModelListener.class) {

			((TableModelListener) listeners[i+1]).tableChanged(e);

		}

			}

		}



		// contents stuff

		public Class getColumnClass(int columnIndex)

			if (delegatedModel.getRowCount() > 0)

		return delegatedModel.getValueAt(0, columnIndex).getClass();

			else

		return Object.class;

		}



		// getColumnCount(), getColumnName(), getRowCount(),

		// getValueAt(), isCellEditable(), setValueAt() listings below



		// internal helpers

		public void setComparatorForColumn (Comparator c, int i) {



		// range check

		if (i > comparators.length) { 

			Comparator[] newComparators = new Comparator[i+1]; 

			System.arraycopy (comparators, 0,

				  newComparators, 0, 

				  comparators.length);

			comparators = newComparators;

		}

		// add the comparator

		comparators[i] = c;

		

	}

	public void setSortColumn (int i) {

		sortColumn = i;



		// reset current comparator, possibly to null, which 

		// will make us use "natural ordering" for those values 

		comparator = null; 

		if ((comparators != null) &&

			(comparators.length > 0))

			// is there one in the list of comparators?

			comparator = comparators[sortColumn];

			

		// now do the sort

		resort();

	}



	public int getSortColumn () {

		return sortColumn;

	}



	// resort() method listed below 

	//SortingDelegate inner class listed below 

	// SortingDelegateComparator inner class listed below

	

	public void tableChanged (TableModelEvent e) {                  

				switch (e.getType()) { 

				case TableModelEvent.DELETE: {

				resort();                         

				fireAllChanged(); 

				break;

				}



				case TableModelEvent.INSERT: { 

				resort();  

				fireAllChanged(); 

				break;

				}

				case TableModelEvent.UPDATE: {

				resort(); 

				fireAllChanged(); 

				break; 

			}



				}

	}



	protected void fireAllChanged() {

		TableModelEvent e = new TableModelEvent (this);

		fireTableModelEvent (e);

	} 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create a JDBC Table Model
Inhaltsvorschau
Bring your database tables into Swing with a minimum of hassle.
If you've worked with databases, you've probably also worked with the tools they provide for quick table maintenance and queries: command-line tools that are well suited to brief hack-and-slash work, but hard to work with once you start dealing with any serious amount of data. It's hard enough to write the SQL command to return 10 or 20 columns in a query—it's even worse when the results word-wrap over the course of a dozen lines, and you can't tell where one result ends and another begins.
Wouldn't it be nice to be able to throw the contents of any database table into a Swing JTable? Give it a few JDBC strings, toss it in a JFrame, and pow!—instant GUI.
If you've worked with both JDBC and Swing, you'll grasp the concept in one sentence: use table metadata to build a Swing TableModel from the database table. If you haven't, here's the background you'll need: JDBC provides an abstract means of accessing databases. Java code to work with one database should work with another, the only difference is in the way that JDBC achieves a Connection to the database, which is usually a matter of providing Strings for:
  • A driver class, which provides implementations of the various java.sql interfaces.
  • A URL with which to connect to the database. This implies the use of sockets, though that's not necessarily the case. Some small embeddable databases can live in the same JVM as your application.
  • An optional username.
  • An optional password.
Once you have the Connection, you can begin to send commands (creation, deletion, and altering of tables) or queries to the database by creating Statements from the Connection. You can also use the Connection to get metadata about the database, like what kinds of features it supports, how long certain strings can be, etc. More importantly for this hack, it allows you to discover what tables are in the database, what columns they have, and what types of data are in those columns.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Export Table Data to an Excel Spreadsheet
Inhaltsvorschau
I don't want an entire spreadsheet API, I just want to get a table of values into Excel.
Most corporate intranet applications require interfacing with standard office software, which usually means Microsoft Word and Excel. Interfacing with Microsoft products can be tricky business. Whole suites of products have been created just to address this issue. One of the most commonly requested features is generating a report from the data in a JTable. You could use a library like Poi (http://jakarta.apache.org/poi/) to read and write Excel files natively, but most of the time that's overkill—you probably don't need to support Excel formulas or complicated formatting. All most users really want to do is dump tabular data into a file that will open in Excel with a double-click. And with a little bit of cleverness, you can do just that.
Excel uses a complicated database-oriented format for its native .xls files. This format defines the formulas, colors, charts and every other advanced feature Excel has supported over the years. Writing to the native .xls format is complicated but, fortunately, Excel supports other formats. The one I'm going to target is known as a tab-delimited text file, so called because tabs separate each field. This format is just plain text, so it will be super easy to write from Java, and open up in Excel with just a double-click.
Tab-delimited files separate each field with a tab character and each row with a standard Unix line break, \n. Since Swing defines a convenient getValueAt() method in the TableModel interface, it's very easy to just loop through the table cells and write it out to a file, as seen in Example 3-14.
Example 3-14. Exporting tab-delimited data from a TableModel
	public class ExcelExporter {

		public ExcelExporter() { }

		public void exportTable(JTable table, File file) throws IOException {

			TableModel model = table.getModel();

			FileWriter out = new FileWriter(file);

			for(int i=0; i < model.getColumnCount(); i++) {

		out.write(model.getColumnName(i) + "\t");

			}

			out.write("\n");



			for(int i=0; i< model.getRowCount(); i++) {

		for(int j=0; j < model.getColumnCount(); j++) {

			out.write(model.getValueAt(i,j).toString()+"\t");

			}

			out.write("\n");

		}



		out.close();

		System.out.println("write out to: " + file);

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Search Through JTables Easily
Inhaltsvorschau
Use this nifty TableModel decorator to search your JTables with minimal fuss.
Tables have a tendency to get very big; thousands of rows are not uncommon. But this causes some severe navigational issues for your users, like extremely small scrollbar handles, which make it difficult for them to find the information they need. One way to get around these navigational issues is to allow your users to search the table data rather than displaying it all. This hack shows you how to simply search your tables using the Apache open source Lucene search engine.
Rather than a custom TableModel with integrated Lucene functionality, you can build a TableModel decorator instead. This will allow you to search preexisting TableModels without modifying them directly.
This works by keeping a set of links to an internal table model based on search criteria. For example, say you have 10 rows in your original table model, and you have a search that limits the results to 5 of those rows. Your inner TableModel will remain unchanged, but your TableModel decorator will have links to only five of the inner TableModel rows—making it look like it only has five rows of data.
Start by creating a class called TableSearcher that implements TableModel.
Next, create a simple decorator (or wrapper) that implements all of the TableModel methods and forwards the calls to the inner model. Depending on your IDE, you may be able to automate the process (IntelliJ IDEA does it for me). You'll have to modify a few of the methods, but most are going to remain unchanged. You don't have to touch the getColumnName() and getColumnClass() methods, for example. Just forward them to the inner TableModel:
	public String getColumnName(int column) {

		return tableModel.getColumnName(column);

	}



	public Class getColumnClass(int column) { 

		return tableModel.getColumnClass(column); 

	}
The getColumnCount() method is slightly different in that you have to check if the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Animate JTree Drops
Inhaltsvorschau
Who said working with tree paths was hard? Now you can reorganize tree hierarchies with drag-and-drop.
JTrees are great for representing hierarchy, but they're not so hot as control widgets. You might want to drag items inside a tree, or accept a drop from some other part of your application, and it turns out not to be well suited to that. The problem is that the JTree isn't really a container, so from the Swing programmer's point of view, you see the tree's visual representation, but not the nodes within it.
The goal of this hack is to take a JTree, like the one shown in Figure 3-11, and allow you to reorganize it through drag-and-drop. The bulk of the work will be in animating and handling the drop. The payoff is that making a single tree reorderable will also get you most of the way to making it a good drag-and-drop participant with the rest of your application, since supporting drag-and-drop within the JTree requires you to make the tree a drag source and a drop target.
If any of the forgoing sounds familiar, it should. Bringing drag-and-drop to the JTree is very similar to supporting it for the JList. In fact, the JTree and the JList have a lot in common—both use cell renderers, both are typically put in JScrollPanes, etc.
Figure 3-11: JTree with drag-and-drop reordering
In fact, the code for this hack started as a straight port of the reorderable JList hack [Hack #17] , with obvious changes for the different helper classes (TreeCellRenderer instead of ListCellRenderer) and different handling of the model, since tree models are hierarchical.
To recap what needs to be done:
  • The tree needs to implement the DragGestureListener (to start a drag), the DropTargetListener (to handle the drop), and the DragSourceListener (only to get the end-of-drop callback).
  • The drag-and-drop implementations need to use the coordinates provided by drag-and-drop events to map to nodes of the tree as either the node to drag or as potential drop targets.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 4: File Choosers
Inhaltsvorschau
Ah, the poor JFileChooser. It's probably not anybody's favorite class in Swing, and it's quite likely the anti-favorite of many developers. Prior to Java 1.4, the Metal version had an "icon view" button that actually didn't do anything—not that it was worth writing home about once it was implemented. Little glitches like this make JFileChooser the whipping boy of many Swing developers. Apple's Java guidelines for Mac OS X actually advocate giving up on the JFileChooser altogether and going back to the AWT FileDialog!
This chapter is here to…well, if not to praise the JFileChooser, then certainly not to bury it either. This is Swing after all, and that means there are all sorts of places that you can hack in and embellish functionality to give the user more power, or to make a component smarter about the contents it displays.
Improve the native platform fidelity of the JFileChooser by adding a contextual menu that lets the user create new folders and delete files.
The standard JFileChooser that comes with Swing is quite limited. It doesn't follow shortcuts or any other linked files. It doesn't have image previews or even a right-click menu. These are all features that users expect to see. Worse, these are the kinds of details that continue to reinforce the belief that Swing apps are inferior to native ones.
This hack will tackle the first limitation, the lack of a context menu. Some platform file choosers—Windows Explorer in particular—provide users with a context menu, also known as a right-click menu. This provides fast access to commonly used functions like Delete and New Folder. Java 5.0 finally added a context menu to the file chooser, but if you want to target the millions of users out there with older versions of Java, then you need your own implementation. This hack creates a right-click menu on the JFileChooser to give the user those missing features.
The goal of this task is to create a contextual menu. This means it's a menu that pops up when you hit the right mouse button (or Control-click on one-button mice). It also is
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 28–32: Introduction
Inhaltsvorschau
Ah, the poor JFileChooser. It's probably not anybody's favorite class in Swing, and it's quite likely the anti-favorite of many developers. Prior to Java 1.4, the Metal version had an "icon view" button that actually didn't do anything—not that it was worth writing home about once it was implemented. Little glitches like this make JFileChooser the whipping boy of many Swing developers. Apple's Java guidelines for Mac OS X actually advocate giving up on the JFileChooser altogether and going back to the AWT FileDialog!
This chapter is here to…well, if not to praise the JFileChooser, then certainly not to bury it either. This is Swing after all, and that means there are all sorts of places that you can hack in and embellish functionality to give the user more power, or to make a component smarter about the contents it displays.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add a Right-Click Context Menu to the JFileChooser
Inhaltsvorschau
Improve the native platform fidelity of the JFileChooser by adding a contextual menu that lets the user create new folders and delete files.
The standard JFileChooser that comes with Swing is quite limited. It doesn't follow shortcuts or any other linked files. It doesn't have image previews or even a right-click menu. These are all features that users expect to see. Worse, these are the kinds of details that continue to reinforce the belief that Swing apps are inferior to native ones.
This hack will tackle the first limitation, the lack of a context menu. Some platform file choosers—Windows Explorer in particular—provide users with a context menu, also known as a right-click menu. This provides fast access to commonly used functions like Delete and New Folder. Java 5.0 finally added a context menu to the file chooser, but if you want to target the millions of users out there with older versions of Java, then you need your own implementation. This hack creates a right-click menu on the JFileChooser to give the user those missing features.
The goal of this task is to create a contextual menu. This means it's a menu that pops up when you hit the right mouse button (or Control-click on one-button mice). It also is contextual, or context sensitive. This means the menu changes—or does something different—depending on what you currently have selected. In this case, there will be two actions: Delete will delete the currently selected file or directory, if one is selected; New Folder will create a new folder (called, not surprisingly, New Folder) in the current directory. Both of these actions depend on the current state of the file selection, so they are considered context sensitive.
For the most part, Swing is quite extensible, but often only in ways that the Swing team thought of beforehand. The JFileChooser has ways to change the rendering of file icons, filtering the file list, adding components, and changing the text. It does not have any way to add pop-up menus, though. In short, we'll have to hack it, and there's no better way to start than by reusing another hack.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Display Shortcuts in the JFileChooser
Inhaltsvorschau
This hack will customize the JFileChooser to recognize shortcut ( linked) folders and overlay them with a link graphic, mimicking the native Windows File Explorer.
Another of JFileChooser's glaring bugs is the lack of any support for linked directories. This is hardly surprising, as Java itself has no understanding of linked directories. Most operating systems support linked files, however, and often indicate to the user that a file is linked—for example, by drawing an arrow overlaid on top of the folder or directory icon. Compare the typical JFileChooser in Figure 4-2 to the standard Windows file chooser in Figure 4-3. There's more than a small difference! No wonder it's hard to get folks to move to Swing.
Figure 4-2: Normal JFileChooser
Like every Swing component, the look of the JFileChooser is controlled by the installed Look and Feel (L&F). However, the JFileChooser also uses a custom class similar to a table cell renderer for drawing the actual files and folders. That class, FileView, is the best place to start hacking the JFileChooser's display.
The FileView contains five methods that determine the names, icons, and other attributes that are actually displayed in a JFileChooser. By overriding these methods, you can change the look or text of any file. To draw shortcuts as linked folders, you just need to override the getIcon() and isTraversable() methods. getIcon() returns the icon to use when drawing the file.
Figure 4-3: Standard Windows file chooser
isTraversable() tells the file chooser if a given file is a directory type of object, meaning the user can click and open it to list new files. These two methods will transform a shortcut.lnk file into a shortcut directory with the right icon.
For the sake of brevity, this is a Windows-specific hack. You should be able to modify this hack easily to work with sym-links on Unix and Linux, as well as Mac OS X. Consider it homework!
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Real Windows Shortcut Support
Inhaltsvorschau
Support Windows shortcuts by actually opening and parsing files with the under-documented LNK format.
It's one thing to properly display Windows shortcuts [Hack #29] by looking for the .lnk extension and changing the default icon to look like a link. But there's a glaring flaw: when you click on the shortcut it doesn't actually link anywhere! This hack will make the shortcuts really work by hacking into the undocumented shortcut files themselves.
Since links are not supported natively by the filesystem, Windows fakes it by storing the shortcut metadata (path, icon, and other information) in a .lnk file. When you click on the shortcut, the windows file manager reads the LNK file, extracts the target file/directory path, and then opens a new window at the real location. Your Java program can do the exact same thing using a custom FileSystemView. The only tricky part is actually parsing the LNK files.
Microsoft has never documented the LNK file format, preferring native Windows developers to use system APIs for all manipulation. Creative hackers on the Web have reverse engineered most of the format, which fortunately includes the parts you need to extract target filepaths.
Jesse Hager has compiled a great PDF describing the format in detail. I used that document to write the code in this hack. You can read the full document at http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf.
The LNK files are binary data broken up into a header followed by a few optional blocks of data. The format provides offsets that make parsing it easy. The following code is the beginning of a LNK parser:
	public class LnkParser {

		public LnkParser(File f) throws Exception {

			parse(f);

		}

		public void parse(File f) throws Exception {

			// read the entire file into a byte buffer

			FileInputStream fin = new FileInputStream(f);

			ByteArrayOutputStream bout = new ByteArrayOutputStream();

			byte[] buff = new byte[256];

			while(true) {

				int n = fin.read(buff);

				if(n == -1) { break; }

				bout.write(buff,0,n);

			}

			fin.close();

			byte[] link = bout.toByteArray();
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Image Preview to File Choosers
Inhaltsvorschau
This hack will show you how to add an image previewer to a JFileChooser, and it will set you on the way toward building your own customizations.
We've already talked about JFileChooser's numerous limitations. Not surprisingly, many applications have their own custom choosers and extensions to support things like image previews. The standard JFileChooser was designed to mimic only the most common features, but it does provide a way to add your own enhancements.
The standard JFileChooser looks like most native file choosers. It has a directory selector, a list of files, and select and close buttons. There may also be a toolbar of sorts. If you want to build your own customized file chooser, you could do it the same way platform-specific file choosers are implemented—through L&F code. This would entail subclassing javax.swing. plaf.basic.BasicFileChooserUI, working around the private methods, and possibly reimplementing the whole thing, none of which is easy or fun. Fortunately, the JFileChooser API provides a simple extension hook in the form of the setAccesory() method. This method lets you add any JComponent to an existing JFileChooser, thereby adding your own features without mucking around in file chooser code.
In this hack, you'll learn how to create an image previewer. This is a component that shows a thumbnail view of the currently selected file—if that file is an image. It will also show the dimensions of the image. Because Java 1.4 provides robust image support with the javax.imageio API, this should be pretty easy. The first step is a custom ImagePreview component:
	public class ImagePreview extends JPanel implementsPropertyChangeListener {

		private JFileChooser jfc;

		private Image img;



		public ImagePreview(JFileChooser jfc) {

			this.jfc = jfc;

			Dimension sz = new Dimension(200,200);

			setPreferredSize(sz);

		}
The code defines the ImagePreview class, a subclass of JPanel. I've hardcoded the size of the component to 200 x 200—large enough to tell what the image is, but small enough not to impact the file chooser very much.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Preview ZIP and JAR Files
Inhaltsvorschau
This hack will show you how to customize the file chooser to let users navigate and load files from inside a ZIP or JAR file archive.
Most modern operating systems now have built-in support for compressed files, usually in the form of ZIP files. You can open up a ZIP file and navigate the contents fromwithin the standard file browser or dialog box, all without actually uncompressing anything. Surprisingly, JFileChooser doesn't support ZIP files, even though Java has built-in ZIP support in the java.util.zip package. And, since JAR files are in the same format, you get two-for-one today!
Before you read any further, I want to warn you that this is one of the longest and most complicated hacks in the book. I don't want to scare you away, but the code is pretty dense. Of course, you wouldn't expect any less from a hacks book, right? What you will learn from this hack, however, will let you build custom filesystem views for any type of datasource, including FTP, WebDAV, or even SQL databases. I think the complexity is worth it. Plus, you'll learn more about how the File object really works and how to hack it to pieces.
The JFileChooser uses a FileSystemView to access the real filesystem. This view, unfortunately, assumes the existence of actual java.io.File objects. There is no way to represent a filesystem without Files, which wouldn't be a problem except that File is a real class with many methods, not a simple interface. Fortunately, File is not declared final. You can override each method with your own version to redirect the calls to another object, and that's exactly how this hack works. By creating proxies around the real ZIP file objects, you can fool the FileSystemView into working with items that aren't real files.
ZIP files are represented in the java.util.zip package by a ZipFile object that contains one ZipEntry for each compressed file it contains. To show the compressed files in the chooser, each
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 5: Windows, Dialogs, and Frames
Inhaltsvorschau
For four chapters, we've hacked away at Swing widgets, from JLabels to JTables, without worrying too much about the context in which they're shown to the user. And yet, every Swing widget must ultimately be contained in some kind of window to be on the screen at all. It's not an exaggeration to say that many competent Swing programmers don't even know or care about the hierarchy of AWT Windows, Dialogs, and Frames or their Swing equivalents, JWindow, JDialog, and JFrame. Yet, it's these same programmers who don't know that commercial components like splash screens are all possible in Swing; they see dialogs and frames and assume everything has a titlebar. This is hardly true, though—you can easily remove the decorations of a dialog, or just work with the window superclass.
Suffice it to say there's much you can do with windows and their subclasses. So much so, in fact, that it fills two chapters. This chapter will deal with hacks that deal with placing, moving, and resizing windows in ways that are fairly consistent with the design of the window classes. The next chapter will be a lot more aggressive in breaking the rules.
Make your windows snap to the edges of the screen by using a special event listener.
Back in the prehistoric days of desktop software, as graphics programs were being invented, they solved the problem of managing the drawing tools by creating mini-windows called palettes (and their later variation, toolbars). Eventually, the programs had so many palettes that the users grew frustrated trying to organize them. Lining them up on the edge of the screen was particularly nasty, so fledgling young programmers took it upon themselves to create snappable windows. These were windows that were magnetic (metaphorically speaking) and could align themselves to the screen's edges. This hack demonstrates how to recreate this technique with Java.
The idea is simple: you check whenever the user moves the window. If the window is off the screen, then move it back to the edge. Moving the window is pretty easy. The trickier part is knowing when the window has moved. Fortunately, AWT has an answer: the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 33–40: Introduction
Inhaltsvorschau
For four chapters, we've hacked away at Swing widgets, from JLabels to JTables, without worrying too much about the context in which they're shown to the user. And yet, every Swing widget must ultimately be contained in some kind of window to be on the screen at all. It's not an exaggeration to say that many competent Swing programmers don't even know or care about the hierarchy of AWT Windows, Dialogs, and Frames or their Swing equivalents, JWindow, JDialog, and JFrame. Yet, it's these same programmers who don't know that commercial components like splash screens are all possible in Swing; they see dialogs and frames and assume everything has a titlebar. This is hardly true, though—you can easily remove the decorations of a dialog, or just work with the window superclass.
Suffice it to say there's much you can do with windows and their subclasses. So much so, in fact, that it fills two chapters. This chapter will deal with hacks that deal with placing, moving, and resizing windows in ways that are fairly consistent with the design of the window classes. The next chapter will be a lot more aggressive in breaking the rules.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Window Snapping
Inhaltsvorschau
Make your windows snap to the edges of the screen by using a special event listener.
Back in the prehistoric days of desktop software, as graphics programs were being invented, they solved the problem of managing the drawing tools by creating mini-windows called palettes (and their later variation, toolbars). Eventually, the programs had so many palettes that the users grew frustrated trying to organize them. Lining them up on the edge of the screen was particularly nasty, so fledgling young programmers took it upon themselves to create snappable windows. These were windows that were magnetic (metaphorically speaking) and could align themselves to the screen's edges. This hack demonstrates how to recreate this technique with Java.
The idea is simple: you check whenever the user moves the window. If the window is off the screen, then move it back to the edge. Moving the window is pretty easy. The trickier part is knowing when the window has moved. Fortunately, AWT has an answer: the ComponentListener interface.
In Java, every UI component (in both AWT and Swing) fires events whenever it moves, resizes, is shown, or is hidden. Any class can receive these events by implementing the ComponentListener interface. For the purposes of this hack, you only need the componentMoved event, so start by subclassing ComponentAdapter, which provides default no-operation implementations of all of ComponentListener's declared methods. Then just override the componentMoved() method, as seen in Example 5-1.
Example 5-1. A ComponentListener to snap a window into place
public class WindowSnapper extends ComponentAdapter {



	public WindowSnapper() { }



	private boolean locked = false;

	private int snap_distance = 50;



	public void componentMoved(ComponentEvent evt) {

		if(locked) return;

		Dimension size = Toolkit.getDefaultToolkit().getScreenSize();

		int nx = evt.getComponent().getX();

		int ny = evt.getComponent().getY();

		// top

		if(ny < 0+snap_distance) {

			ny = 0;

		}

		// left

		if(nx < 0+snap_distance) {

			nx = 0;

		}

		// right

		if(nx > size.getWidth() - evt.getComponent().getWidth() -

				snap_distance) {

			nx = (int)size.getWidth()-evt.getComponent().getWidth();

		}

		// bottom

		if(ny > size.getHeight() - evt.getComponent().getHeight() -

				snap_distance) {

			ny = (int)size.getHeight()-evt.getComponent().getHeight();

		}

		

		// make sure we don't get into a recursive loop when the

		// set location generates more eventslocked = true;

	    evt.getComponent( ).setLocation(nx,ny);

		locked = false;

	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make a Draggable Window
Inhaltsvorschau
Drag a window by clicking on its background using a special event listener.
Most windows let you move them by dragging the titlebar. Some program windows, however, don't have titlebars. In the age of eye-candy interfaces (see iTunes and WinAmp for prime examples) it is very common to have a window—possibly non-rectangular—without any titlebar or window controls at all. This makes for a pretty window, but how do you move it? Simply by dragging any available space on the window. Though not terribly intuitive, such programs are commonplace, and this book wouldn't be called Swing Hacks without providing a Java implementation of draggable windows, even when no titlebar is used.
The simplest approach to this problem is to create a listener that simply catches all drags and moves the window:
            public class MoveMouseListener implements MouseListener, MouseMotionListener

            {

                JComponent target;

                JFrame frame;

                public MoveMouseListener(JComponent target, JFrame frame) {

                    this.target = target;

                    this.frame = frame;

                }

               

                public void mouseClicked(MouseEvent e) {}

                public void mouseEntered(MouseEvent e) {}

                public void mouseExited(MouseEvent e) {}

                public void mousePressed(MouseEvent e) {}

                public void mouseReleased(MouseEvent e) {}

                public void mouseMoved(MouseEvent e) {}

                public void mouseDragged(MouseEvent e) {

                    frame.setLocation(new Point(e.getX(),e.getY());

                }

            }
This class implements MouseListener and MouseMotionListener with no-ops for all methods except mouseDragged(), which moves the frame to the current mouse location. However, this approach has two problems. First, the mouse coordinates are going to be relative to the component, rather than the screen. Thus, a click on a 50 x 50 button in the bottom right of the screen might return (25, 25) when it should really be more like (1000, 700). The other problem is that the code moves the origin of the frame to the mouse cursor. This would look strange because the window would immediately jump so that its upper-left corner is right under the cursor. The proper behavior is for the window to stay in the same position
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Windows Resize Icons
Inhaltsvorschau
The Windows resize icons aren't built into Java. Here's how to make your own.
Windows has two standard icons to let users know that they can resize a window. I can't tell you why there are two or how to decide between them, but I can tell you how to reproduce both of them in Java. Note that this hack is concerned with painting the icons accurately—it will be up to your code to handle events on components that use these icons and handle them appropriately.
First take a look at the two icons. Figure 5-2 shows the icon used by Windows Explorer, MS Paint, and other applications; Figure 5-3 shows the icon used by Word and other Office applications.
The easiest and most flexible way to implement these icons is through the Icon interface. Using Icon allows you to change an icon's appearance easily if you need to match new system defaults. It's also a lot easier to implement transparency with an Icon than by making an image from the corner icon using a screen capture and editing it in a professional graphics application like Photoshop.
Figure 5-2: The Windows Explorer resize icon
Figure 5-3: The Windows MS Office resize icon
Icon is pretty simple, and it has only three methods:
         voidpaintIcon(Component c, Graphics g, int x, int y);



         int getIconWidth();



         int getIconHeight();
The getIconWidth() and getIconHeight() methods should be pretty easy to implement—you just need the pixel size of your custom icons. The paintIcon() method is where all the interesting stuff happens.
Figure 5-4 shows a huge blowup of what we'll call the Explorer icon.
At a glance, you can see the six squares in a triangular pattern with a subtle white 3D effect on the squares. The easiest thing to handle is the size, so start with that. This icon is 12 x 12 pixels (one square in Figure 5-4 equals one pixel).
Figure 5-4: The Windows Explorer icon zoomed in
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Status Bars to Windows
Inhaltsvorschau
Lots of applications use a bottom-of-window panel to communicate status. But Swing doesn't provide a consistent way to do this. So, you need to provide it yourself.
Many applications in Microsoft Windows use a status bar—an area at the bottom of each window (to the left of the resize box if there is one) that can be used to communicate summary information to the user in a compact form. Typical contents of a status bar might include what a web browser is doing (e.g., "Connecting to www.oreilly.com"), or, as in Figure 5-6, a summary of the contents of a folder, showing the number of contained items, their size, etc.
Figure 5-6: Windows Explorer's status bar
This is the standard MS Windows setup for a status bar:
  • An icon on the far right letting users know they can resize the application
  • A label on the left for free form text
  • Several labels on the right for details (e.g., 42.3 MB in Figure 5-6)
There is also a bit of custom painting involved to get the top and bottom shading right. First, you'll do the panel shading, and then loop back to the previous list and add all of the necessary components to the status bar.
This hack copies the Windows Explorer status bar in Windows XP. Different applications are slightly different. The purpose of this hack isn't to start a religious war about which application to copy. You can just make minor changes if you want to copy a different application's status bar (such as Word or Outlook, which are different than Windows Explorer and also from each other).
Start by creating a class called JStatusBar extending JPanel:
       public class JStatusPanel extends JPanel {

          //more to come

       }
Then add a constructor and set the preferred height to be 23 pixels (the height of the Windows Explorer status bar—count 'em up if you don't trust me). You can ignore the preferred width of the status bar since you'll be adding the status bar to your frame with a
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Save Window Settings
Inhaltsvorschau
Make sure your windows always show up right where you left them, even after a program restarts, by saving the window position and size automatically.
Swing is a rich toolkit that can be used to create many kinds of programs, but there are certain features that virtually all applications need, like window settings and preferences. This hack shows how to automatically store and retrieve window locations and dimensions in an existing program without using custom Frame subclasses, or even making many changes to your existing code.
Saving the size and location of a window is actually pretty easy. You can just store them in a file and retrieve them later. The difficulty is identifying each window, and doing it in a way that's as noninvasive as possible.
The first step is to create a class that handles all of the work. Because managing windows will be a global function of the entire program, start with a simple singleton with a factory interface, as shown in Example 5-4.
Example 5-4. Singleton for saving window settings
    public class WindowSaver implements AWTEventListener {



        private static WindowSaver saver;

        private Map framemap;

        

		private WindowSaver() {

            framemap = new HashMap();

        }



        public static WindowSaver getInstance() {

            if(saver == null) {

                saver = new WindowSaver();

        }

        return saver;

    }
The WindowSaver constructor creates a private map to store all of an application's frames. When each frame is loaded, the saver will store a reference to it in this map. Later, when the application needs to save or reload each window, it will use the map to find each frame again. The constructor is private so that only one instance (and one map) of the program ever exists in the JVM.
The WindowSaver also implements AWTEventListener. This is how it can find each frame. Swing has a global event queue that allows you to get every event throughout the JVM. You can access this queue by registering an
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Earthquake Dialog
Inhaltsvorschau
Make sure your users really know they got their password wrong.
One of the funny ways that Mac OS X uses animation in its UI is when the user logs in. If she enters an incorrect login and/or password, the whole login dialog shakes violently for a second, like a road sign thwacked with a bat, or a cartoon character who has just run full-speed into a solid object (say, a picture of a tunnel painted over a wall).
We like this effect a lot, so we thought we'd bring it to Swing. It's a pretty straightforward bit of animation, so we jazzed it up…with trigonometry!
Here's an initial queston: do you want to subclass JDialog and add the animation effect to that class, or create a class that animates the shaking on another JDialog? I thought that subclassing would be a bad choice because JOptionPane generates some very convenient JDialogs, and you wouldn't want to lose those. So, you'll have to have another class animate your dialogs.
I've called it DialogEarthquakeCenter because it'll be a class that monitors the shaking, just like seismologists do in their earthquake centers.
Obviously, the DialogEarthquakeCenter needs a reference to the dialog that it will be shaking. It also needs a few other values, which I've set as constants:
SHAKE_DISTANCE
The maximum distance in each direction the dialog should move.
SHAKE_CYCLE
The time in milliseconds for a complete cycle: center, right, center, left, back to center.
SHAKE_DURATION
Total time in milliseconds to shake the dialog.
SHAKE_UPDATE
How often (in milliseconds) to update the dialog's position and repaint. You might increase this if the CPU use is excessive, but animation smoothness decreases with less-frequent updates.
Beyond that, all you'll need to keep track of is where the dialog started (so you can put it back at the end of the animation), a running clock of how far you are into the animation, and where the dialog is located. Example 5-5 shows the code to put all this into action.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Spin Open a Detail Pane
Inhaltsvorschau
You don't want to bombard the user with details, but you don't want to hide them either. Here's a way to let the user pop open a More Info widget.
You have to be careful not to weigh down your GUIs with so much information that the user can't see what really matters. On the other hand, there are times that the user may want more information than is obvious on one screen or panel. A simple way of dealing with this is to put a More Info button that pops up a new window. That leads to the annoyance of having too many windows on the screen, none associated in any way with their source.
Mac OS X has a nice idea: the spin open disclosure widget. It works like this: you have a component of some sort—perhaps a simple label or a complex panel—with a triangle-shaped spinner below it. When the user clicks the spinner, a whole new widget opens up below the spinner, offering more information. In fact, the new widget can have significant functionality: to set file permissions from the Finder, you open a Get Info window and spin open an Ownership and Permissions section to set your own access (if you own the file), and a second Details spinner lets you set access levels for the owner, group, and others.
This hack is fairly simple and relies on one fact: you can add a component to a layout and alternately make it visible and invisible. Its position relative to other components is preserved when it's invisible, but it takes up no onscreen space. So, a spin-open container consists of three components:
  • The top component, which is always visible
  • The spinner
  • The bottom component, whose visibility can be set by clicking on the spinner
The layout of these three is pretty straightforward, as seen in Example 5-6, which lists the MoreInfoPanel class but omits an inner class (for now).
Example 5-6. Laying out the three panel components
          public class MoreInfoPanel extends JPanel {

 

               public Component topComponent;

               protected SpinWidget spinWidget;

               public Component bottomComponent;



               public static final int SPIN_WIDGET_HEIGHT = 14;



               public MoreInfoPanel (Component tc, Component mic) {

                   topComponent = tc;

                   spinWidget = new SpinWidget();

                   bottomComponent = mic;

                   doMyLayout();

               }



               protected void doMyLayout() {

                   setLayout (new BoxLayout (this, BoxLayout.Y_AXIS));

                   add (topComponent);

                   add (spinWidget);

                   add (bottomComponent);

                   resetBottomVisibility();

               }



                protected void resetBottomVisibility() {

                    if ((bottomComponent == null) ||

                       (spinWidget == null))

                        return;

                    bottomComponent.setVisible (spinWidget.isOpen());

                    revalidate();

                    if (isShowing()) {

                        Container ancestor = getTopLevelAncestor(); 

                        if ((ancestor != null) && (ancestor instanceof Window))        

                            ((Window) ancestor).pack();

              repaint();

                    }

                }

				

                public void showBottom (boolean b) {

                    spinWidget.setOpen (b);

                }



                 public boolean isBottomShowing () {

                     return spinWidget.isOpen();

                 }

                 // See below for SpinWidget inner class 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Minimize to a Mini-Frame
Inhaltsvorschau
When you want your program to have a smaller window but still be on the screen, try building a mini-mod.
Since the advent of iTunes, it seems that all consumer-oriented applications must have meticulous interfaces that can dynamically adjust themselves. Gone are the days of simply minimizing an application. Now your program must have a small version rather than (or in addition to) hiding when minimized. The smaller version contains limited controls but can fit nicely at the bottom of the screen. This hack shows how to create a dynamic frame that can switch properly between sizes for a more modern-looking interface.
Switching a frame between two sizes is quite easy: just call setSize( ) and you're done. Doing it well is a bit more difficult, however. When you minimize the window, you also need to remove the window decorations, hide the menu bar, and remove the components that shouldn't be visible in the mini-view. This is a bit trickier, not the least of which because you can't turn off the window decorations of a frame once it has been created. But I'm getting ahead of myself. First, you need a sample application.
Let's take a simple clock program. The normal window looks like Figure 5-11. The goal is to provide a mini version that looks like Figure 5-12.
Figure 5-11: A normal application window
Figure 5-12: A mini application window
This program has a clock, a panel with more configuration options (represented here with just the label More configuration), a menu bar, and a pop-up menu for later use. Example 5-9 creates the interface and puts the components in the right places, but it doesn't do anything with them yet.
Example 5-9. The beginning of a clock with a mini version
public class MiniMizeHack implements MouseListener, ActionListener {



    public JFrame frame;

    public JPanel panel;

    public JPopupMenu popup;



    public JMenuBar menubar;

    public JLabeltop;

    public JLabel bottom;



    public MiniMizeHack() {

        top = new JLabel(new ImageIcon("image.png"));

        bottom = new JLabel("More configuration here");



        frame = new JFrame("Mini Mize Hack");

        panel = new JPanel();

        panel.setLayout(new BorderLayout());

        panel.add("Center",bottom);

        panel.add("North",top);

        frame.getContentPane().add(panel);



        menubar = new JMenuBar();

        JMenu menu = new JMenu("File");

        menu.add(new JMenuItem("Open"));

        menu.add(new JMenuItem("Quit"));

        menubar.add(menu);



        JMenu window = new JMenu("Window");

        JMenuItem mini = new JMenuItem("Minimize");

        mini.addActionListener(this);

        window.add(mini);

        menubar.add(window);

        frame.setJMenuBar(menubar);



        popup = new JPopupMenu();

        JMenuItem restore = new JMenuItem("Restore");

        restore.addActionListener(this);

        popup.add(restore);

    }



    public void mousePressed(MouseEvent e) {

        maybeShowPopup(e);

    }



    public void mouseReleased(MouseEvent e) {

        maybeShowPopup(e);

    }

    public void mouseExited(MouseEvent e) { }

    public void mouseEntered(MouseEvent e) { }

    public void mouseClicked(MouseEvent e) { }

    private void maybeShowPopup(MouseEvent e) {

        if (e.isPopupTrigger()) {

            popup.show(e.getComponent(),

                       e.getX(), e.getY());

        }

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 6: Transparent and Animated Windows
Inhaltsvorschau
In the previous chapter, our window hacks generally played by the rules—we simulated the earthquake dialog [Hack #38] by animating calls to setLocation(), and switched to a mini-size window [Hack #40] by calling setSize() and removing some window decorations.
This chapter's hacks approach from outside the Window API per se, by hacking the windows with Java 2D, stuffing things into the glass pane of a JDialog or JFrame, and more. Some of them are practical, some are just pretty, but all of these hacks offer something unexpected.
Create translucent and shaped windows, while avoiding native code, with clever use of a screenshot.
One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.
The process of faking a transparent window is basically:
  1. Take a screenshot before the window is shown.
  2. Use that screenshot as the background of the window.
  3. Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.
To start off, create a JPanel subclass that can capture the screen and paint it as the background, as shown in Example 6-1.
Example 6-1. A transparent background component
public classTransparentBackground extends Jcomponent { 

	private JFrame frame; 

	private Image background;



public TransparentBackground(JFrame frame) {

	this.frame = frame;

	updateBackground();

}



public void updateBackground() {

	try {

		Robot rbt = new Robot();

		Toolkit tk = Toolkit.getDefaultToolkit();

		Dimension dim = tk.getScreenSize();

		background = rbt.createScreenCapture(

		new Rectangle(0,0,(int)dim.getWidth(),

                          (int)dim.getHeight()));

	} catch (Exception ex) {

		p(ex.toString());

		ex.printStackTrace();

	}

}

public void paintComponent(Graphics g) {

	Point pos = this.getLocationOnScreen();

	Point offset = new Point(-pos.x,-pos.y);

	g.drawImage(background,offset.x,offset.y,null);

}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 41–47: Introduction
Inhaltsvorschau
In the previous chapter, our window hacks generally played by the rules—we simulated the earthquake dialog [Hack #38] by animating calls to setLocation(), and switched to a mini-size window [Hack #40] by calling setSize() and removing some window decorations.
This chapter's hacks approach from outside the Window API per se, by hacking the windows with Java 2D, stuffing things into the glass pane of a JDialog or JFrame, and more. Some of them are practical, some are just pretty, but all of these hacks offer something unexpected.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Transparent Windows
Inhaltsvorschau
Create translucent and shaped windows, while avoiding native code, with clever use of a screenshot.
One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.
The process of faking a transparent window is basically:
  1. Take a screenshot before the window is shown.
  2. Use that screenshot as the background of the window.
  3. Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.
To start off, create a JPanel subclass that can capture the screen and paint it as the background, as shown in Example 6-1.
Example 6-1. A transparent background component
public classTransparentBackground extends Jcomponent { 

	private JFrame frame; 

	private Image background;



public TransparentBackground(JFrame frame) {

	this.frame = frame;

	updateBackground();

}



public void updateBackground() {

	try {

		Robot rbt = new Robot();

		Toolkit tk = Toolkit.getDefaultToolkit();

		Dimension dim = tk.getScreenSize();

		background = rbt.createScreenCapture(

		new Rectangle(0,0,(int)dim.getWidth(),

                          (int)dim.getHeight()));

	} catch (Exception ex) {

		p(ex.toString());

		ex.printStackTrace();

	}

}

public void paintComponent(Graphics g) {

	Point pos = this.getLocationOnScreen();

	Point offset = new Point(-pos.x,-pos.y);

	g.drawImage(background,offset.x,offset.y,null);

}

}
First, the constructor saves a reference to the parent JFrame; then it calls updateBackground(), which captures the entire screen using java.awt.Robot
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Your Frame Dissolve
Inhaltsvorschau
Create animated frame dissolves using two screenshots and some clever graphics code.
Dissolve is a term from old motion pictures where the director would switch scenes by fading, or dissolving, from one image to another. Eventually directors came up with more interesting dissolves like the vertical wipe, the venetian effect (little thin strips that look like venetian blinds), and the classic fade to black. With a little bit of screenshot hackery, you can create similar effects in Swing, allowing your program to fade away or do some other interesting animation when the user quits.
AWT doesn't support real transparent or shaped windows (though you can fake it with a screenshot pasted into a window that fills the screen [Hack #41] . Most dissolves involve applying some graphic effect to both the starting and ending images, which in this case means the application window itself and the rest of the user's desktop under the window. With this in mind, the basic plan is four steps:
  1. Capture an image of the window.
  2. Capture an image of the entire screen without the window.
  3. Cover up the entire screen with a new window.
  4. Show the dissolve animation.
To keep this simple, I have created a special class that does a simple fade-to-transparent animation. Once the class is built, you can create more complicated animations by overriding the paint method, leaving the messy details to the parent class. Here's the basic skeleton:
class Dissolver extends JComponent implements Runnable {Frame frame;

	Window fullscreen;

	int count;

	BufferedImage frame_buffer;

	BufferedImage screen_buffer;



	public Dissolver() { }
Dissolver is a JComponent that implements Runnable so that it can have an animation loop. It has member variables for the application frame to dissolve (frame), the window that covers up the screen (fullscreen), an animation counter (count), and the two buffers for storing the frame and the desktop background image (frame_buffer and screen_buffer
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create Custom Tool Tips
Inhaltsvorschau
Replace the standard rollover tool tip with an attractive custom version, including a border and rounded corners.
Every Swing component can have a tool tip, a little snippet of explanatory text that pops up when you let your mouse cursor linger over the component. These tool tips are often useful, but they usually look quite boring. This hack shows how to create visually interesting tool tips with a custom subclass.
In Swing, all tool tips are instances of the JToolTip class. To create your own version, you need only subclass JToolTip and override the paintComponent() method. In this hack, we'll create a tool tip with a rectangle that has a beveled border and a white background. The actual drawing can be taken care of with a few Java2D drawing commands. Example 6-4 is the code to draw the tool tip's background and border.
Example 6-4. A nice-looking tool tip
class CustomToolTip extends JToolTip {



	public void paintComponent(Graphics g) {



	// create a round rectangle

	Shape round = new RoundRectangle2D.Float(4,4,

        this.getWidth()-1-8,

        this.getHeight()-1-8,

		15,15);



	// draw the white background

	Graphics2D g2 = (Graphics2D)g;

	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

		RenderingHints.VALUE_ANTIALIAS_ON);

	g2.setColor(Color.white);

	g2.fill(round);



	// draw the gray border

	g2.setColor(Color.gray);

	g2.setStroke(new BasicStroke(5));

	g2.draw(round);

	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

		RenderingHints.VALUE_ANTIALIAS_DEFAULT);



	// draw the text

	String text = this.getComponent().getToolTipText();

	if(text != null) {

           FontMetrics fm = g2.getFontMetrics();

           int h = fm.getAscent();

 g2.setColor(Color.black);

		   g2.drawString(text,10,(this.getHeight()+h)/2);

		}

	}
This code creates a round rectangle shape that is then reused to draw the background and border. Notice that anti-aliasing is turned on for drawing the shape, but it's turned back to the default (which could be on or off) before drawing the text. It would look strange to have anti-aliased text if the rest of the interface was still using standard aliased text—using the default is a safer idea.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Turn Dialogs into Frame-Anchored Sheets
Inhaltsvorschau
One of Mac OS X's best ideas is binding the dialog to the window it blocks. This hack shows you how to mimic this in Swing.
One of my favorite features in Mac OS X is the sheet. This is a dialog box replacement that slides down from a window's titlebar. Figure 6-7 shows an example of a sheet in Apple's Safari web browser.
Looking at it, you might think, "what's the big deal" or “how is this any different than a regular dialog?” Oh, it's far better:
A sheet is visually anchored to the window that it blocks
On platforms where dialogs have titlebars and close boxes, the relationship between a dialog and the window it blocks is not necessarily intuitive. On a related point….
Figure 6-7: Sheet in Mac OS X Safari browser
A sheet doesn't have a close box
Dialog close boxes are one of the most hateful and stupid concepts in Windows and its many Linux imitators. What does the close box mean? Cancel? The default option? What does it mean when the dialog has multiple options of equal plausibility and thus no default? Perhaps the worst thing about the close box was back in the AWT era when Java developers—too lazy to add and wire-up an OK button to their dialogs—just figured users could dismiss the dialog with the close box. Mac OS 8 and 9 dialogs didn't have close boxes, so when a Java application brought up such a dialog, the application blocked itself forever. Duh. Sheets mean having to click one of the provided buttons, so the user's choices are unambiguous.
A sheet is used to block one window
This is an obvious side effect of being visually tied to a single window, but that's probably the most common case. As a side effect, this gives greater prominence to dialogs that block all windows for a single application ("Are you sure you want to Quit and lose all unsaved changes in all documents?") and dialogs that block all applications (“Are you sure you want to Shut Down?”).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Animating a Sheet Dialog
Inhaltsvorschau
By animating the sheet's appearance and disappearance, you give the user a better clue that his attention is required. Plus, it looks cool.
Another really great thing about the sheet functionality in Mac OS X is that it doesn't just suddenly appear—it slides out from the titlebar, as if unrolling from under the bar. This animation further reinforces the relationship between the sheet and the window because the short animation catches your eye and alerts you to the fact that something about the window has changed dramatically—namely, that it is now blocked by the sheet dialog.
You already know how to get components into the glass pane [Hack #44] , so you should expect that the key to sheet animation is to perform the animation in the glass pane, on top of the other components. Of course, you might have also guessed that the tricky part of this is going to be showing successively larger parts of the dialog as the animation progresses.
To make this work, you first need to create a custom component for the animating version of the sheet, separate from the sheet itself. Then, on each pass of the animation cycle, change the size of the custom component. It will always have the same width—the width of the real sheet—but its height will be some percentage of the height of the original, based on how much of the animation time has elapsed. This approach can work for both directions of the animation: when the sheet is incoming, the height will get progressively greater; when the sheet is going out, the height will decrease.
To actually draw the animating sheet during the animation, you can use BufferedImage.getSubimage( ) to grab a portion of the real sheet, and then draw that into its own Graphics via paint() callbacks. When the animation completes, the animating sheet is removed from the glass pane and the real sheet is added.
An interesting side effect of this is that the user can't click the buttons as the sheet appears or retracts because it's just an image of the sheet, not the sheet itself. Of course, the animation is so short (one second in Example 6-9, and Mac OS X's actually comes out faster than that), that it's unlikely a user could track the moving sheet with her mouse and successfully click a button anyway.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Slide Notes Out from the Taskbar
Inhaltsvorschau
Pop up a note above the taskbar when your application wants attention.
On Windows, long-running applications sometimes will slide in a window above the taskbar to call attention to themselves when an interesting event occurs, such as a finished download or an IM buddy's appearance.
If you want to do this in Java, you need to deal with a pretty significant problem: neither AWTnor Swing has any concept of the taskbar (where it is, how big it is, whether it's auto-hiding, or anything else). As a result, you don't know where to draw the window, and just taking a guess or hardcoding something is hazardous—too high and the window floats inexplicably on the desktop, too low and it gets buried under the taskbar.
Furthermore, how is this going to work on other operating systems? On the Mac, the proper way to get attention is to bounce your application's dock icon. Since there's no API exposing that functionality, can you at least use a Windows-like slide-in window above the dock? Sure…if you can figure out how tall the dock is (it's user configurable), or whether the dock is even on the bottom of the screen (it might be on the right or left, too).
Fortunately, it is possible to figure out what unobstructed space is available to you on the main display. After that, it's just a matter of offscreen imaging and animation.
The key to figuring out your available space is to get the local GraphicsEnvironment, which describes the display, and then call getMaximumWindowBounds(). This method, introduced in Java 1.4, returns a Rectangle representing the largest centered Window that could fit on the display, accounting for objects that intrude on the display's usable space, like the Windows taskbar or the Mac's monolithic menu bar.
This means that on Windows, the Rectangle will have an upper-left corner at 0,0; on the Mac, it will be at 0,22, which leaves space for the Mac's menu bar. Meanwhile, the height of the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Indefinite Progress Indicator
Inhaltsvorschau
Despite its numerous advanced widgets, Swing offers no efficient way to show that a task of unknown length is in progress. This hack presents two solutions to address this issue.
Have you ever watched an application do something, but not tell you what that something is? Other applications let you know what's going on but don't really tell you how long they will need to complete the task. For instance, the Microsoft Windows copy dialog is famous for its silly (and lengthy) nonprogress indicators. As a user, I find this very annoying; as a programmer, I know how difficult it can be to determine the duration of a task.
To address this issue, developers created particular widgets meant to show a task of unknown length is in progress. You can see such a widget in Mozilla's installer. It displays an indefinite progress bar—also called a Cylon—in which a small rectangle bounces back and forth between the two horizontal edges. I have also seen indefinite progress bars filling like regular progress bars, going backward once filled and starting all over again. The idea of an indefinite progress bar is great, but most existing implementations are just wrong. Users know what a progress bar looks like, and they also know how it is supposed to behave. It is a bad idea to present a familiar widget acting in a very surprising way. Unfortunately, the Swing designers followed this trend and added the setIndeterminate(boolean) method to JProgressBar. Check out Example 6-14.
Example 6-14. An indefinite progress bar
import javax.swing.*; 

public class CylonBar {

  public static void main(String[] args) {

    JFrame f = new JFrame("Progress");

    JProgressBar p = new JProgressBar();

    p.setIndeterminate(true);

    f.getContentPane().add(p);

    f.pack();

    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    f.setVisible(true);

  } 

}
This short example creates a new window containing an indefinite progress bar. When you launch the program, you can see a small rectangle bouncing back and forth within the progress bar's bounds, as in Figure 6-12. The result is much better on Mac OS X because it uses the native Look and Feel automatically, as shown in Figure 6-13. Despite this visual improvement, though, the result is still far from perfect.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 7: Text
Inhaltsvorschau
Text handling pervades the Swing API, from the labeling of a JButton to handling styled text in a JTextArea. When we talk about hacking Swing's text handling, we often mean two different things: hacking into the representation of the text (say, by making it searchable), or hacking into how that text is displayed. This chapter will help you see that Swing text isn't just about little JTextFields for entering your username.
This hack will show you how to add incremental search to a text area as a simple document listener, making it very easy to integrate with your existing software.
Many years ago, text editors had either no searching capabilities or a straight word search only. You would type a word into a dialog box and the program would search for that word, moving to its first location in the document. If you were lucky, there was a command to search again, instead of starting the process over. Then one day Emacs introduced a new kind of searching. The program would search as you typed in each character, updating the cursor with each keystroke. To search again, you only had to hit the Return key. If you wanted a less specific search, you could just hit the Backspace key and remove letters. Everything updated in real time. Incremental searching was born.
Most GUI toolkits—Java Swing included—do not provide incremental search. Java does, however, give you the tools to build your own incremental search. The 1.4 release of Java finally added a long requested feature: regular expressions. These are complex but concise patterns that are internally compiled into searching and matching code. With a single regular expression (known more commonly as a regex) you could match email addresses, split a complex data field, parse SQL expressions, or recognize different date formats. This hack has much more humble needs, but Java 1.4's built-in regex support will make the search implementation very easy.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 48–55: Introduction
Inhaltsvorschau
Text handling pervades the Swing API, from the labeling of a JButton to handling styled text in a JTextArea. When we talk about hacking Swing's text handling, we often mean two different things: hacking into the representation of the text (say, by making it searchable), or hacking into how that text is displayed. This chapter will help you see that Swing text isn't just about little JTextFields for entering your username.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Text Components Searchable
Inhaltsvorschau
This hack will show you how to add incremental search to a text area as a simple document listener, making it very easy to integrate with your existing software.
Many years ago, text editors had either no searching capabilities or a straight word search only. You would type a word into a dialog box and the program would search for that word, moving to its first location in the document. If you were lucky, there was a command to search again, instead of starting the process over. Then one day Emacs introduced a new kind of searching. The program would search as you typed in each character, updating the cursor with each keystroke. To search again, you only had to hit the Return key. If you wanted a less specific search, you could just hit the Backspace key and remove letters. Everything updated in real time. Incremental searching was born.
Most GUI toolkits—Java Swing included—do not provide incremental search. Java does, however, give you the tools to build your own incremental search. The 1.4 release of Java finally added a long requested feature: regular expressions. These are complex but concise patterns that are internally compiled into searching and matching code. With a single regular expression (known more commonly as a regex) you could match email addresses, split a complex data field, parse SQL expressions, or recognize different date formats. This hack has much more humble needs, but Java 1.4's built-in regex support will make the search implementation very easy.
The plan for this hack is to create a searching utility object that targets a JTextComponent (the common superclass for all Swing text components such as JTextArea and JTextField). It will also listen for action and document change events from a search component (usually a JTextField) to do the actual searching.
The code in Example 7-1 defines the IncrementalSearch class, which implements the DocumentListener and ActionListener
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Force Text Input into Specific Formats
Inhaltsvorschau
Use Java's powerful pattern matching to enforce rules on typed input
Validating input is an important GUI task, and some applications will validate your input when you tab off a field or even validate it on every keystroke. After all, it's a lot easier to deal with bogus data by not letting it into your system in the first place.
One technique for validating user input is to use a regular expression and then evaluate the input against it. For example, a field that can be uppercase letters only must always match the expression [A-Z]*, and one that can be any combination of uppercase, lowercase, numbers, and spaces must match [A-Za-z0-9]* (notice the space after 9).
Java's regex feature lets you create TextComponents that enforce matching against an expression. The basic idea is to watch for changes in the underlying Document and do your pattern match then.
Hopefully, you won't be surprised to know that you don't need to touch the view classes—JTextField, JTextArea, etc.—to add text constraint functionality. Text entry is happening in the model—in other words the Document—so that's where you tie in your regex code. This hack, listed in Example 7-2, subclasses PlainDocument to run the regex check on every call to insertString().
Example 7-2. A document allowing input that matches only a regex
import javax.swing.text.*; 

import java.util.regex.*;



public classRegexConstrainedDocument extends PlainDocument {



	Pattern pattern;

	Matcher matcher;



	public RegexConstrainedDocument () { super(); } 

	public RegexConstrainedDocument (AbstractDocument.Content c) { super(c); }	

	public RegexConstrainedDocument (AbstractDocument.Content c, String p) {

		super (c);

		setPatternByString (p);

	}

	public RegexConstrainedDocument (String p) {

		super();

		setPatternByString (p);

	}



	public void setPatternByString (String p) {

		Pattern pattern = Pattern.compile (p);

		// checks the document against the new pattern

		// and removes the content if it no longer matches

		try {

			matcher = pattern.matcher (getText(0, getLength())); 

			System.out.println ("matcher reset to " +											getText (0, getLength()));

			if (! matcher.matches()) {

				System.out.println ("does not match");

				remove (0, getLength());

			}

		} catch (BadLocationException ble) {

			ble.printStackTrace(); // impossible?

		}

	}



	public Pattern getPattern() { return pattern; }



	public void insertString (int offs, String s, AttributeSet a)

		throws BadLocationException {

		// consider whether this insert will match

		String proposedInsert =

			getText (0, offs) +

			s +

			getText (offs, getLength() - offs);

		System.out.println ("proposing to change to: " +

				proposedInsert);

        if (matcher != null) {

			matcher.reset (proposedInsert);

			System.out.println ("matcher reset");

			if (! matcher.matches()) {

				System.out.println ("insert doesn't match");

				return;

			}

		}

		super.insertString (offs, s, a);

	} 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Auto-Completing Text Fields
Inhaltsvorschau
Typing in a whole URL is a pain. When the user starts to type, complete his text with previously entered options, and let the user select one instead of typing the whole URL.
The auto-completing text field is instantly familiar from its use in browsers, where it is probably most needed. Nobody wants to have to try to type—or for that matter even remember—a huge URL to some page they've visited before, particularly not something like those Amazon.com URLs with inexplicable 20-digit numbers and bunches of seemingly arbitrary characters. On the other hand, not everything needs to be saved as a bookmark.
The text field that pops up a window of recently viewed sites is a happy compromise. It jogs your memory by showing you completion options, and it saves lots of typing by letting you simply click one of the options and having that text inserted immediately into the text field.
This hack takes a JTextField and has it manage a JWindow, which contains a JList of possible completion values. The real work is done by an inner class that manages the list of completions and has a javax.util.regex.Pattern object to match each potential completion against the field's current text. Example 7-4 is what you need to get going.
Example 7-4. A JTextField that manages a pop-up list of completions
import java.awt.*; 

import javax.swing.*; 

import javax.swing.event.*; 

import javax.swing.text.*; 

import java.util.*; 

import java.util.regex.*;



public class CompletableJTextField extends JTextField 

	implements ListSelectionListener {



	Completer completer;

	JList completionList;

	DefaultListModel completionListModel;

	JScrollPane listScroller;

	JWindow listWindow;



	public CompletableJTextField (int col) { 

		super (col);        

		completer = new Completer();        

		completionListModel = new DefaultListModel(); 

		completionList = new JList(completionListModel); 

		completionList.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); 

		completionList.addListSelectionListener (this); 

		listScroller =

			new JScrollPane (completionList, 

				 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 

				 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        listWindow = new JWindow();

		listWindow.getContentPane().add (listScroller);

	}



	public void addCompletion (String s) {

		completer.addCompletion (s); }



	public void removeCompletion (String s) {

		completer.removeCompletion (s); }



	public void clearCompletions (String s ) {

		completer.clearCompletions (); }



	public void valueChanged (ListSelectionEvent e) {

		if (e.getValueIsAdjusting()) { return; }

		if (completionList.getModel().getSize() == 0) {return;}

		listWindow.setVisible (false);

		final String completionString =

           (String) completionList.getSelectedValue();

		Thread worker = new Thread() {

				public void run() {setText (completionString);

				}

			};

		SwingUtilities.invokeLater (worker);

	}



	/** inner class does the matching of the JTextField's

		document to completion strings kept in an ArrayList

	*/



	class Completer implements DocumentListener {

		private Pattern pattern;

		private ArrayList completions;

		public Completer() {

			completions = new ArrayList();

			getDocument().addDocumentListener (this);

		}

		

		public void addCompletion (String s) {

			completions.add (s);

			buildAndShowPopup();

		}



		public void removeCompletion (String s) {

			completions.remove (s);

			buildAndShowPopup();

		}

       

		public void clearCompletions () {

			completions.clear();

			buildPopup();

			listWindow.setVisible(false);

		}

		private void buildPopup() {            

			completionListModel.clear();            

			System.out.println ("buildPopup for " + completions.size() +

					" completions");

			Iterator it = completions.iterator();            

			pattern = Pattern.compile (getText() + ".+");			

			while (it.hasNext()) {

				// check if match

				String completion = (String) it.next();

				Matcher matcher = pattern.matcher (completion);

				if (matcher.matches()) {

				// add if match

				System.out.println ("matched "+ completion);

				completionListModel.add (completionListModel.getSize(),

								 completion); 

               } else {

				System.out.println ("pattern " +														pattern.pattern() + 

							" does not match " + 

                                        completion);

               }

			}

		}



		private void showPopup() {

			if (completionListModel.getSize() == 0) { 

				listWindow.setVisible(false); 

				return;

			}

			// figure out where the text field is,

			// and where its bottom left is

			java.awt.Point los = getLocationOnScreen();

			int popX = los.x;

			int popY = los.y + getHeight();

			listWindow.setLocation (popX, popY);

			listWindow.pack();

			listWindow.setVisible(true);

		}



		private void buildAndShowPopup() {

			if (getText().length() < 1)

				return;

			buildPopup();

			showPopup();

		}



		// DocumentListener implementation

		public void insertUpdate (DocumentEvent e) { buildAndShowPopup(); }

		public void removeUpdate (DocumentEvent e) { buildAndShowPopup(); }

		public void changedUpdate (DocumentEvent e) { buildAndShowPopup(); }



	} 

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Write Backward Text
Inhaltsvorschau
Baffle your friends by turning their text into its mirror image.
The fact that everything in Swing goes through the Java2D rendering pipeline makes it really easy to apply all sorts of effects to Swing components. Text offers some fun possibilities. For one, you can challenge the user by turning his text into a mirror image of itself.
The easiest text component to distort is the simple JLabel. The BackwardsJLabel class in Example 7-6 subclasses JLabel and uses an AffineTransform in the paint() method to do the flip.
Example 7-6. Rendering a JLabel as a mirror image
import java.awt.*; 

import javax.swing.*; 

import java.awt.geom.*; 

import javax.swing.text.Document;

public class BackwardsJLabel extends JLabel {

   

	public BackwardsJLabel () { super(); }

	public BackwardsJLabel (Icon image) {super (image);}

	public BackwardsJLabel (Icon image, int align) {super (image, align);}

	public BackwardsJLabel (String text) { super (text);}

	public BackwardsJLabel (String text, Icon icon, int align) {

		super (text, icon, align); 

	} 

	public BackwardsJLabel (String text, int align) { super (text, align);}

	public void paint (Graphics g) {

		if (g instanceof Graphics2D) {

			Graphics2D g2 = (Graphics2D) g;

			AffineTransform flipTrans = newAffineTransform();

			double widthD = (double) getWidth();

			flipTrans.setToTranslation (widthD, 0);

			flipTrans.scale (-1.0, 1);

			g2.transform (flipTrans);

 }

 super.paint(g);

}
The constructors make trivial calls to their parent classes, so the key is the overridden paint() method. It first checks that you have a Graphics2D and does the cast. Any Graphics2D has an AffineTransformation that defines transforms that are to be applied as the Graphics2D is rendered. The AffineTransform of a Component will usually have some important transforms already defined in it, so it's best not to replace its transform, but rather to use the Graphics2D.transform() method to modify the existing AffineTransform with one of your own making.
But what kind of transform do you want to do? A mirror image consists of two separate trasnformations: scaling the x-coordinates by a factor of -1 (flipping them around the axis, so that the pixels furthest to the right are now furthest to the left), and translating by the width of the component (so the pixels move from negative coordinates, where they wouldn't be seen, back into positive space). This transformation is illustrated in Figure 7-8.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Use HTML and CSS in Text Components
Inhaltsvorschau
Spruce up your plain JLabels and buttons using HTML and CSS effects, such as underlines, color, and even embedded tables.
You may know that you can display HTML using a subclass of JTextPane, but did you also know that Swing supports simple HTML and CSS in virtually every text component? As long as you can trick it into showing it as HTML instead of the markup, you can do some pretty nifty things.
Every text component in Swing can display HTML, but the component needs to know that the text is HTML, rather than a string that just happens to contain a bunch of angle brackets. Since there is no setHTMLText(true) method on JTextComponent, you have to resort to being a little trickier. If the string passed to the component's constructor (or setText()) method starts with <html>, then the component will switch to HTML mode. Here is a quick example:
JButton b1a = new JButton("<html><i>my button</i>");
This code will produce a button that looks like Figure 7-11.
Figure 7-11: Italic text
You don't need to match the <html> with an </html> tag at the end. Swing's HTML parser is pretty tolerant of malformed HTML, so for simple things you can just type whatever is shortest. The mode can only be set once, so if you put plain text into the component first and then HTML later, it will still be in plain text mode. You should note that slower computers will exhibit a noticeable delay the first time a component is shown with HTML. This is because Swing has to load up all of the javax.swing.text.html classes; however, they are cached for any further instances.
To avoid this initial delay, you could load a hidden component in a separate thread during program startup.
HTML can be used as a shortcut for text effects that would be cumbersome or impossible with standard Font objects. For example, Font doesn't provide a way to draw underlined text, but with HTML you can do this:
JLabel l1 = new JLabel("<html><u>underlined</u></html>");
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Use Global Anti-Aliased Fonts
Inhaltsvorschau
Think Swing apps always look ugly because of the chunky fonts? Finally, you can do something about it!
Java 1.2 introduced Java2D, complete with the ability to draw anti-aliased text. Unfortunately, anti-aliasing is off by default and turning it on requires a programmatic change on each UI component. This hack shows how to turn on anti-aliasing for an entire frame without customizing each component. It also introduces a special repaint manager that is the key to several other hacks in this book, such as the partial translucent menus [Hack #12] .
To turn on anti-aliasing, you simply need to set a rendering hint:
	Graphics2D g2 = (Graphics2D)g;

	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

				RenderingHints.VALUE_ANTIALIAS_ON);
Unfortunately, the Graphics object is not very long-lived. There is no global place to set a hint because there's a new Graphics object for every repaint(). Any property you set would be gone by the next paint call. The usual workaround is to subclass the component you want to anti-alias and override the paint() method:
	class AAButton extends JButton {

		public voidpaint(Graphics g) {

			Graphics2D g2 = (Graphics2D)g;

			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

					RenderingHints.VALUE_ANTIALIAS_ON);

            super.paint(g2);

		}

	}
This will work, but it means you have to create a custom subclass for every component in your application. Not a very appealing solution.
Mac OS X provides anti-aliased rendering through a system property, but this only works because Apple thoughtfully added it to their JVM. Developers on other platforms are left out. Java 5.0 provides a standard system property for anti-aliasing, but that doesn't help the millions of 1.3 and 1.4 JVMs out there. Another option would be to use some form of code injection to modify each paint method at the bytecode level, but this requires an AOP tool, custom build scripts, and other things that are probably overkill for such a simple feature. There has to be a better way—and there actually is.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Anti-Aliased Text Without Code
Inhaltsvorschau
Draw anti-aliased text without any code changes at all using two clever tricks introduced in Java 5.0.
Since Java 1.2, UI programmers can draw anti-aliased text. Unfortunately, anti-aliasing must be enabled for every Swing component by writing a few lines of code for each of them [Hack #53] . This hack describes a clever way to turn on anti-aliasing for an entire frame by adding a customized repaint manager. As every programmer seeks for effortless solutions, we will discover how to do the same without writing any lines of code.
Sun Microsystems released Java 5.0, a.k.a. Tiger, in September 2004. Among many improvements, like a new theme for the Metal L&F, this release of J2SE paves the way for application-wide text anti-aliasing support in Mustang, the upcoming release of Java. To this end, the Swing team added a special field in the hidden class com.sun.java.swing.SwingUtilities2. Meant for internal purposes only, this class is left undocumented by Sun's engineering teams.
If you look closely at its source code, provided in src.zip with Sun's JVM, you'll discover a very interesting method: drawTextAntialiased(JComponent c). This method returns a boolean value used by Swing's painting framework to know whether the specified component must be drawn with anti-aliased text. Here is its complete source code:
	private static boolean drawTextAntialiased(JComponent c) {

		if (!AA_TEXT_DEFINED) {

			if (c != null) {

				return ((Boolean)c.getClientProperty(

				           AA_TEXT_PROPERTY_KEY)).booleanValue();

			}

			return false;

		}

		return AA_TEXT;

	}
As you can see, there are two ways to enable anti-aliased text. In the first case, the static variable AA_TEXT_DEFINED is set to false, and a check is performed against the component's properties. Hence, a component in which the property AA_TEXT_PROPERTY_KEY is set to true will be anti-aliased. You can set this property to a given component with the following line of code:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Anti-Aliased Text with a Custom Look and Feel
Inhaltsvorschau
Another way to get smooth text is to use a custom Look and Feel to avoid the fragile Java 5.0 APIs.
When the Java 5.0 anti-aliasing trick [Hack #54] was first discovered, some discussions arose on the Web. Many people strongly disagree with using it because it can be broken at any time by Sun Microsystems. Should we be deprived of anti-aliased text because of such a silly problem? Frédéric Lavigne, author of the famous Skin L&F and webmaster of www.javootoo.com, a great repository of Look and Feels for Swing, didn't think so and found an elegant and clever way to get the same result.
His idea is to use a custom Look and Feel whose sole purpose is to enable anti-aliasing hints on the Graphics instances used to draw the UI. He implemented his idea in the Wrap Look and Feel, which can be downloaded at wraplf.l2fprod.com. Another Look and Feel, SmoothMetal, enables anti-aliasing in your application. Yet, you are stuck with Metal Look and Feel when using it. Wrap Look and Feel acts as a decorator for the current Look and Feel. Thus, you can choose any Look and Feel you want and wrap it with Wrap Look and Feel to enable anti-aliasing. Doing so requires a single line of code:
	import com.l2fprod.common.swing.plaf.wrap.Wrapper;

	Wrapper.wrap();
No matter which Swing Look and Feel you set, the wrap() method will handle it properly. One line of code is good, but not good enough. We'd be better off with no line of code at all. Frédéric feels the same way and provides the excellent Wrapit class you can use to install the Wrap Look and Feel at runtime:
	java -classpath wraplf.jar;. Wrapit WebHunter
The Wrapit class contains a main() entry point that will install the Wrap Look and Feel and then call the main() entry point of the class passed as the first argument after Wrapit. This Look and Feel is a powerful tool you can use to enhance the appearance of any Java application, whether you have the source code or not.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 8: Rendering
Inhaltsvorschau
Sometimes it's not what you put into your GUI, but how you draw it. The hacks in this chapter are based in some way on using (or abusing) how AWT and Swing render the graphic contents of a GUI. In several cases, we use Java2D to bring graphic transformations and color-handling to Swing components. In others, we use AWT's font handling to change components; not just JTextComponents, but any components that need to draw text to render themselves. And in still other cases, we mess with the process by which Swing renders its contents.
Zoom in on those pixels with a little creative abuse of the AWT's debuggingoriented Robot class.
Some graphics programs use a component that shows a magnified view of what the cursor is currently hovering over. This can be very helpful for doing pixel-accurate editing of a picture.
It should be simple enough to do in Swing—get pixels from one component and put them in another—but there are some missing pieces. Specifically, how do you get the pixels out of the source component as an Image so you can drawImage() them into the magnified component? You could do this if you owned the source component and set it up with a double-buffer because creating the offscreen buffer would require creating an Image, which is exactly what you needed anyway. But for an arbitrary JComponent, you can't assume that level of access to the source's pixels.
But there's another option back in AWT: the Robot class, introduced in J2SE 1.3. It has a createScreenCapture() method that can grab the screen, or just part of it, and return it as a Java2D BufferedImage. This is what we need to get things going.
The DetachedMagnifyingGlass will need to keep track of the Component it's viewing, the current mouse location in that component, a zoom factor, and its own size. It will also need an instance of the AWT Robot for taking screen grabs. The other thing it needs to do is to have a MouseMotionListener, so that it will get updates on the cursor's position and, when it changes, do a new grab and
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 56–64: Introduction
Inhaltsvorschau
Sometimes it's not what you put into your GUI, but how you draw it. The hacks in this chapter are based in some way on using (or abusing) how AWT and Swing render the graphic contents of a GUI. In several cases, we use Java2D to bring graphic transformations and color-handling to Swing components. In others, we use AWT's font handling to change components; not just JTextComponents, but any components that need to draw text to render themselves. And in still other cases, we mess with the process by which Swing renders its contents.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create a Magnifying Glass Component
Inhaltsvorschau
Zoom in on those pixels with a little creative abuse of the AWT's debuggingoriented Robot class.
Some graphics programs use a component that shows a magnified view of what the cursor is currently hovering over. This can be very helpful for doing pixel-accurate editing of a picture.
It should be simple enough to do in Swing—get pixels from one component and put them in another—but there are some missing pieces. Specifically, how do you get the pixels out of the source component as an Image so you can drawImage() them into the magnified component? You could do this if you owned the source component and set it up with a double-buffer because creating the offscreen buffer would require creating an Image, which is exactly what you needed anyway. But for an arbitrary JComponent, you can't assume that level of access to the source's pixels.
But there's another option back in AWT: the Robot class, introduced in J2SE 1.3. It has a createScreenCapture() method that can grab the screen, or just part of it, and return it as a Java2D BufferedImage. This is what we need to get things going.
The DetachedMagnifyingGlass will need to keep track of the Component it's viewing, the current mouse location in that component, a zoom factor, and its own size. It will also need an instance of the AWT Robot for taking screen grabs. The other thing it needs to do is to have a MouseMotionListener, so that it will get updates on the cursor's position and, when it changes, do a new grab and repaint().
The DetachedMagnifyingGlass code is shown in Example 8-1.
Example 8-1. JComponent to provide a magnified view of another JComponent
public class DetachedMagnifyingGlass extends JComponent 

	implements MouseMotionListener {

	

	double zoom;

	JComponent comp;

	Point point;

	Dimension mySize;

	Robot robot;



	public DetachedMagnifyingGlass (JComponent comp,

						Dimension size,

						double zoom) {



		this.comp = comp;

		// flag to say don't draw until we get a MouseMotionEvent

		point = new Point (-1, -1);

		comp.addMouseMotionListener(this);

		this.mySize = size;

		this.zoom = zoom;

		// if we can't get a robot, then we just never

		// paint anything

		try {



            robot = new Robot();

		} catch (AWTException awte) {

			System.err.println ("Can't get a Robot");

			awte.printStackTrace();

		}

	}



	public void paint (Graphics g) {

		if ((robot == null) || (point.x == -1))

		{

			g.setColor (Color.blue);

			g.fillRect (0, 0, mySize.width, mySize.height);

			return;

		}

		Rectangle grabRect = computeGrabRect();

		BufferedImage grabImg = robot.createScreenCapture (grabRect);

		Image scaleImg =

			grabImg.getScaledInstance (mySize.width, mySize.height, 

						   Image.SCALE_FAST);



			g.drawImage (scaleImg, 0, 0, null);

		}private Rectangle computeGrabRect() {

			// width, height are size of this comp / zoom

			int grabWidth = (int) ((double) mySize.width / zoom);	

			int grabHeight = (int) ((double) mySize.height / zoom);

			// upper-left corner is current point

			return new Rectangle (point.x, point.y, grabWidth, grabHeight);

		}



		public Dimension getPreferredSize() { return mySize; }

		public Dimension getMinimumSize() { return mySize; }

		public Dimension getMaximumSize() { return mySize; }



		// MouseMotionListener implementations

		public void mouseMoved (MouseEvent e) {

			Point offsetPoint = comp.getLocationOnScreen();

			e.translatePoint (offsetPoint.x, offsetPoint.y);

			point = e.getPoint();

			repaint();

		} 

		public void mouseDragged (MouseEvent e) {

			mouseMoved (e); 

		} 

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create a Global Right-Click
Inhaltsvorschau
Give your application a right-click context menu without having to add a listener to every component.
Oftentimes, an application needs to have a pop-up menu that is accessible from more than one component. Sometimes the entire window should be right-clickable. Unfortunately, doing this the normal way in Swing would require adding a mouse listener to every component in the window, which isn't a very appealing solution, especially if your UI code is spread across many classes. It would be much nicer if there were a single place to add the context menu. This hack shows how to use a single glass pane to provide a right-clickable menu to the entire application.
A glass pane is an invisible JComponent that covers an entire JFrame. The glass pane can be used to catch events or draw on top of the rest of the application. For this hack, we will use a glass pane to capture right-click events and trigger a pop up, alleviating the need to register a mouse listener with every component in the frame. The basic idea is for the glass pane to intercept all mouse events and forward them on to the application, except for the right-click. Right-clicks will trigger the pop-up menu instead. This way there is only one listener per frame, instead of potentially hundreds.
To start off, you need a component that has a reference to the content pane of the frame and a pre-built pop-up menu. This is the beginning of just such a class:
public class RightClickGlassPane extends JComponent

	implements MouseListener, MouseMotionListener {

	

	private JPopupMenu popup;

	private Container contentPane;

	

public RightClickGlassPane(Container contentPane, JPopupMenu menu) {

	addMouseListener(this);

	addMouseMotionListener(this);

	this.contentPane = contentPane;

	popup = menu;

}



public void paint(Graphics g) {

}



// catch all mouse events and redispatch them

public void mouseMoved(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void

            mouseDragged(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void mouseClicked(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void mouseEntered(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void mouseExited(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void mousePressed(MouseEvent e) {

	redispatchMouseEvent(e, false);

}

public void mouseReleased(MouseEvent e) {

	redispatchMouseEvent(e, false);

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Block a Window Without a Modal Dialog
Inhaltsvorschau
Block the input in a single window during long operations without stopping your entire application.
Since the dawn of GUIs, most toolkits have had the concept of a modal dialog box. This is a small window that restricts input to itself, blocking access to the rest of the program (or entire operating system in some cases). Modal windows often produce the desired effect, but sometimes you need a window that can block itself without blocking access to the whole application. The most common use for such a window is a long running process, like rendering frames of a movie or waiting for the network to respond. In this case, you would like to let the user still interact with the rest of the application but block the one window that represents the work in progress. Swing doesn't provide a modal window like this, but since when has that stopped us?
To block a window, you could disable the components within it, but then you would need to recursively find each component and disable it manually. This is a big headache, and would make for a very ugly window (all those grayed-out components). All you really want to do is capture all input to the window and block that. Swing provides a great way to do this: the glass pane. The glass pane sits on top of all other components in a window, making it the perfect place to implement blocking behavior.
Since you want your glass pane to be transparent, it's best to start with a plain JComponent that doesn't draw anything. Example 8-3 defines a WindowBlocker class that extends JComponent and implements the MouseInputListener (a compound interface that combines the MouseListener and MouseMotionListener).
Example 8-3. Listening for mouse events
public class WindowBlocker extends JComponent 

	implements MouseInputListener {

	

	public WindowBlocker() {

		addMouseListener(this);

		addMouseMotionListener(this);

	}

	

	public void mouseMoved(MouseEvent e) {

	}

	public void mouseDragged(MouseEvent e) {

	}

	public void mouseClicked(MouseEvent e) {

	}

	public void mouseEntered(MouseEvent e) {

	}

	public void mouseExited(MouseEvent e) {

	}

	public void mousePressed(MouseEvent e) {

	

		Toolkit.getDefaultToolkit().beep();

	}	

	public void mouseReleased(MouseEvent e) {

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create a Color Eyedropper
Inhaltsvorschau
Enhance your color pickers with an eyedropper tool that grabs a color from anywhere on the screen.
Most paint tools give you an eyedropper, but I've never seen a Java program do it. Getting a screen pixel requires native access, which is usually blocked off from Java programs. Java 1.3 introduced a new method to the Robot class, getPixelColor(), which can retrieve the color anywhere on the screen. The problem is that you don't get mouse events once the cursor leaves your JFrame. This is fine if you only want to select colors from your own application, but a color chooser needs to select from anywhere on the screen. Java 5.0 introduces new APIs for getting complete mouse events, but that doesn't help us today.
The answer to this tricky problem, of course, is to cheat! This hack makes a screenshot and then paints it into a JFrame called ColorChooserDemo, which fills the entire screen. The screenshot is indistinguishable from the real desktop except that nothing in the background updates. However, since the screenshot is only needed while the user selects a color, this should be OK. ColorChooserDemo also has a JLabel in the center of the screen, which displays the currently selected color. Once the user has finished selecting a color by releasing the mouse, the entire frame will disappear and the component that launched the chooser—usually a JButton—will get the color through setBackground(). While it's running, ColorChooserDemo looks like Figure 8-3.
Figure 8-3: Running the ColorChooserDemo
The first step is to set up the required components. The ColorChooserDemo is a subclass of JFrame with member variables to hold the screenshot (background_image), the panel to draw the image (image_panel), the JLabel to display the current color under the cursor (label), and a few support variables. The beginnings of this class are shown in Example 8-6.
Example 8-6. Skeleton of the ColorChooserDemo class
public class ColorChooserDemo extends JFrame 

	implements MouseListener, MouseMotionListener {



	JPanel image_panel;

	Dimension screen_size;

	JComponent comp = null;

	Image background_image = null;

	Robot robot;

	JLabel label;

	

	public ColorChooserDemo(JComponent comp) {

		// get the screen dimensions

		screen_size =Toolkit.getDefaultToolkit().getScreenSize();

		

		// set up the frame (this)

		this.addMouseListener(this);

		this.addMouseMotionListener(this);

		this.comp = comp;

		this.setUndecorated(true);

		this.setSize(screen_size.width, screen_size.height);

		

		// set up the panel that holds the screenshot

		image_panel = new JPanel() {

			public void paintComponent(Graphics g) {

				super.paintComponent(g);

				g.drawImage(background_image,0,0,null);

			}

		};

		image_panel.setPreferredSize(screen_size);

		this.getContentPane().add(image_panel);

		

		// set up the display label

		label = new JLabel("Selected Color");

		label.setOpaque(true);

		label.setSize(100,100);

		image_panel.setLayout(null);

		image_panel.add(label);

		label.setLocation((int)screen_size.getWidth()/2 - 50,

		    (int)screen_size.getHeight()/2 - 50);

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Changing Fonts Throughout Your Application
Inhaltsvorschau
Get a quick font face-lift, without having to write a whole Look and Feel.
With no standards documents to obey and more flexible user expectations, web designers get much more freedom with their fonts than Swing developers expect. They get to set font styles with CSS, while we're expected to just leave well enough alone. Sure, you can change fonts on a component-by-component basis with setFont(), but it's not like you can just say "from now on, I want all JLabels to use the Cheese Deluxe Demi-Bold font." Well, OK, you could create a subclass of JLabel to set that font in its constructor, but your change wouldn't be picked up by any of JLabel's subclasses, like the default renderers for list, table, and tree cells. Fortunately, there is a much easier way than fighting with single inheritance.
Swing components get many of their defaults (e.g., fonts, icons, borders), from a Hashtable owned by the UIManager class. Actually, it is a subclass of Hashtable, called UIDefaults, which offers strongly typed methods like getFont(), getBorder(), getColor(), etc., each of which takes a key object.
Now, since this is just a Hashtable, you can put stuff in just as easily as you can get it out. All you have to do is know what the key is. As it turns out, for fonts, the keys are Strings that end with a .font suffix. So, for demonstration purposes, you can iterate through the keys of the UIDefaults, and every time you find one that ends in .font, put the Font of your choice back into the UIDefaults.
The goal of the ChangeAllFonts example is to change the default font of all Swing components, by changing all the appropriate keys it can find in UIDefaults. It starts by getting a font name from the command line and creating a 12-point plain Font instance.
Next, it gets the UIDefaults object as a Hashtable and gets an Enumeration of its keys. It walks the enumeration and, for every key ending in .font, it uses put() to replace the previous font with the user-selected font.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Load New Fonts at Runtime
Inhaltsvorschau
Who cares what fonts your users have? Bundle the fonts you want your application to use and load those fonts dynamically!
Using fonts with any predictability used to be a nightmare in Java. For a while, you could only depend on having access to one serif, one sans-serif, and one monospaced font, and the constants you'd use to get those fonts changed between Java 1.0 and 1.1. Fortunately, you're now free to use any font installed on the user's machine and load it by name.
Of course, not everyone has the same fonts. Even different installations of the same operating system will have different fonts available. I still use some TrueType fonts I've been toting from machine to machine for 15 years, and it's a safe bet that very few other people will have those same fonts.
This would seem to limit your Swing application to using only the fonts you know are installed with an operating system—maybe Arial and Times New Roman on Windows, Lucida Grande and Palatino on Mac OS X, etc. But it's not so. You can load font files at runtime and make them available to your Java application, even if the font isn't installed on the user's machine.
Using dynamically loaded fonts comes down to a single, critical, often overlooked AWT method in the Font class: createFont(). This method, introduced in Java 1.3, takes two parameters: a font format (as an int), which to date has no legal value other than Font.TRUETYPE_FONT, and an InputStream.
This stream is typically a FileInputStream from a .ttf TrueType file, or some equivalent. By equivalent, I meant that you could presumably put the font on the network and get a stream from a URL or put the font file inside a .jar, find it along the classpath with ClassLoader.getResourceAsStream(), and load from that.
Mac OS X's font suitcases—a holdover from the Classic Mac OS—aren't supported. Your fonts need to be in .ttf files.
FontLoadingDemo, shown in Example 8-8, offers a straightforward application of this font-loading technique. It takes the path to a
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Build a Colorful Vector-Based Button
Inhaltsvorschau
Build a resolution-independent OS X-style button using scalable graphics code.
The button in this hack is resolution independent, meaning that it can resize and rescale automatically as the user's windows and display change, stretching and tiling the graphics to fill the new space. The button doesn't depend on being any particular size to look good. As higher-quality and higher resolution monitors become more common, users will start to expect attractive interfaces that scale and reflow with their increasingly expansive displays. This hack shows how to create an attractive JButton that will scale with both size and resolution, opening the door for a completely vector drawn (meaning with shapes instead of images) Swing Look and Feel.
Since this button must scale with the size of the screen, you can use a variable called scale. Every piece of drawing code for this button is done relative to scale's value. If the scale value changes, the entire button will change accordingly. The scale value itself is based on the current font size of the component. If the component's font is resized (due to a change in screen resolution, for example), then the scale value will change accordingly, resizing the entire button. With a scale value in place, this hack is simply a matter of recreating the Aqua button look with Java2D calls. The goal is a button that looks like Figure 8-7.
Figure 8-7: A green vector JButton
Not a simple task, but it's not impossible either. All the work is done in Example 8-9.
Example 8-9. Creating liquid buttons
public class VectorButton extends JButton implements MouseListener {    

	public VectorButton() { 

		this.addMouseListener(this); 

	}

   public Dimension getPreferredSize() {

		String text = getText();

		FontMetrics fm = this.getFontMetrics(getFont());

		float scale = (50f/30f)*this.getFont().getSize2D();

		int w = fm.stringWidth(text);

		w += (int)(scale*1.4f);

		int h = fm.getHeight();

		h += (int)(scale*.3f);

		return new Dimension(w,h);

	}

	public void paintComponent(Graphics g) {

		Graphics2D g2 = (Graphics2D)g;

		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

			RenderingHints.VALUE_ANTIALIAS_ON);

		g2.setColor(this.getBackground());

		g2.fillRect(0,0,this.getWidth(),this.getHeight());



		float scale = (50f/30f)*this.getFont().getSize2D();



		drawLiquidButton(this.getForeground(),

			this.getWidth(), this.getHeight(),

			getText(), scale,

			g2);

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add a Third Dimension to Swing
Inhaltsvorschau
User interfaces have stuck to 2D drawing for many years. Today, Swing and Java3D give you a chance to go one step further and add 3D widgets to your UI.
Have you ever wondered how to add nice 3D components into your Swing applications? Java3D is a free API provided by Sun Microsystems for Linux and Windows, and by Apple for Mac OS X, that lets you create 3D scenes. Although well documented, Java3D seems impossible to use with Swing—at least at first glance.
Imagine you decided to create a new, astonishing application called AmazonPick that would let the user search for books on the Amazon.com store. Your eye-candy user interface would even display the currently selected book as a 3D object; whenever the user selects another book, the 3D object would flip to show the new cover on its opposite side. Figure 8-8 shows how the application should look.
Figure 8-8: AmazonPick shows books as full 3D objects
Unfortunately, you won't be able to obtain these results without a little imagination. For instance, take a close look at Figure 8-8 and notice the gradient background of the window. Displaying such a background is very easy with Swing and the opaque properties of Swing components, as seen in the rather simple class in Example 8-10.
Example 8-10. A demo program for 3D components
public BooksDemo() 

{

	super("AmazonPick");



	JButton cover1 = UIHelper.createButton("", "cover1_small_button", true);

	JButton cover2 = UIHelper.createButton("", "cover2_small_button", true);

	JButton cover3 = UIHelper.createButton("", "cover3_small_button", true);

	

	JPanel buttons = new JPanel();

	buttons.add(cover1);

	buttons.add(cover2);

	buttons.add(cover3);

	buttons.setOpaque(false);

	

	setContentPane(new GradientPanel());

	getContentPane().setLayout(new BorderLayout());

	getContentPane().add(buttons, BorderLayout.SOUTH);

	

	pack();

	setResizable(false);

	setDefaultCloseOperation(EXIT_ON_CLOSE);



	UIHelper.centerOnScreen(this); 

}
This code creates three buttons, each containing a picture of a book loaded by the utility class
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Turn the Spotlight on Swing
Inhaltsvorschau
Users often get lost when using applications, as if they were in total darkness. Why don't you turn on a spotlight to show them the way?
Most applications involve a fair amount of data manipulation. The user creates, edits, deletes, and moves around data items. When the amount of data becomes important, it is vital for the user to be able to search for items easily. Yet, search result displays are often irritatingly complicated, and the results are a pain to browse through. This hack describes how to draw a user's attention to only the parts of the UI you want him to look at.
Figure 8-11: All the problems provoked by mixing Java3D and Swing have been solved
If you have ever been to a comic show, to the theatre, or to the circus, you've probably noticed how spotlights are used to move your focus to a particular location on the stage. A search operation can be compared to a theatre stage, where actors have been replaced by search results. You just need to get the user to focus on the right location.
Take a look at an example application, shown in Figure 8-12, which displays a predefined set of books and lets you search for one or more of them by entering a search query in the text field at the bottom of the window.
Since the application is just a demo, you can only enter one of the following queries: books, sci-fi, adams, and pratchett. Each query, when validated by the Enter key, will find the related books and spotlight them, as shown in Figure 8-13.
Figure 8-12: The bookshelf application must let the user search for books
Figure 8-13: Searching for "sci-fi" highlights Science Fiction books
You can even go one step further: the more light you shed, the less darkness there is. For instance, when several books are found, it is likely that the search query was not very precise. This means the user will be more interested in lots of items. When the query yields only a few results, it is likely that the user wants to see only a few specific items.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 9: Drag-and-Drop
Inhaltsvorschau
Several years ago, the Swing team introduced a pair of APIs for data transfer: java.awt.datatransfer and java.awt.dnd (Drag and Drop). The former abstracts the concept of data exchange to and from your application (participating with either Java or native applications) and provides clipboard-based copy-and-paste functionality. The latter particularizes these abstractions to the specifics of drag-and-drop behavior. While many developers use these APIs for working with unstyled clipboard text only, you can do much more. Both Drag and Drop events and the clipboard support images, URLs, Files, and even custom Java objects.
Drag files from your application directly to the desktop, complete with translucent icons.
This hack shows you how to go much further than mere clipboard access by digging into the lower levels of the Drag and Drop APIs and building a program that can save files directly to the desktop via dragging, complete with proper file icons and drag feedback.
When you use an editor to write a large document, you often save it to a particular location on your filesystem—in a Projects folder perhaps. This is because you will keep the file around for a long time, so you want to store it for later use. Small documents, however, are often created for transient reasons. I often write a few paragraphs and then immediately post it to a weblog or attach it to an email. Some applications (particularly those on Mac OS X) let you save something quickly by dragging a small marker into another application or the desktop. The marker represents the file and lets you quickly move the entire file into another context (a blog editor, for example) without thinking about where to save the file (and trying to remember where you stashed it 10 minutes later).
Since drag-to-save behavior is not a standard part of the Java platform, you will have to build it from scratch using the Drag and Drop APIs. First, you will need a class that can trigger the drop action. The plan is to detect the gesture, create a temp file to be saved, and then start the real drag with the appropriate cursor and user feedback. Here's a starting point:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 65–69: Introduction
Inhaltsvorschau
Several years ago, the Swing team introduced a pair of APIs for data transfer: java.awt.datatransfer and java.awt.dnd (Drag and Drop). The former abstracts the concept of data exchange to and from your application (participating with either Java or native applications) and provides clipboard-based copy-and-paste functionality. The latter particularizes these abstractions to the specifics of drag-and-drop behavior. While many developers use these APIs for working with unstyled clipboard text only, you can do much more. Both Drag and Drop events and the clipboard support images, URLs, Files, and even custom Java objects.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Drag-and-Drop with Files
Inhaltsvorschau
Drag files from your application directly to the desktop, complete with translucent icons.
This hack shows you how to go much further than mere clipboard access by digging into the lower levels of the Drag and Drop APIs and building a program that can save files directly to the desktop via dragging, complete with proper file icons and drag feedback.
When you use an editor to write a large document, you often save it to a particular location on your filesystem—in a Projects folder perhaps. This is because you will keep the file around for a long time, so you want to store it for later use. Small documents, however, are often created for transient reasons. I often write a few paragraphs and then immediately post it to a weblog or attach it to an email. Some applications (particularly those on Mac OS X) let you save something quickly by dragging a small marker into another application or the desktop. The marker represents the file and lets you quickly move the entire file into another context (a blog editor, for example) without thinking about where to save the file (and trying to remember where you stashed it 10 minutes later).
Since drag-to-save behavior is not a standard part of the Java platform, you will have to build it from scratch using the Drag and Drop APIs. First, you will need a class that can trigger the drop action. The plan is to detect the gesture, create a temp file to be saved, and then start the real drag with the appropriate cursor and user feedback. Here's a starting point:
class FileDragGestureListener extends DragSourceAdapter

		implements DragGestureListener {

		JTextArea text;

		Cursor cursor;

		public FileDragGestureListener(JTextArea text) {

			this.text = text;

	}
The FileDragGestureListener implements DragGestureListener and extends the DragSourceAdapter. Swing sends all drag events to a DragSource listener.
Extending the DragSourceAdapter, instead of implementing DragSource directly, lets your class avoid implementing all of the required methods.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Handle Dropped URLs
Inhaltsvorschau
Drag-and-drop is like a box of chocolates; you never know what you're going to get….
Bookmark menus are so 1995. Today, you should expect to be able to drag URLs to other applications and have those applications open web pages, store the address in a bookmark database, start an email in response to a mailto: URL, etc. Java's networking chops are well-established and aren't the problem here. The issue is actually getting the URL itself from the drop.
To accept drops of native objects, your GUI needs to designate some Component as the onscreen drop target. Your code then implements the DropTarget interface, which means your implementation will get callbacks when the user drags the mouse into your component, over it, out of it, etc. Most of DropTarget's methods can be left as no-ops; for now, only the drop() method matters.
What's interesting about native drag-and-drop (and copy-and-paste, for that matter) is that there's not necessarily one way to represent the data being transferred. Instead, you do a sort of negotiation with the Transferable passed to you by the DropTargetDropEvent: it specifies, in order of robustness, which DataFlavors it can deliver, or you ask whether specific DataFlavors are supported.
In the demo code in Example 9-3, I have a method called dumpDataFlavors(), which shows the DataFlavors offered to you by the drop. You can use System.out.println() on a particular flavor to get its MIME type, which describes the contents of the drop, and a representation class, which indicates how those contents will be provided to you by Transferable. getTransferData(). For example, some browsers will give you a java.net. URL, whose DataFlavor looks like this:
	java.awt.datatransfer.DataFlavor[mimetype=application/x-java-url;

 		representationclass=java.net.URL]
One thing that's surprising is the number of DataFlavors offered by popular web browsers. Table 9-1 is a short listing of some of the browsers I tested.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Handle Dropped Images
Inhaltsvorschau
I spy, with my little drop() method, something that doesn't support DataFlavor.imageFlavor…arrrgh!
Handling drag-and-drop from native applications [Hack #66] can be tricky because they're not particularly consistent about how they represent the data being transferred to your application. Now, let's say you want to accept dropped images—not image files, but actual images inside of browser windows, digital photo viewers, word processing and page-layout applications, etc. There's a constant in DataFlavor for images, so surely you can count on that being a flavor offered to you by the Transferable, right?
No, of course not. That would be too easy.
Using the dumpDataFlavors() strategy of the earlier URL hack, I checked out the DataFlavors offered by images dropped from some popular Windows and Mac applications. The results are pretty interesting—check out Table 9-2.
Table 9-2: DataFlavor offerings for images on various platforms
Application/platform
DataFlavors
Preview 2.1 / Mac OS X
1
GraphicConverter 4.6 / Mac OS X
1
Finder / Mac OS X
1
Safari 1.3 / Mac OS X
55
Firefox 1.0 / Mac OS X
57
QuickTime Player 6.5 / Mac OS X
1
AppleWorks 6.2.9 / Mac OS X
1
MarinerWrite 3.6.4 / Mac OS X
1
iPhoto 4.0.3 / Mac OS X
1
Explorer / Windows XP
1
MSIE 6.0 / Windows XP
27
Firefox 1.0 / Windows XP
80
Paint / Windows XP
N/A
Windows Picture and Fax Viewer / Windows XP
N/A
Windows Media Player / Windows XP
N/A
QuickTime Player 6.5.1 / Windows XP
N/A
QuickTime Picture Viewer 6.5.1 / Windows XP
N/A
The first thing you should notice is the poor support for dragging and dropping images in Windows: Paint, Picture and Fax Viewer, and the Quick-Time applications are not drag sources, and dragging an image from the playlist of Windows Media Player to the Swing application in this hack actually crashes Java.
Furthermore, the supported DataFlavors are all over the map. A lot of these provide references to image files in the form of either a javaFileListFlavor
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Handling Dropped Picts on Mac OS X
Inhaltsvorschau
For Mac applications that provide only the legacy Pict flavor of drops, QuickTime for Java offers a Mac-specific solution.
You should already recognize the existence of hard-to-handle DataFlavors [Hack #67] passed by certain Mac applications. Of the Mac apps I tested, several old apps (many using the Carbon APIs, which were developed to migrate classic Mac apps to OS X) support only one DataFlavor. When I drop from GraphicConverter, QuickTime Player, AppleWorks, and MarinerWrite, the only supported DataFlavor was reported as:
	java.awt.datatransfer.DataFlavor[mimetype=image/x-pict; 

		representationclass=java.io.InputStream]
For those of you who don't use Macs, Pict is part of QuickDraw, the original Mac graphics API. The term is wildly overloaded—Pict can refer to a file format, a resource hidden in an application file, a wrapper around vector drawing commands, and as a wrapper around optionally compressed pixel data. It's in this latter form that Pict is a preferred format for passing image data on the Mac clipboard because Picts are easy for Mac applications to render and convert (through the QuickDraw library, of course).
Unfortunately, Java doesn't know the first thing about Picts, so it's frustrating to see that Pict is the only supported DataFlavor. Worse, the data is supplied as an InputStream, instead of a file or a URL, meaning you have to handle it in Java, instead of handing off to non-Java code that might be able to convert the Pict to something that can handle the format more gracefully.
Fortunately, there is a Java solution to the problem, and it's called QuickTime for Java (QTJ). This API is a Java wrapper around the native QuickTime multimedia API. It's available for Mac OS X and Windows, but since a Windows application isn't going to pass you a Pict, you only need to worry about the Mac OS X case. One advantage of all this API-wrangling: QTJ is installed by default on Mac OS X, so you can count on it being there.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Translucent Drag-and-Drop
Inhaltsvorschau
The Java implementation of drag-and-drop offers poor visual feedback. This hack shows how to provide more information and a better-looking response to the user.
A good way to create user-friendly interfaces is to provide the ability to drag-and-drop almost anything from, within, and onto those interfaces. Mac OS X is a perfect example of a good drag-and-drop use. Every time I try to drag something to drop it onto something else, it just works. AWT, and therefore Swing, let applications implement drag-and-drop but lack something Mac OS X already offers: really cool visual feedback to let the user know what's going on.
J2SE has offered drag-and-drop facilities since version 1.2. All the necessary classes and interfaces can be found in the package java.awt.dnd. Although not very easy to use at first, this package provides powerful features you can use to greatly improve the usability of your applications. Unfortunately, the Java drag-and-drop framework offers little visual feedback. In fact, the only feedback the user can get is a simple mouse cursor. For instance, you can show that a drag-and-drop operation is in progress with the following line of code:
	dropTarget.setCursor(DragSource.DefaultMoveDrop);
Figure 9-7 shows what the visual feedback looks like on Windows.
Figure 9-7: Default drag-and-drop visual feedback on Windows
Not only does this look bad, it also conveys very little information. Mac OS X users are used to a much richer environment, and why should Windows or Linux users expect less? As an example of what can be done, Safari—Mac OS X's default web browser—shows a translucent thumbnail of the picture you are dragging. Figure 9-8 shows what this looks like in action.
Figure 9-8: Mac OS X provides great looking visual feedback for drag-and-drop
The quality of the interaction is greatly enhanced by the quantity of information provided by the visual feedback. Another example is dragging a link from the web browser to the desktop, where a translucent text box containing the link title and the URL is shown. Wouldn't it be great to be able to do the same in your application? Thankfully, Swing is perfectly suited for that kind of job.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 10: Audio
Inhaltsvorschau
Sound is underrated as a useful tool for building good user interfaces. A lot of developers balk at the thought of sound support, imagining an office full of noisy machines, emitting a beeping cacophony more like a 1980s videogame arcade than a place of business. But on the other hand, don't you appreciate it when you get a nice little audible cue? For example:
  • When your IM buddy logs in
  • When your CD has finished burning
  • When your gigantic upload has finished
  • When someone is trying to hack into your network and you're not even looking at the screen
And beyond these kinds of uses, don't forget the whole realm of applications that are, by their nature, all about sound: music players, sound editors, voice chat, and VoIP, etc. Clearly, java.awt.Toolkit.beep() is not going to cut it.
Java has two built-in options for playing simple sounds in memory: applet AudioClips and JavaSound. Because of their limitations, this chapter will also look at two extensions: Java Media Framework (JMF) and QuickTime for Java (QTJ). Later in the chapter, you'll find more sophisticated JavaSound coverage, including how to visualize an in-memory sound clip, and how to play sounds too big to fit in memory.
If you're forced to write to the old Applet API for sound, here's how you do it. Good luck.
Let me be very clear up front: applet-based sound sucks. If you are in a hurry to get sound into your application and can count on your users having Java 1.3 or better, go ahead and use JavaSound instead [Hack #71] . Appletbased sound is going to be most useful to those who must deliver applets (and only applets) to very old browsers and JVMs. And it's not going to be pretty.
Java 1.0 and 1.1 shipped with no support for application-based sound. None. The only sound support was for applets, presumably so they could punt the responsibilities for audio to the enclosing browser. The idea in the early JDKs is built around an AudioClip, a class found in the
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 70–78: Introduction
Inhaltsvorschau
Sound is underrated as a useful tool for building good user interfaces. A lot of developers balk at the thought of sound support, imagining an office full of noisy machines, emitting a beeping cacophony more like a 1980s videogame arcade than a place of business. But on the other hand, don't you appreciate it when you get a nice little audible cue? For example:
  • When your IM buddy logs in
  • When your CD has finished burning
  • When your gigantic upload has finished
  • When someone is trying to hack into your network and you're not even looking at the screen
And beyond these kinds of uses, don't forget the whole realm of applications that are, by their nature, all about sound: music players, sound editors, voice chat, and VoIP, etc. Clearly, java.awt.Toolkit.beep() is not going to cut it.
Java has two built-in options for playing simple sounds in memory: applet AudioClips and JavaSound. Because of their limitations, this chapter will also look at two extensions: Java Media Framework (JMF) and QuickTime for Java (QTJ). Later in the chapter, you'll find more sophisticated JavaSound coverage, including how to visualize an in-memory sound clip, and how to play sounds too big to fit in memory.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Play a Sound in an Applet
Inhaltsvorschau
If you're forced to write to the old Applet API for sound, here's how you do it. Good luck.
Let me be very clear up front: applet-based sound sucks. If you are in a hurry to get sound into your application and can count on your users having Java 1.3 or better, go ahead and use JavaSound instead [Hack #71] . Appletbased sound is going to be most useful to those who must deliver applets (and only applets) to very old browsers and JVMs. And it's not going to be pretty.
Java 1.0 and 1.1 shipped with no support for application-based sound. None. The only sound support was for applets, presumably so they could punt the responsibilities for audio to the enclosing browser. The idea in the early JDKs is built around an AudioClip, a class found in the java.awt.applet package—as David Flanagan says in Java Foundation Classes in a Nutshell (O'Reilly), "only because there is no better place for it."
To demonstrate AudioClips, Example 10-1 shows a hacked up little applet.
Example 10-1. Playing an AudioClip in an applet
public class AppletSound extends Applet

	implements ActionListener {



	JButton fileButton, loadButton, playButton, loopButton, stopButton;

	JLabel urlLabel;

	JTextField urlField;

	AudioClip clip;



	public AppletSound() {

		setLayout (new GridLayout (2,1));

		// first row layout

		JPanel topPanel = new JPanel();

		urlLabel = new JLabel ("URL:");

		topPanel.add (urlLabel);

		urlField = new JTextField (25);

		urlField.addActionListener (this);

		topPanel.add (urlField);

		loadButton = new JButton ("Load");

		loadButton.addActionListener (this);

		topPanel.add (loadButton);

		fileButton = new JButton ("File");

		fileButton.addActionListener (this);

		topPanel.add (fileButton);

		add (topPanel);

// second row layout

		JPanel bottomPanel = new JPanel();

		playButton = new JButton ("Play");

		playButton.addActionListener (this);

		bottomPanel.add (playButton);

		stopButton = new JButton ("Stop");

		stopButton.addActionListener (this);

		bottomPanel.add (stopButton);

		loopButton = new JButton ("Loop");

		loopButton.addActionListener (this);

		bottomPanel.add (loopButton);

		add (bottomPanel);

	}

	public void stop() {

		clip.stop();

	}



	public void actionPerformed (ActionEvent e) {

		Object source = e.getSource();

		if (source == fileButton) {

           JFileChooser chooser = new JFileChooser();int pick = chooser.showOpenDialog(this);

		   if (pick == JFileChooser.APPROVE_OPTION) {

				try {

				File file = chooser.getSelectedFile();							urlField.setText (file.toURL().toString());

				} catch (MalformedURLException murle) {							murle.printStackTrace();

				}

			}

		} else if (source == loadButton ) {

			try {

				System.out.println ("field: " + urlField.getText());

				URL clipURL = new URL (urlField.getText());

				System.out.println ("loading " + clipURL);

				clip = getAudioClip (clipURL);

				System.out.println ("got clip");

				} catch (MalformedURLException murle) {

				murle.printStackTrace();

				}

		} else if (source == playButton ) {

			clip.play();

		} else if (source == stopButton ) {

			clip.stop();

		} else if (source == loopButton ) {

			clip.loop();

		}

	}

public static void main (String args[]) {

		JFrame f = new JFrame ("Applet Sound");

		f.getContentPane().add (new AppletSound());

		f.pack();

		f.setVisible(true);

	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Play a Sound with JavaSound
Inhaltsvorschau
Get a small clip to play from memory with a lot less hassle.
The JavaSound API was developed to answer complaints about the inadequacies of the AudioClip class in the applet package…not the least of which was the fact that it couldn't be used in applications. JavaSound consists of two packages—javax.sound.sampled and javax.sound.midi—plus two service provider interface (spi) sub-packages for adding support for new devices, formats, converters, etc.
JavaSound was introduced as an extension to Java 1.2 (I know, I know, "Java 2 Standard Edition, version 1.2"), and it became part of Core Java in 1.3. In other words, you're pretty safe assuming that it's present on your user's machine. That's one big point in its favor.
To show off JavaSound, the code in Example 10-3 exhibits a short application that allows the user to pick a file from the local filesystem and play it. A dialog shows the selected filename; OK it when the audio completes to exit the program.
Example 10-3. Playing audio with JavaSound
public class CoreJavaSound extends Object

	implements LineListener {

	

	File soundFile;

	JDialogplayingDialog;

	Clip clip;



	public static void main (String[] args) {

		JFileChooser chooser = new JFileChooser();

		chooser.showOpenDialog(null);

		File f = chooser.getSelectedFile();

		try {

			CoreJavaSound s = new CoreJavaSound (f);

		} catch (Exception e) {

			e.printStackTrace();

		}

	}



	public CoreJavaSound (File f)

		throws LineUnavailableException, IOException,

				UnsupportedAudioFileException {

		soundFile = f;

		// prepare a dialog to display while playing

		JOptionPane pane = new JOptionPane ("Playing " + f.getName(),

								JOptionPane.PLAIN_MESSAGE);

		playingDialog = pane.createDialog (null, "Application Sound");

		playingDialog.pack();



		// get and play sound

		Line.Info linfo = new Line.Info (Clip.class);

		Line line = AudioSystem.getLine (linfo);

		clip = (Clip) line;

		clip.addLineListener (this);

		AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile);

		clip.open (ais);

		clip.start();

	}

	// LineListener

	public void update (LineEvent le) {

		LineEvent.Type type = le.getType();

		if (type == LineEvent.Type.OPEN) {

			System.out.println ("OPEN");

		} else if (type == LineEvent.Type.CLOSE) {

			System.out.println ("CLOSE");

			System.exit (0);

} else if (type == LineEvent.Type.START) {

			System.out.println ("START");

			playingDialog.setVisible(true);

		} else if (type == LineEvent.Type.STOP) {

			System.out.println ("STOP");

		}

	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Play a Sound with Java Media Framework
Inhaltsvorschau
Use the Java Media Framework for better performance and support for more audio formats.
Java Media Framework (JMF) is Sun's attempt to bring a broadly focused multimedia framework to Java, supporting audio, video, and other timebased media types. The idea is to provide Java desktop applications with these features across operating systems. Like JavaSound, it's meant to be extended so that Sun or third parties could add support for new file formats or codecs (the compression/decompression encoding schemes used inside those files).
JMF offers another way to provide sound from an application. The advantages of doing so are that JMF may provide access to many more sound files than JavaSound will alone, and that JMF is somewhat easier to code than JavaSound, particularly for simple tasks. The disadvantages are that JMF capabilities vary wildly by platform, and that the end user will have to install JMF separately, which will be difficult or simply not allowed for some users.
Download and install JMF from its home page at http://java.sun.com/products/java-media/jmf/index.jsp and you should be ready to go—no reboot required. The installers should have put everything into the correct path and set up your environment. If you have trouble getting JMF programs to run, or if you used the all-Java version that doesn't have a special installer, you can try adding the following environment variable:
	JMFHOME="C:\Program Files\JMF2.1.1"
Next, add the two JMF Java libraries to your classpath:
	CLASSPATH="$JMFHOME\lib\jmf.jar;$JMFHOME\lib\sound.jar;.;$CLASSPATH"

	PATH="$JMFHOME\lib;$PATH"
The demo in Example 10-4 is basically a port of the CoreJavaSound demo used in playing audio with JavaSound [Hack #71] , except that the eventhandling has been simplified to a "quit when done" implementation. JMF's playback metaphor involves Players, which simply play media, and Processors, which may take action on the media, such as adding effects or transcoding to other formats. This allows the simple stuff to stay simple: to play a file, you wire it up to a
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Play a Sound with QuickTime for Java
Inhaltsvorschau
Using QuickTime, you can play even more kinds of sounds, but only on two operating systems.
QuickTime for Java offers another way to significantly improve the media capabilities of your application. Its list of supported formats is huge (see http://www.apple.com/quicktime/products/qt/specifications.html for the current list) and always growing as Apple continues to improve it. That's a big advantage over JMF, which was dropped into maintenance mode in 1999 and largely ignored since then.
The huge disadvantage with QuickTime for Java is that it works on Windows and Mac only. That's because QTJ, as it's typically called, is really just an object-oriented (OO) wrapper to call C functions in the native Quick-Time library. That gives you native-speed performance, but it also means the wrappers don't do anything without an underlying native implementation.
For the purposes of this hack, let's say you only need to support Mac and Windows, or that you need to open files from the iTunes Music Store (QTJ can do it, which is apparently the only way to do it in Java), or for whatever reason QTJ looks like the right solution. Example 10-5 shows a port of the JavaSound audio player to a QTJ-based implementation.
Example 10-5. Playing audio with QuickTime for Java
import quicktime.std.*;

import quicktime.std.clocks.*;

import quicktime.std.movies.*;

import quicktime.*;

import quicktime.io.*;

import quicktime.app.time.*;



public class QTJSound extends Object {



	File soundFile;

	JDialogplayingDialog;

	Movie movie;



	public static void main (String[] args) {

		JFileChooser chooser = new JFileChooser();

		chooser.showOpenDialog(null);

		File f = chooser.getSelectedFile();

		try {



			QTJSound s = new QTJSound (f);

		} catch (Exception e) {

			e.printStackTrace();

		}

	}



	public QTJSound (File f)

		throws QTException {

		soundFile = f;

		// prepare a dialog to display while playing

        JOptionPane pane = new JOptionPane ("Playing " + f.getName(),

								JOptionPane.PLAIN_MESSAGE);

		playingDialog = pane.createDialog (null, "QTJ Sound");

		playingDialog.pack();



		// get and play sound

		QTSession.open();

		QTFile qtf = new QTFile (f);

		OpenMovieFile omf = OpenMovieFile.asRead (qtf);

		movie = Movie.fromFile (omf);

		MyDemoCloser closer = new MyDemoCloser (movie);

		TaskAllMovies.addMovieAndStart ();

		movie.start();

		playingDialog.setVisible(true);

	}

	class MyDemoCloser extends ExtremesCallBack {



		public MyDemoCloser (Movie m) throws QTException {				super (m.getTimeBase(),

				StdQTConstants.triggerAtStop);

            callMeWhen();

		}

  public void execute() {

			playingDialog.setVisible (false);

			System.out.println ("dialog closed");

			// note: this can hang on Windows - consider

			// using QTSession.exitMovies() instead

			QTSession.close();

			System.out.println ("closed QTSession");

			System.exit(0);

		}

	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add MP3 Support to JMF
Inhaltsvorschau
MP3s are everywhere, and by installing a plug-in you can use them with Java Media Framework, too.
It used to be said that every program will continue to grow until it includes an email reader. Today we could say the same for MP3 players. They are everywhere, and any program that has plug-ins will eventually be given a music player. Playing MP3s in Java used to be quite an ordeal, involving a suite of toolkits and codecs from different sources. Fortunately, it's a lot easier to play an MP3 file these days, and this hack shows how.
JMF came out in 1998, supporting playback of a number of audio and video formats, but not MP3. Support for this popular format arrived with JMF 2.0 in 1999. Unfortunately, in 2002, Sun removed MP3 support from JMF because of licensing problems. Finally, in November of 2004, Sun released a fully licensed MP3 plug-in for public download on their web site. With this plug-in, you can play any MP3 file with only four lines of code.
First, install Java Media Framework [Hack #72] . To add MP3 support, download the plug-in from http://java.sun.com/products/java-media/jmf/mp3/download.html. The download page offers an .exe installer for Windows and a ZIP for other platforms. In both cases, there is an mp3plugin.jar file that the install docs say you need to place in the ext/lib directory of any JRE you want to provide the plug-in to. With the JAR in your classpath, you install the plug-in with the following command:
	java com.sun.media.codec.audio.mp3.JavaDecoder
On Mac OS X, the proper way to add JAR files to the classpath is to put them in /Library/Java/Extensions, instead of using the actual ext/lib directory, which is hard to find and will be wiped out by system installers and updates anyways.
Example 10-6 is the code for pretty much the simplest MP3 playing program you can create. You will need to import javax.media.* in addition to the usual
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Build an Audio Waveform Display
Inhaltsvorschau
With a little understanding of audio data formats, you can easily build a basic graphical audio display.
Representing audio visually is extremely useful. You can use waveform displays to quickly tell audio files apart, like a file thumbnail, or for non-linear editing, such as deleting parts of the file and processing.
Figure 10-4 shows a waveform displayed in Audacity, a free, open source audio editing application. This hack shows you how to build a basic waveform display from raw audio data.
Figure 10-4: Audacity with an audio waveform displayed
The end result of this hack is displayed in Figure 10-5. You'll start by reading in the entire audio file using an AudioInputStream. Then you'll convert the raw data from the stream into useful audio samples, organized by channel. With the converted channel audio data, you'll create a single waveform panel. Then you'll wrap up the complete audio display by combining several waveform panels to display multi-channel audio.
Figure 10-5: The waveform display you'll build in this hack
You'll need to know a few basic terms and concepts about audio before you get started.
Sample
One measurement of audio data. For Pulse Code Modulated (PCM) encoding, a sample is an instantaneous representation of the voltage of the analog audio. There are other types of encoding, like μ-law and a-law, that are rarely used.
Sampling Rate
The number of samples in one second. Measured in Hertz (Hz) or kilo-Hertz (kHz). The most common sampling rate is 44.1 kHz (CD quality audio). Often, you'll find 22.05 kHz or 11.025 kHz on the Web, since the files are smaller and the conversion is easier.
Sample Size
The number of bits in one sample. It is typically a multiple of eight because data is stored in 8-bit bytes. The most common sample size is 16 bits, which is CD quality audio. Often you'll find 8-bit audio because the files are smaller. You'll rarely find anything less then 8-bit audio because the quality is pretty poor. Sample size is sometimes called bit depth.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Play Non-Trivial Audio
Inhaltsvorschau
When loading an entire audio clip into memory is a bad idea (or just impossible), you have to take JavaSound responsibilities into your own hands.
Playing JavaSound audio with a Clip [Hack #71] is a pretty convenient way to play a short sound, like a sound effect for a desktop application. The only problem is that the Clip loads all the audio into memory, which could have a couple of bad side effects:
  • It makes your application use more memory, which could cause problems.
  • The audio you need might not fit into memory at all.
You might have run into this second point if you tried to load a really big audio file into a Clip. For example, I took a 3 minute, 45 second track from a CD and converted it to 8-bit mono PCM in an AIFF file, which ended up being 9.4 MB. You can guess what happened:
	[aeris:HacksBook/Media/52] cadamson% java CoreJavaSound			

	javax.sound.sampled.LineUnavailableException: Failed to allocate clip data: 

	Requested buffer too large.

		at com.sun.media.sound.MixerClip.implOpen(MixerClip.java:536)

		at com.sun.media.sound.MixerClip.open(MixerClip.java:161)

		at com.sun.media.sound.MixerClip.open(MixerClip.java:249)

		at CoreJavaSound.<init>(CoreJavaSound.java:39)

		at CoreJavaSound.main(CoreJavaSound.java:17)
Unfortunately, most of the JavaSound code you'll find on the Web deals with Clips only and not with getting a Line for larger files, or potentially endless streams for that matter. Why? Perhaps because JavaSound doesn't do it for you—you are responsible for reading bytes and feeding them to JavaSound!
This hack is going to play an uncompressed (i.e., PCM) AIFF or WAV file of arbitrary length by getting a DataLine for the data and then repeatedly reading the data from disk and writing it to the DataLine.
PCM stands for Pulse Code Modulation, which means that analog audio has been sampled at regular intervals and quantized (i.e., each sample is expressed as a numeric value). It's the lowest-level, most common denominator data that JavaSound understands, since it can be delivered directly to a sound system for playback.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Show Audio Information While Playing SoundHack
Inhaltsvorschau
Providing visual feedback for JavaSound audio, or at least trying to….
You might want to play a clip without any corresponding visuals; for example, if you were using it to signal the end of a long-running process such as uploading a file. On the other hand, if the sound is the focus of the application, as in a music-player application, you might need to show the user some information about the audio he's playing.
You already know how to play audio from a file or stream [Hack #76] ; building on that, you can create a simple GUI that shows some of the basic traits of the audio, by pulling fields out of the AudioFormat object, which can be retrieved from the Line once it has been created. These fields include the audio format, bits/sample, frame size and rate, and endianness (which indicates how two-byte values are to be interpreted: big-endian means the first byte is more significant, and little-endian means the second is).
More impressively, DataLine provides a getLevel() method that returns the current level of the audio being played, as a float from 0.0 (silence) to 1.0 (maximum volume). You can use this to create a graphical level meter by getting the level and coloring in that percentage of a component. For example, if the level is 0.5, you'd fill in half of the component.
Drawing this level meter is pretty straightforward: create a JPanel whose paint() method clears the Graphics, gets the line level, and fills a rectangle starting at (0,0) with a height equal to the component's height and a width equal to the level times the component's width. Then you need to set up an animation loop—a javax.swing.Timer is convenient because it avoids any thread-safety issues while doing the painting—to repeatedly call repaint() on the meter.
Combine this together and you have the DataLineInfoGUI, seen in Example 10-10. Note that to play the audio, it uses the PCMFilePlayer
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Provide Audio Controls During Playback
Inhaltsvorschau
Let your users take control of JavaSound playback.
To complete this set of JavaSound-related hacks, why not give the user the opportunity to control the sound as it plays? JavaSound provides a very dynamic means of getting at controls like gain and pan (more commonly thought of as volume and balance) through a discovery mechanism that you can use to support any kind of control that might exist, even a control you know nothing about.
On the other hand, JavaSound presents a control not as a GUI widget, but just as an object that can affect the behavior of a Line. This hack will help you provide the GUI side.
The Control class simply defines a getType() and toString() method. What's more interesting is its subclasses, each of which defines a different kind of control:
BooleanControl
Controls a value that can be either true or false
EnumControl
Controls a value that can be one of n known values
FloatControl
Controls a value that is expressed as a floating-point number
CompoundControl
Controls multiple properties, and itself contains multiple controls
You can get the Controls supported by your Line simply by calling Line. getControls(), which returns an array of Controls. You can also ask for a specific control by using a constant of the Control.Type subclass, such as BooleanControl.Type.MUTE or FloatControl.Type.MASTER_GAIN. Pass this constant to Line.isControlSupported() to see if the control is available for the given line, and then get the control object with Line.getControl().
If you look at the subclasses of Control, you'll see that each provides getter and setter methods appropriate to its data type. BooleanControl, for example, has a getValue() that returns a boolean and a setValue() that takes a boolean. FloatControl has similar methods that work with floats. Each also provides a number of what might be called "verbiage methods" that provide text for building a GUI. For example, FloatControl offers label names for its minimum, maximum, and middle values, and another that describes the units represented by the control (like "dB" for a gain-related control, or "frames per second" for one that is timing-related).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 11: Native Integration and Packaging
Inhaltsvorschau
You can try really hard to develop a desktop application that looks good, feels right, and meets the user's needs, but if a Windows or Mac user has to drop down to a command line and type java -jar MyCoolApp.jar to run it, it's not going to win you any points in the user experience department. There are points of integration with the native platform that you'll often want and need to access from a Swing application, or specific functionality you'll want to provide on a platform-by-platform basis, and that's what this chapter is about.
Actually, this chapter was almost rendered irrelevant by the JDesktop Integration Components (JDIC) project on Java.net (https://jdic.dev.java.net/), which is addressing the most serious needs for desktop Java applications: creating platformappropriate double-clickables, providing access to the native web browser component, associating Java applications with certain kinds of documents, etc. JDIC may solve some of the biggest issues facing Java on the desktop…which leaves us all the more room for creative hackery.
With one simple command you can tell Windows to open files, directories, and URLs on your behalf.
Swing programmers have always had difficulty dealing with native operating systems because of Java's cross-platform nature. Even simple things like opening a web browser require building native hooks with JNI or building on top of custom libraries. This hack will show you how to open files, URLs, and start an email app without using any native libraries or custom C coding.
Native integration in Java has always depended on the Java Native Interface, or JNI. Whether you code to JNI directly or use a third-party library, you are still dealing with native C code through a Java layer. This has always been problematic because in order to write a JNI library, you need to know a lot about the internals of the underlying operating system. Most Java developers went to Java to
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 79–87: Introduction
Inhaltsvorschau
You can try really hard to develop a desktop application that looks good, feels right, and meets the user's needs, but if a Windows or Mac user has to drop down to a command line and type java -jar MyCoolApp.jar to run it, it's not going to win you any points in the user experience department. There are points of integration with the native platform that you'll often want and need to access from a Swing application, or specific functionality you'll want to provide on a platform-by-platform basis, and that's what this chapter is about.
Actually, this chapter was almost rendered irrelevant by the JDesktop Integration Components (JDIC) project on Java.net (https://jdic.dev.java.net/), which is addressing the most serious needs for desktop Java applications: creating platformappropriate double-clickables, providing access to the native web browser component, associating Java applications with certain kinds of documents, etc. JDIC may solve some of the biggest issues facing Java on the desktop…which leaves us all the more room for creative hackery.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Launch External Programs on Windows
Inhaltsvorschau
With one simple command you can tell Windows to open files, directories, and URLs on your behalf.
Swing programmers have always had difficulty dealing with native operating systems because of Java's cross-platform nature. Even simple things like opening a web browser require building native hooks with JNI or building on top of custom libraries. This hack will show you how to open files, URLs, and start an email app without using any native libraries or custom C coding.
Native integration in Java has always depended on the Java Native Interface, or JNI. Whether you code to JNI directly or use a third-party library, you are still dealing with native C code through a Java layer. This has always been problematic because in order to write a JNI library, you need to know a lot about the internals of the underlying operating system. Most Java developers went to Java to get away from that sort of thing, so it's often not worth it for something simple like opening a URL. There is another way of talking to the native OS, though. You can use Runtime.exec().
Since 1.0, Java has had the Runtime.exec() static function to start another program directly and pass command-line parameters. It's easy to forget about command-line utilities, but for simple things they can be far, far easier than trying to deal with JNI. The disadvantages of calling a native program over a Java API are of course speed, since you are starting a new process, and the fact that the program is not cross-platform. This may be an acceptable tradeoff, however, since you could disable the feature that needs the exec() call or provide a different command for other platforms.
Windows 2000 introduced a small program called start. Originally a separate install, the start program is now just a command built into the cmd.exe that comes with Windows XP. It is a simple command but it can do some powerful things, such as opening a file with the default viewer, showing a directory in the file explorer, or even launching a web browser.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Open Files, Directories, and URLs on Mac OS X
Inhaltsvorschau
Open files, directories, and URLs in external programs right from your Swing app.
We can't let Windows have all of the fun. Mac OS X has a similar and even easier to use program called open, which lets you open any file, directory, or web page directly from the command line. This hack shows you how to embed open in your own program.
Back in the late 80s, NeXT, Inc. created the first true integration between a graphical user interface and a Unix-like operating system when they released NeXTSTEP. Part of this OS was a command-line program called open, which could open a file, directory, and (once the Web was invented) a URL. Apple purchased NeXT in the late 90s and NeXTSTEP became the core of Mac OS X. Along with this purchase came the open command, still as useful as ever. open has a simple syntax: open filename. By calling it from within your program, you can open any file with its default application. open will start launching the viewer automatically and return control to your program immediately. As in Microsoft Windows, you can call the program using Runtime.exec():
	public static void main(String[] args) throws Exception {

		Runtime rt = Runtime.getRuntime();

		rt.exec("open notes.txt");

	}
This program will open a file in the current directory (notes.txt), in the default viewer for text files, usually TextEdit. You can also specify an absolute path for the file:
	rt.exec("open /Users/josh/Desktop/notes.txt");
If you pass a directory instead of a file, then OS X will open that directory in a new Finder window. This can be useful for showing the location of a recently downloaded file or demonstrating where to install new software:
	// open the current working directory

	rt.exec("open .");

	// open the applications directory

	rt.exec("open /Applications");
Finally, you can open any web page using the user's default web browser (probably Safari) by calling open with a URL:
	// open Yahoo! in the user's web browser

	rt.exec("open http://www.yahoo.com/");
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Mac Applications Behave Normally
Inhaltsvorschau
Setting a few system properties will make your application seem more like other Mac apps.
Of the desktop platforms your application is likely to run on, the Mac is the least like the others. Maybe it's because the various Linux desktops hemmed closely to Windows' ways of thinking, or maybe the GNOME guys had never used a Mac and didn't "think different." But the result is that certain assumptions you might reasonably make on Windows or Linux—like assuming that windows have menu bars and that any corner or edge of a window can be dragged to resize the window—aren't correct on the Mac.
To smooth over the cross-platform differences somewhat, Apple does certain things differently in its Java implementation. For one thing, it will automatically put a Swing application into its native Look and Feel, Aqua, rather than defaulting into cross-platform Metal or Ocean as would happen on other platforms. In other words, you don't have to do anything special to pick up the Mac Look and Feel, although redundantly asking for and setting the native Look and Feel classname doesn't hurt either.
Moreover, Apple provides some key/value pairs that you can set in the Java system properties to get even more Mac-like behavior. Because these properties all start with apple or com.apple, you can set them and not worry that they'll affect the behavior of your application on any other platform.
Apple has been changing the names and behaviors of these system properties for a while, and some of them are deprecated or no-op'ed, so I'll just show four of the most useful ones here. To see the whole list, check out the Runtime System Properties of Apple's Java 1.4.1 release notes on http://developer.apple.com/releasenotes/Java/index.html.
You can set the properties several ways. The obvious way is to use the -D command-line argument:
	java -Dapple.awt.showGrowBox=true MyClass
However, this becomes tedious to type after you decide to use multiple properties. Another option is to simply call
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Control iTunes on Mac OS X
Inhaltsvorschau
With a few bits of AppleScript, you can monitor and control Macintosh iTunes from your own application.
You can make some really great applications using plain Java, but the software world isn't what it used to be. The buzzword for new applications is integration. Modern programs are defined not only by what they do, but what they can talk to. The poster child for integration these days is iTunes, so what better way to show off the power of Java than by taking control of iTunes directly from your own Java app!
The task of dealing with native applications is, by nature, platform specific. For example, though Apple ships identical looking copies of iTunes for both Windows and Mac, the integration APIs couldn't be more different.
Most well-written Mac OS X applications support an API called Apple Events. Apple Events let a programmer send commands and requests to a running application from another program, through a process often called scripting. The application must be written to support Apple Events and every scriptable feature must be defined explicitly when the program is written. Since it was Apple that wrote iTunes, they did a very good job of exposing virtually every feature through Apple Events. All you need to do is tap into these events.
Apple Events is an API, and you need a programming language to support it. There are a variety of languages to choose from, but the easiest one to start with is AppleScript, as the syntax is simple and OS X ships with a command-line interpreter. There are also direct Java bindings available, but for the kinds of simple things you are likely to want to do with iTunes, exec()ing the interpreter will be much easier. All you have to do is call osascript with the AppleScript commands you want to run, and OS X will do the rest.
AppleScript is a simple language with a somewhat natural language feel to it. If you want to tell iTunes to toggle the Play/Pause button, you can call this script from the command line:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Control iTunes Under Windows
Inhaltsvorschau
Use a simple open source library to monitor and control Windows iTunes from your Swing application.
Windows doesn't have a standard scripting API like Apple Events, but it does have another object model that iTunes supports. Using an open source library, this hack will show you how to script iTunes just as easily on Windows as you can on the Mac.
The Component Object Model (COM) is a standard way for Windows components to expose functionality that other programs can call at runtime. com4j (https://com4j.dev.java.net/) is an open source library that creates connections from Java programs to COM objects. com4j has two parts: a command-line program to create the Java interfaces that your program will call, and a native library that binds your program to the COM object at runtime.
com4j uses class annotations to do its magic, so you can only use it with Java 5.0 or greater.
To get started, download the com4j package at https://com4j.dev.java.net/servlets/ProjectDocumentList. With the com4j stubber and the iTunes executable in your current directory, you can generate the interfaces like this:
java -jar tlbimp.jar -o jtunes -p test.jtunes iTunes.exe
This command will load the iTunes executable and look for COM definitions. Once they are located, tlbimp will generate a bunch of Java interfaces in the test.jtunes package and put the .java files into the jtunes directory. If you look at the generated Java interfaces, you will see a whole slew of methods and objects for playing, querying tracks, and dealing with virtually every other feature of iTunes. com4j will also pull out any embedded documentation and insert the documentation as JavaDoc comments in the generated interfaces.
This process is pretty quick, so you may find it useful to call it from Ant as part of your compile process.
Once you have the interface stubs, you can create a program to control iTunes quite easily. You can use the same program that you did when controlling iTunes on the Mac
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Construct Single-Launch Applications
Inhaltsvorschau
Only allow one instance of a program, notifying the existing instance when the user tries to launch a new one.
Most graphical desktop applications are designed for multitasking. You start your program to work on something, then switch to another program and come back later. Oftentimes you'll leave a large program, like a word processor, running in the background to be used again when you need to open another document, say an email attachment you received. When you click on the attachment, your operating system won't start a new instance of the word processor; instead, it will send a message to the currently running instance to open the new file—saving lots of system resources.
Java programs aren't designed with single-launch behavior in mind. They still use the old Unix style of single use, command-line launching. You start the program to do something and it finishes quickly. If you use the program again it will start a new instance, do the work, and finish. There is never any instance reuse, but modern desktop programs demand it. Because Java doesn't support single-launch applications, this hack shows you how to build it into your programs with a simple use of sockets.
Building a single-launch application requires two parts. When the program starts, it needs to detect if another copy is already running. If there is, then the program can quit instead of continuing to launch. The new program also needs to tell the first copy about any command-line arguments—the filename to open, for example. You could create a temp file in a known location and look to see if it already exists. This would take care of multiple program instances, but not passing arguments around. Plus, you would need to worry about race conditions and cleaning up the temp file when the last program exits. Thankfully, there's a much better solution: local sockets.
A socket is a network connection defined by a hostname and a port. A
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Stuff Stuff in JARs
Inhaltsvorschau
Hide images, sounds, and more inside JAR files.
Does your application need a special installer? Do you have to put images, sounds, icons, and properties all in their own folders or other special locations relative to your application? Does your application launch with a .sh script on Unix or a .bat on Windows?
You do? Really? I was just speaking rhetorically. I kind of figured everyone was using JARs by now.
JAR files—the acronym is short for Java ARchive—must be the best-known, least-used feature in Java. Many developers throw a JAR in their classpath to pick up some standard extension API or third-party library, but how many actually distribute their software that way?
And JARs aren't just about code. It's really easy to put the files your program needs into a JAR. This has the added advantage of hiding your images, sounds, default settings, and so forth from end users.
But to load these items, you need to make a change in how you load stuff in your code. Instead of specifying a known path or URL, you ask a ClassLoader to find these resources along the classpath. By doing this, you can get your resources from flat files while you're developing, and then easily switch to getting them from inside a JAR when the code is deployed in the field.
The key is the ClassLoader's getResource() and getResources() methods, which take a path relative to the loaded class and return a URL and an array of URLs respectively. A getResourceAsStream() method converts the URL to an InputStream as a convenience.
To clarify the relative path: say you have a directory that includes your compiled classes in a path like com/mycompany/mypackage/…, an images directory, and a sounds directory. A relative path would be one of the form images/something.png, sounds/something.aiff, etc. By using a resource on the classpath, there is no difference (to the user) between files in a JAR and files in sub-directories on a filesystem. Either way, you get a
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Quick Look and Feel Changes
Inhaltsvorschau
Customize Metal with custom fonts, colors, and even system-bound properties using just a few API calls.
Swing is a very customizable UI toolkit. The most advanced way of changing the look of your application is with a custom Look and Feel (L&F), but they can be tricky to build. Swing's L&F API is very complicated, often requiring thousands of lines of code for a complete custom theme. Fortunately, if you want to change just a few colors or fonts, the L&F API provides a much easier way. This hack shows how to create simple visual changes using UI properties.
Every Look and Feel that extends the javax.swing.plaf.basic.* classes can accept special properties that define the behavior of each Swing component. For example, there is property to control the background color of every JButton. If you change this property at the start of your program, then every JButton you create will have that background color.
The UI properties are stored in a static class called the UIManager. To set a property, just put in the name of the property and a value object like a Color. To set the background color of a button to green, you would do something like this:
	UIManager.put("Button.background", Color.green);
It is important to set these properties before any components are created or they won't pick up the new settings.
Below is the code for a simple program that shows a few components in a frame. It has a button, label, and text field along the top and a text area in a scroll pane in the middle. There is also a simple file menu at the top. Before creating any components, it sets the foreground and background colors for the button, label, text field, and panel to light and dark green:
	public static void main(String[] args) throws Exception {

		Color bg = Color.green.brighter();

		Color fg = Color.green.darker();

		UIManager.put("Button.background",bg);

		UIManager.put("Button.foreground",fg);



		UIManager.put("Label.background",bg);

		UIManager.put("Label.foreground",fg);

		UIManager.put("TextField.background",bg);

		UIManager.put("TextField.foreground",fg);

		UIManager.put("Panel.background",bg);

		UIManager.put("Panel.foreground",fg);



		JTextArea jta = new JTextArea();

		jta.setText("text\ntext\ntext\ntext\ntext\ntext"+

			"\ntext\ntext\ntext\ntext\ntext");

		JScrollPane scroll = new JScrollPane(jta);

		JButton button = new JButton("A Button");

		JLabel label = new JLabel("A Label");

		JTextField text = new JTextField("A TextField");



		JMenuBar mb = new JMenuBar();

		JMenu file = new JMenu("File");

		file.add(new JMenuItem("Open"));

		file.add(new JMenuItem("Close"));

		mb.add(file);

		JFrame frame = new JFrame("Custom LaF Defaults");

		JPanel top = new JPanel();

		top.setLayout(new BoxLayout(top,BoxLayout.X_AXIS));

		top.add(button);

		top.add(label);

		top.add(text);



		JPanel panel = new JPanel();

		panel.setLayout(new BorderLayout());

		panel.add("North",top);

		panel.add("Center",scroll);



		frame.getContentPane().add(panel);

		frame.setJMenuBar(mb);



		frame.pack();

		frame.setSize(300,200);

		frame.setVisible(true);

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create an Inverse Black-and-White Theme
Inhaltsvorschau
Create a custom black-and-white theme for monochrome LCD displays using a few simple UIManager calls.
The UIManager lets you set simple resources for color, fonts, and padding, but you may have noticed that if you want to theme all of the components, you need to set properties on each one. Since Swing has over 300 Look and Feel properties, this could become a problem. Fortunately, there is another way to make global changes without creating an entire custom L&F. Metal, the standard cross-platform L&F that comes with Swing, can use themes. This hack demonstrates how to create a Metal theme that forces the components to use only black and white.
Swing lets you switch between different Look and Feels. You can use a native Look and Feel (such as the one that comes with Mac OS X) or a thirdparty Look and Feel if you have them installed. Swing also comes with a standard L&F called Metal. Metal is built into the JRE and is always available, making it the ideal L&F to customize.
Like all L&Fs, Metal has many, many classes that you can subclass to make changes. However, it also comes with an interface called MetalTheme. If you implement MetalTheme, then you can customize most of Metal without digging into the details. Most of the colors in Metal can be set, in fact, with just six values. Metal makes it even easier by providing a default implementation called, unsurprisingly, DefaultMetalTheme that you can use as a starting point.
A Metal theme is defined by a series of colors and fonts. The most important ones are the three primary and three secondary colors. These define the standard set of colors used for every widget on screen. Certain components have additional colors, like the text selection, but almost everything is based on these six. Example 11-9 is a theme that uses only white and black. It is useful for embedded devices that can't afford the hardware or memory requirements of a color display.
Example 11-9.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Chapter 12: Miscellany
Inhaltsvorschau
Not everything about Swing fits into nice little groupings of functionality. Some of the cool stuff you can do involves cursors, event-dispatching, networking, and even the lights on the keyboard. So, here we present some hacks that were just unique (or weird) enough to defy easy categorization.
Use the setCursor() method and an animation thread to show a frame's busy status.
One of Swing's lesser-known features is the ability to change the mouse cursor on a per-component basis. Because the cursor change happens very quickly, you could combine this ability with some simple threading to create an animated cursor. This hack will show you how to create an animated cursor useful for showing a busy status.
Any Swing component—even a frame—can have a custom cursor. The application as a whole will retain the normal mouse cursor, but when the user moves over the appropriately configured component, the cursor will change to whatever is set for that component. This behavior lends itself nicely to restricting user access during long running processes because the rest of the application can look and feel responsive while the portion that represents the process is visually unusable by the custom cursor.
The best way to manage a short animation is by pre-generating your images in an array. This lets you loop through the array rather than creating a bunch of nasty conditionals. If you later expand the animation, you can just add more images to the array, leaving the rest of the code untouched.
First, you need to build an AnimatedCursor class and generate the images in the constructor. In this case, the images are instances of the java.awt.Cursor object. I used standard, predefined cursors to keep the code easy, but you could also use cursors derived from custom images. A JFrame is passed into the constructor and stored for later use, as you can see in Example 12-1.
Example 12-1. A simple animated cursor
	public class AnimatedCursor implements Runnable, ActionListener {

		private boolean animate; 

		private Cursor[] cursors;

		private JFrame frame;

		public AnimatedCursor(JFrame frame) {

			animate = false;

			cursors = new Cursor[8];

			this.frame = frame;

			cursors[0] = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);

			cursors[1] = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR);

			cursors[2] = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);

			cursors[3] = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR);

			cursors[4] = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);

			cursors[5] = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR);

			cursors[6] = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);

			cursors[7] = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR);



		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Hacks 88–100: Introduction
Inhaltsvorschau
Not everything about Swing fits into nice little groupings of functionality. Some of the cool stuff you can do involves cursors, event-dispatching, networking, and even the lights on the keyboard. So, here we present some hacks that were just unique (or weird) enough to defy easy categorization.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Display a Busy Cursor
Inhaltsvorschau
Use the setCursor() method and an animation thread to show a frame's busy status.
One of Swing's lesser-known features is the ability to change the mouse cursor on a per-component basis. Because the cursor change happens very quickly, you could combine this ability with some simple threading to create an animated cursor. This hack will show you how to create an animated cursor useful for showing a busy status.
Any Swing component—even a frame—can have a custom cursor. The application as a whole will retain the normal mouse cursor, but when the user moves over the appropriately configured component, the cursor will change to whatever is set for that component. This behavior lends itself nicely to restricting user access during long running processes because the rest of the application can look and feel responsive while the portion that represents the process is visually unusable by the custom cursor.
The best way to manage a short animation is by pre-generating your images in an array. This lets you loop through the array rather than creating a bunch of nasty conditionals. If you later expand the animation, you can just add more images to the array, leaving the rest of the code untouched.
First, you need to build an AnimatedCursor class and generate the images in the constructor. In this case, the images are instances of the java.awt.Cursor object. I used standard, predefined cursors to keep the code easy, but you could also use cursors derived from custom images. A JFrame is passed into the constructor and stored for later use, as you can see in Example 12-1.
Example 12-1. A simple animated cursor
	public class AnimatedCursor implements Runnable, ActionListener {

		private boolean animate; 

		private Cursor[] cursors;

		private JFrame frame;

		public AnimatedCursor(JFrame frame) {

			animate = false;

			cursors = new Cursor[8];

			this.frame = frame;

			cursors[0] = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);

			cursors[1] = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR);

			cursors[2] = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);

			cursors[3] = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR);

			cursors[4] = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);

			cursors[5] = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR);

			cursors[6] = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);

			cursors[7] = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR);



		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Fun with Keyboard Lights
Inhaltsvorschau
Flash the Caps Lock, Num Lock, and Scroll Lock keys for extra user feedback.
The AWT and Swing APIs are huge and full of robust components and frameworks for building big applications. They also have some dark corners where the lesser-known functions live. While cruising through the JavaDoc for java.awt.Toolkit, I ran across a function I had never noticed before, despite it being in the API for over four years. This hack explores building a keyboard busy indicator using the Toolkit.setLockingKeyState() function.
The root class of AWT, Toolkit, has a very interesting little function: setLockingKeyState(). You pass it the KeyEvent for the key you want to lock down and turn it on or off with the boolean. For most keyboards, this means the Caps Lock, Num Lock, and Scroll Lock keys (some keyboards may also have a Kana lock for Kanji support). Now that you have this nifty little function, what should you do with it?
My first thought was a busy cursor. If you've got three lights in a row, why not blink them off and on in sequence? The code in Example 12-2 will flip each light on and off in order, creating a moving bar effect (depending on the order of your keyboard LEDs).
Example 12-2. Lights, camera, action
	class SpinnerThread extends Thread { 

		private boolean go;  

		public void quit() {

			go = false;

		}

		public void run() {

			go = true;

			// get a toolkit

			Toolkit tk = Toolkit.getDefaultToolkit();

			// save the old key states

			boolean old_num, old_caps, old_scroll;

			old_num = tk.getLockingKeyState(KeyEvent.VK_NUM_LOCK);

			old_caps = tk.getLockingKeyState(KeyEvent.VK_CAPS_LOCK);

			old_scroll = tk.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);



			// set all keys to off

			tk.setLockingKeyState(KeyEvent.VK_NUM_LOCK,false);

			tk.setLockingKeyState(KeyEvent.VK_CAPS_LOCK,false);

			tk.setLockingKeyState(KeyEvent.VK_SCROLL_LOCK,false);
SpinnerThread is a Runnable implementation, meaning you can launch it with new Thread(new SpinnerThread()).start(). The
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Create Demonstrations with the Robot Class
Inhaltsvorschau
Use the Robot class to control the mouse cursor and create interactive software features.
One of the coolest things about Swing is that you can often use a class for something completely different than what it was originally intended. The java.awt.Robot class, for example, can move the mouse cursor programmatically. This feature was originally intended for use by automated testing tools (hence the name Robot), but I've found it very useful to demonstrate software features by moving the mouse cursor through the same actions as the user. Instead of just describing something in a help file, you can actually show the user what to do—and this hack explains how.
To create a mouse animation, you need three things:
  1. The ability to move the cursor
  2. The start and end points of the animation
  3. A way to smoothly interpolate the cursor position
The java.awt.Robot class has a variety of methods for capturing program state and controlling the user interface, including Robot.mouseMove(), which allows you to move the mouse cursor programmatically.
The following code is the implementation of a method moveMouse(), which takes three arguments: a starting component, an ending component, and a duration for the animation (in milliseconds). Because most demonstrations involve showing the user a particular component, the easiest points to use are the centers of start and end components. The mouse will smoothly move from the center of the starting component to the center of the ending one. We normally think of components as being positioned relative to their parents, but since the Robot class uses absolute mouse positions, you'll need to convert the components to their screen locations:
	public void moveMouse(JComponent start, JComponent end,

		final int duration) throws Exception {

		final Robot robot = new Robot();

		// get middle of start

		final Point start_coords = start.getLocationOnScreen();

		start_coords.translate(start.getWidth()/2,



			start.getHeight()/2);



		// get middle of end

		final Point end_coords = end.getLocationOnScreen();

		end_coords.translate(end.getWidth()/2,



			end.getHeight()/2);

		// create interpolation point and offsets

		int steps = duration/50;

		//Point current = new Point(start_coords);

		int distx = (end_coords.x - start_coords.x);

		int disty = (end_coords.y - start_coords.y);



		for(int i=1; i<=steps; i++) {

			int x = start_coords.x + i*distx/steps;

			int y = start_coords.y + i*disty/steps;robot.mouseMove(x,y);

			try { Thread.currentThread().sleep(50);

			} catch (Exception ex) {}



		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Check Your Mail with Swing
Inhaltsvorschau
Add email checking to your application with just a few method calls.
As email becomes a bigger part of our daily lives, I have seen it creep into more and more places. My email program alerts me when there is new mail. I can check my email via the phone. I log in to my web mail from an Internet cafe. Email is everywhere, so why shouldn't it be in your Swing application? This hack shows how to embed in your application an email checker that shows the current number of unread messages and can launch the user's email application.
Dealing with email servers can be a complicated and tricky business. To help address these issues, Sun created the JavaMail API, which is a set of classes defining a vendor-neutral interface for accessing email servers.
Sun's sample implementation provides IMAP support, which is what I will demonstrate here. If you have another kind of email server, such as Exchange, you could install your own service provider and use it the same way.
The code in this hack needs to do two things. First, it must open a connection to the email server periodically and check for new mail. Second, it must launch the user's email program on a double-click. I have encapsulated the email checking and launching code into separate classes, making it very easy to add to an existing program.
The EmailChecker class, shown in Example 12-5, is a simple Runnable implementation that receives a JLabel to its constructor. The run loop will sleep for a certain amount of time (one minute in this case) then call checkEmail(). Every time there is new mail, it will set the text of the label to something like "You have N new messages."
Example 12-5. Checking for new messages
	import java.util.Properties;

	import javax.swing.JLabel; 

	import javax.swing.SwingUtilities;

	import javax.mail.*;



	public class EmailChecker implements Runnable { 

		private JLabel label;

		public EmailChecker(JLabel label) {

			this.label = label;

		}



		public void run() {

			while(true) {

			

				try {    

				checkEmail();   

				Thread.currentThread().sleep(1000*60); // sleep 1 min

				} catch (Exception ex) {

				System.out.println("exception: " + ex);

				ex.printStackTrace();



				}

			}



		}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Don't Block the GUI
Inhaltsvorschau
Thread your heavy lifting so the event-dispatch thread stays responsive.
Practically every AWT and Swing book you'll ever see keeps things simple by responding to button clicks, menu selections, and other actions by doing something in the event listener. That's probably good for helping you learn the various GUI widgets, but it sets you up for a really bad habit: putting increasingly long-lasting calls in your event callbacks.
This is bad because the thread that calls actionPerformed(), valueChanged(), and other event-based methods is the same thread that services GUI events throughout AWT and Swing. The AWT Event Dispatch Thread is responsible for polling for events, dispatching them to listeners, and for repainting everything. If you block it on some long-lasting call—such as database or network access, intense calculation, etc.—then mouse clicks and key-presses won't be processed, menus won't be available, portions of your GUI may not get repainted if they become obscured by other windows, etc. Oh, and the user will hate you. Just so you know.
The trick, then, is to keep heavy lifting out of the event-dispatch thread. There's a very straightforward way to do this in Java: move complicated processing to its own thread, and let event dispatching continue immediately after starting this new thread. Then you just have to deal with cleanup when the launched thread finishes up.
AWTBlockDemo, shown in Example 12-8, offers a test bed for exhibiting and fixing the problem. It offers a JTextField along with two JButtons: Load (blocking) and Load (non-blocking). A menu also offers the blocking and non-blocking load as JMenuItems, along with a Quit menu item.
The text field takes a URL. When you click one of the load buttons or menu items, it loads the file at that address into the text area. The text area is pre-populated with the address for java.awt.Component in Sun's JavaDoc, a nice 300 KB file that will take a little while to load, even with a fast network connection.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Code Models That Don't Block
Inhaltsvorschau
Models should know that they're doing work on another thread.
You already know how to keep Swing responsive by moving expensive operations off the event-dispatch thread [Hack #92] . However, one downside to that approach is that it uses a separate inner class to coordinate the interaction between the work being done on the other thread and the Swing components. If you reused the same widgets in several places, you wouldn't want to have to write the "start a thread and populate when done" code over and over again. So don't. Why couldn't the model be responsible for this sort of behavior?
Well, the model can—you just have to do a little thinking. Models in Swing are generally in two states: null (no data) or populated with data. What if you had a third state, one that indicated that the model was still loading its data?
Adapt Example 12-8 to create AWTBlockModels.java. To illustrate the loading, this hack has a JProgressBar that you need to declare before the constructor, and which you add at the bottom of initMainLayout():
	progressBar = new JProgressBar (0, 100);

	getContentPane().add (progressBar, BorderLayout.SOUTH);
The strategy in this hack is to make the JTextArea's model responsible for its own threaded loading, so get rid of the loadURL() method. That code will move to the models, which subclass javax.swing.text.PlainDocument. First, change the actions to use these documents:
	classBlockingLoadAction extends AbstractAction {

		public void actionPerformed (ActionEvent e) {

			BlockingURLDocument bud =

				new BlockingURLDocument (urlField.getText());

			progressBar.setEnabled (true);

			progressBar.setValue (0);

			contentArea.setDocument (bud);

			progressBar.setValue (100);

		}

	}



	class NonBlockingLoadAction extends AbstractAction {

		public void actionPerformed (ActionEvent e) {

			NonBlockingURLDocument nbud =

				new NonBlockingURLDocument (urlField.getText());

			contentArea.setDocument (nbud);

			// makeProgressBarUpdaterFor (nbud);

		}

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Fire Events and Stay Bug Free
Inhaltsvorschau
Most developers think that writing an event-firing method is trivial. Most developers are wrong.
As you develop your own Swing components, it's likely that you'll eventually need to have them fire off events; this comes up as soon as you have a model that needs to update a view. If you've strongly typed everything by writing new classes for the model, view, event, and listener, then you'll have to write your own fire method.
Most developers assume this to be trivial. For example, to manage a list of FooListeners, they'll typically maintain a Vector or ArrayList and fire off the event with a block like this:
	Iterator it = listeners.iterator();

	while (it.hasNext())



		((FooListener).it.next()).handleEvent (fooEvent);
And there you have it. It's simple. It's clean. It's elegant. It's wrong.
To illustrate the problem and its various solutions, consider the listener class in Example 12-11.
Example 12-11. A simple event listener
	import java.util.*;

	public class TestEventListener extends Object 

		implements EventListener { 

		String id; 

		public TestEventListener (String id) {

			this.id = id;

		}

		public void handleEvent (EventObject o) {



			System.out.println (id + " called"); 

			if (id.equals ("C")) {   

				((TestEventSource) o.getSource()).removeListener (this); 

			}

		}

	}
This listener hangs on to a String and prints that string to standard out when handleEvent() is called. Also, if the string is a specific value—C in this case—it removes itself from the event source. If you can see why that's going to be a big deal, congratulations. If not, read on.
Next, define an abstract class to exercise various means of firing the event. This is shown in Example 12-12.
Example 12-12. Abstract class for testing event-firing techniques
	public abstract class TestEventSource {

	public abstract void addListener (TestEventListener l); 

	public abstract void removeListener (TestEventListener l);

	public abstract void fireEvent (java.util.EventObject o);    

	public void test() {

		addListener (new TestEventListener ("A"));

		addListener (new TestEventListener ("B"));

		addListener (new TestEventListener ("C"));

		addListener (new TestEventListener ("D"));

		addListener (new TestEventListener ("E"));

		fireEvent(new java.util.EventObject(this));



	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Debug Your GUI
Inhaltsvorschau
Standard out and err aren't just for log files anymore.
Debugging GUIs often means keeping one or two console windows open, so you can see the debugging messages you print to standard out (via System. out.println() and the like), as well as stack traces printed to standard err when exceptions are caught. In the field, you might want to log these to a file with something like java.util.logging, but at design time, or when investigating a bug, you want to see exactly when the exceptions happen, and running tail -f mylog.txt in multiple terminal windows may not be practical, especially if you're trying to get a customer on the phone to do it.
An alternative is for your own application to have debugging windows that collect everything printed to standard out and err, something that you or a user can bring up with a keypress or menu item.
Fortunately, taking control of the standard output and error streams is pretty easy. The trick is to repoint it into your own JTextAreas, as shown in Example 12-16.
Example 12-16. Redirecting System.out and System.err to Swing windows
	import java.awt.*;

	import java.awt.event.*;

	import javax.swing.*; 

	import java.io.*;



	JTextArea outArea, errArea;

	public StdErrOutWindows () {

		// out

		outArea = new JTextArea (20, 50);

		JScrollPane pain =



			new JScrollPane (outArea, 

				 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,

				 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

		JFrame outFrame = new JFrame ("out");

		outFrame.getContentPane().add (pain);

		outFrame.pack();

		outFrame.setVisible(true);

		// err

		errArea = new JTextArea (20, 50);

		pain =



			new JScrollPane (errArea, 

				 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,

				 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

			JFrame errFrame = new JFrame ("err");

			errFrame.getContentPane().add (pain);

			errFrame.pack();

			errFrame.setLocation (errFrame.getLocation().x + 20,

                             errFrame.getLocation().y + 20); 

			errFrame.setVisible (true); 

			// set up streams 

			System.setOut (new PrintStream (newJTextAreaOutputStream (outArea))); 

			System.setErr (new PrintStream (new JTextAreaOutputStream (errArea)));

		}

		public static void main (String[] args) {

			new StdErrOutWindows();

			// test

			System.out.println ("test to out");

			System.out.println ("another test to out");

			try {

			

				throw new Exception ("Test exception");

			} catch (Exception e) {

				e.printStackTrace();

			}

		}



		public class JTextAreaOutputStream extends OutputStream {

			JTextArea ta;

			public JTextAreaOutputStream (JTextArea t) {



				super();

				ta = t;

			}



		public void write (int i) {

			char[] chars = new char[1];

			chars[0] = (char) i;

			String s = new String (chars);

			ta.append(s);

		}

		public void write (char[] buf, int off, int len) {

			String s = new String (buf, off, len);

			ta.append(s);

		}



	}

}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Debug Components with a Custom Glass Pane
Inhaltsvorschau
Show component boundaries at runtime using a glass pane.
Sometimes when I'm building a really complicated Swing layout, I start to lose track of what I'm looking at. Which component is this? Does that panel extend all the way to the end of the frame? A way to visualize the layout would be a useful addition to the usual development tools. This hack explores using a custom glass pane to highlight each component and its classname.
A glass pane is a normally transparent Swing component that is drawn on top of all of the other components in a frame, as you saw when you put dialog-like "sheets" into the glass pane [Hack #44] . It is this ability that forms the center of the hack. The custom glass pane will traverse the entire tree of components in the frame, filling a translucent rectangle over each component. Deeper components will get painted multiple times resulting in a darker color. The glass pane will also watch the mouse cursor to determine which component the user is pointing at. That component's classname will then be drawn in the glass pane as well.
The first step is to create a sample screen for the glass pane to draw on top of, as seen in Example 12-17.
Example 12-17. A screen for the glass pane
	public class ComponentGlassPane extends JComponent {

	public static void main(String[] args) {

		JFrame frame = new JFrame("Component Boundary Glasspane");

		Container root = frame.getContentPane();

		root.setLayout(new BoxLayout(root,BoxLayout.Y_AXIS));

		final JButton activate =

			new JButton("Show component boundaries");

		root.add(activate);

		root.add(new JLabel("Juice Settings"));

		JPanel panel = new JPanel();

		panel.setLayout(new BoxLayout(panel,BoxLayout.X_AXIS));

		panel.add(new JLabel("Flavor"));

		panel.add(new JTextField(" "));

		root.add(panel);



		frame.pack();

		frame.show();



		final ComponentGlassPane glass =

			new ComponentGlassPane(frame);

		frame.setGlassPane(glass);

			

		activate.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent evt) {

				glass.setVisible(true);

			}

		});

		

	}
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Mirror an Application
Inhaltsvorschau
With creative use of the AWT event log, you can bind two instances of an application together over a socket, creating a mirroring effect.
One of the coolest—and severely underrated—features of Java is serialization. Because Java code runs entirely in a virtual machine, it's possible to send objects over the network to another program and have the objects still functional when they get there. One day while perusing the AWT documentation, I came across the AWTEventListener. I wondered what interesting thing you could do by capturing all of the events in a program. I could write them to disk, of course, but it would be even cooler to send them over the network to another copy of the program. That way the two programs could reuse each other's events and become mirrors! With a global event queue and a bit of serialization, this turns out to be quite easy.
To replicate events over the network, you need to do three things:
  1. Capture all AWT events. This can be done with an AWTEventListener.
  2. Send the event objects over the network.
  3. Pick the objects up on the other end of the network and repost them in the second program.
It sounds pretty simple, but there are always a few dragons hiding in the mist.
Every test program begins with a frame and a few components. This program is no different, with ApplicationMirrorTest (shown in Example 12-18) creating a frame, button, and text field in its constructor.
Example 12-18. Simple test program for mirroring
	public class ApplicationMirrorTest {

		public ApplicationMirrorTest() {

			JFrame frame = new JFrame();

			frame.getContentPane().setLayout(new FlowLayout());



			final JButton button = new JButton("action generator");

			frame.getContentPane().add(button);



			JTextField tf = new JTextField("text field");

			frame.getContentPane().add(tf);

			frame.pack();

			frame.show();

		}
There is only one program, but it must run in two modes: one for sending AWT events and one for receiving. If the program starts and it's the first instance running, then it should wait to receive events. If it's the second instance running, then it should send events instead. But how does the program know if it is the first or second instance? The only real way is to look for a shared resource. If the resource is already taken, then this must be the second instance. As with creating single-launch applications on Windows
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Add Velocity for Dynamic HTML
Inhaltsvorschau
Use the Velocity template engine to mimic server-side web technologies in your Swing application.
Servlets, JSPs, and other server-side technologies help separate an application's model from its view and allow you to build flexible and dynamic web applications. Of course, Swing applications have their own benefits, like fast user interaction without the need for web server communication. It would be cool to have the power of those server-side technologies right in your Swing application, but without the overhead of a local web server. You can actually mimic a lot of that functionality using a combination of Apache's Velocity template engine and a Swing HTML panel.
As an example, suppose you want to display the weather for the next three days as part of your application. You need a nice graphic weather display showing your users the current weather, as in Figure 12-13.
Figure 12-13: A graphical weather page
Velocity is an open source template engine, released under the Apache Jakarta umbrella. At its simplest, Velocity allows you to add intelligent replacement from a text file. At its most extreme, Velocity allows you to call Java methods and use the entire VTL (Velocity Template Language) to create intelligent templates using loops, conditionals, and variables. In other words, you get the power of an MVC infrastructure like JSP, but in a very lightweight local-client technology.
When using Velocity, you have two basic elements to deal with: the VelocityContext and the template. The VelocityContext holds objects that can be referenced from the template. The template is text with embedded VTL that controls the Velocity output.
In this simple example, ${name} and ${what} indicate replaceable values:
	${name} is a total ${what}
Here is a simple context:
	VelocityContext context = new VelocityContext();

	context.put("name", Jonathan);

	context.put("what", Rockstar);
When you run Velocity with this context and this template, it will print out:
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Get Large File Icons
Inhaltsvorschau
Using an undocumented Windows-only class, you can retrieve large, full-color file icons from the operating system.
The FileSystemView provides access only to file icons of a default size, which usually means 16 x 16 pixels. If you look at your desktop, however, you may see icons that are much bigger and with more detail and color. There is no standard way to get the larger icons, but on Windows you can use an undocumented (and unsupported) class to get access to them. Sun's JRE for Windows includes a hidden class called sun.awt.shell.ShellFolder that will let you retrieve larger (32 x 32) desktop file icons.
This class is only available in Sun's JRE for Windows, so it won't work with other vendors or on other platforms.
The class in Example 12-22 will take a filename and show its large icon in a window.
Example 12-22. Grabbing a large icon
	public class LargeIconTest {

		public static void main(String[] args) throws Exception {

			// Create a File instance of an existing file

			File file = new File(args[0]);



			// Get metadata and create an icon

			sun.awt.shell.ShellFolder sf =

				sun.awt.shell.ShellFolder.getShellFolder(file);

			Icon icon = new ImageIcon(sf.getIcon(true));

			System.out.println("type = " + sf.getFolderType());



			// show the icon

			JLabel label = new JLabel(icon);

			JFrame frame = new JFrame();

			frame.getContentPane().add(label);

			frame.pack();

			frame.show();

		}

	}
ShellFolder is a wrapper for metadata of the selected file. With this object, you can retrieve both the icon and a text description of the file's type. A normal MP3 icon would be only 16 x 16 pixels (Figure 12-15), but if you ran the MP3 file through LargeIconText it would print the string type = MPEG Layer 3 Audio and show a much nicer 32 x 32 pixel media icon (Figure 12-16).
Figure 12-15: Normal MP3 icon
Figure 12-16: Large MP3 icon
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Make Frames Resize Dynamically
Inhaltsvorschau
Make your application feel more responsive by turning on dynamic layout.
By default, JFrames don't resize dynamically. This means that the frame will not redraw itself as the user is resizing it. A repaint will only occur after the user lets go of the mouse and the window is refreshed. This behavior often results in extra gray areas and an unresponsive-feeling application. However, you can fix this with just one method call!
Just call one method on the default Toolkit:
Toolkit.getDefaultToolkit().setDynamicLayout(true);
You can query the dynamic layout property like this:
	if(Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {

		// do something

	}
or like this:
	if(Toolkit.getDefaultToolkit().isDynamicLayoutSet()) {

		// do something

	}
isDynamicLayoutSet() will tell you if dynamic layout was set programmatically, while isDynamicLayoutActive() will tell you if dynamic layout is supported. You need to use both methods because some platforms don't support dynamic layout, and others don't let you turn it off.
When you have dynamic layout turned on, the window will repaint each time the user moves the mouse. This will make the application feel responsive because there is always information on the screen being updated. If your frame contains an animated component, it will continue to play while the user resizes the window.
The disadvantage of dynamic layout is that a resize will generate a whole lot of repaint requests in a very short time. Even if the user moves the window just one pixel, it will trigger a repaint on the entire frame (unlike scrolling, which usually requires just repainting a strip at the bottom). If your painting code is slow (or you have a lot of components on screen), then the dynamic layout could actually make your program feel slower. Be sure you make your painting as fast as possible, perhaps skipping some of the more complicated effects during the resize. You may also want to use dynamic layout only in a program with a small streamlined window, such as a media player or utility app.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
	

Zurück zu Swing Hacks


Themen

Buchreihen

Special Interest

International Sites

O'Reilly China O'Reilly USA O'Reilly Japan O'Reilly Taiwan