|
|
||
Scott Violet's BlogJ2SE ArchivesJPasswordField with an empty echo character: the fixPosted by zixle on December 19, 2005 at 03:38 PM | Permalink | Comments (5)In my last blog I explained how to customize the visual feedback provided by JPasswordField. Unfortunately the first option I detailed, specifying a character that takes no space, had a bug in it. This blog discusses the bug and how to fix it. If you want to cut to the chase and run the demo it can be found here. Specifying you want JPasswordField to render a character with no space is as easy as:
passwordField.setEchoCharacter('\u200b');
As my last demo
illustrated, this will produce an ArithmeticException when you click on the password field (second one in the demo). As you
could probably guess the exception occurs because PasswordView is
dividing the width of the character without checking it,
resulting in the exception. The fix is to create a subclass of
PasswordView that overrides viewToModel without dividing by the
character width. Here's the code:
public int viewToModel(float fx, float fy, Shape boundsAsShape, Position.Bias[] bias) {
boundsAsShape = adjustAllocation(boundsAsShape);
Rectangle bounds = (boundsAsShape instanceof Rectangle) ?
(Rectangle)boundsAsShape : boundsAsShape.getBounds();
if (fx < bounds.x) {
return getStartOffset();
}
return getEndOffset();
}
With a character that takes up no space there's no way for the user to click
anywhere but the beginning or end of the text. As such,
viewToModel will return one of these.
To install this View you need to do something similar to that of the last blog (refer to it for more details). Create a custom JPasswordField, with a custom UI that returns the new View:
new JPasswordField() {
public void updateUI() {
setUI(new BasicPasswordFieldUI() {
public View create(Element elem) {
return new EmptySpacePasswordView(elem);
}
});
}
};
And here's the demo.
That's enough for passwordfield and text, next time around something
new.
WeakReferences and ActionsPosted by zixle on November 09, 2005 at 01:25 AM | Permalink | Comments (10)In my last blog I delved into why one might use Actions. In this article I'm going to cover how Swing's components support Actions. Eventually I'll wind up in why you should know about WeakReferences. Actions can be dynamically changed. This is a key feature of Action. The most common use case is changing the Action's enabled property based on some criteria. For example, the back button in a browser is enabled based on whether or not you can navigate to the previous page. To get this to work requires toggling the enabled property. Actions communicate changes through the ProperyChangeListener interface. That is, any time you change a property on an Action the Action will notify all its PropertyChangeListeners on the change. When you set an Action on a Swing component the Swing component installs a PropertyChangeListener on the Action. The PropertyChangeLisener allows for the Swing component to update it's state to match that of the Action. An implementation of this might look like:
public class AbstractButton extends JComponent {
public void setAction(Action action) {
action.addPropertyChangeListener(new ActionPropertyChangeListener());
}
private class ActionPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
if ("enabled".equals(e.getPropertyName())) {
setEnabled(action.isEnabled());
}
...
}
}
}
This code looks fine, but it turns out this code is wrong!
One of Java's many claims to fame is garbage collection. With garbage collection developers no longer need to explicitly allocate or deallocate memory, it's taken care of for you. None-the-less developers still need to be cognizant of the lifetime of objects. If we had implemented support for Actions as highlighted in the previous code the lifetime of the component would be tied to that of the Action. This results from the Action keeping a reference to the PropertyChangeListener, which in turn references the component, which references the Action... In other words we would have a cycle. When this happens the lifetime of each object is the same. As such it's not possible for one to be freed without the other being freed. Here's a picture showing what's going on:
Cycles are only problematic when you want the life cycle of one of the objects in the cycle to be longer than that of the others. For example, the components in a containment hierarchy form a cycle, but this is not problematic because you wouldn't want a button in a visible window to go away suddenly if you didn't explicitly reference it. With Actions this isn't the case, in particular the Action should be able to outlive the lifetime of the component. How do we do this while keeping the cycle? WeakReferences are a way of indirectly referencing an object. The advantage of referencing an Object via a WeakReference is that the reference isn't considered by the gc. In particular if the only references to an object are via WeakReferences then that object is fair game for the gc to free. This is exactly what we want for Actions. Rather than having the PropertyChangeListener directly reference the Component it'll indirectly reference the Component through a WeakReference. This way the life time of the component is not tied to that of the Action. For example, if you dispose a window containing a component with an Action the component can now be gc'd. Here's an image illustrating what is going on now. Notice the extra reference. There is still a cycle, but because of the magic of WeakRefernces the Component can now be gc'd.
And here's the code:
public class AbstractButton extends JComponent {
public void setAction(Action action) {
action.addPropertyChangeListener(new ActionPropertyChangeListener(this, action));
}
private static class ActionPropertyChangeListener implements PropertyChangeListener {
private WeakReference<AbstractButton> buttonRef;
private Action action;
ActionPropertyChangeListener(AbstractButton button, Action action) {
buttonRef = new WeakReference<AbstractButton>(button);
this.action = action;
}
public void propertyChange(PropertyChangeEvent e) {
AbstractButton button = buttonRef.get();
if (button == null) {
// The button has gcd, remove listener
action.removePropertyChangeListener(this);
} else {
if ("enabled".equals(e.getPropertyName())) {
button.setEnabled(action.isEnabled());
}
...
}
}
}
}
There's a couple of interesting things to mention here. Because we're
using WeakReferences it's possible for the referenced object, the
button in this case, to be gc'd. If this happens, the get method of
WeakReferences will return null. If get returns null
ActionPropertyChangeListener removes itself from the set of
PropertyChangeListeners on the Action. The second key thing is that
this inner class must be static. If the class weren't static an
implicit reference would be kept to the button so that the lifetime of
the button and Action would again be the same, defeating all the work
we've done.
For brevitys sake I've isolated this code all in one class and taken a few shortcuts. Internally in Swing we've factored this out into a common class of the same name. If you're curious, download the latest mustang source and poke around.
WeakReferences aren't for everything, but they are necessary in cases
like this. In particular if you have a cycle and want some objects to
live shorten than others in that cycle, WeakReferences are for you!
Customizing Ocean GradientsPosted by zixle on September 26, 2005 at 08:37 AM | Permalink | Comments (9)In my last blog I went over how we were able to make Ocean perform as well, and in some cases better than Steel. In this blog I'm going to cover how you can change the gradients that Ocean draws. For a quick refresher the gradients in Ocean consist of 4 distinct regions as shown here:
As with other visual properties Ocean determines how to draw this gradient from the defaults table. JButton uses the defaults key "Button.gradient" to get the value that dictates what to draw. The value is a java.util.List consisting of five values. The first value is the percentage (as a Number) of the width (or height) for the first gradient. The second number gives the percentage (again as a Number) of the the width (or height) for the solid region. The remaining three values are the colors: start color for gradient 1, end color for gradient 1 and end color for gradient 2. The algorithm for drawing a vertical gradient is roughly like: List values = ...; int gradient1Height = ((Number)values.get(0)).floatValue() * h; int solidHeight = ((Number)values.get(1)).floatValue() * h; Color c1 = (Color)values.get(2); Color c2 = (Color)values.get(3); Color c2 = (Color)values.get(4); // First gradient // Fictional method that draws a gradient of the specified size. drawGradient(0, 0, width, gradient1Height, c1, c2); // Solid rectangle g.setColor(c2); fillRect(0, gradient1Height, width, solidHeight); // First gradient inverted drawGradient(0, gradient1Height + solidHeight, width, gradient1Height, c2, c1); // Last gradient drawGradient(0, gradient1Height * 2 + solidHeight, width, h - (gradient1Height * 2 + solidHeight), c1, c3); And the code to initialize the value is roughly like:
java.util.List buttonGradient = Arrays.asList(
new Object[] {new Float(.3f), new Float(0f),
new ColorUIResource(0xDDE8F3), getWhite(), getSecondary2() });
Because the colors are not hard coded, you can change the way Ocean looks by changing the value in the defaults table. For example, you can go for the garish:
To the minimalist:
Or a slightly brighter Ocean look:
You can even get a look similar to that used by Incor's Alloy Look and Feel:
Or an XP like gradient:
Ocean Gradient CustomizerIf you have 1.5 you can try your hand at a good combination using the Ocean Gradient Customizer. Post your favorite combinations and I'll do a future blog on the various combinations people have come up with. Ocean, Gradients and Image Caching - oh myPosted by zixle on September 06, 2005 at 06:00 AM | Permalink | Comments (3)One of the biggest features we did in 1.5 was revamping the java look and feel by providing a new theme (OceanTheme). Love it or hate it, Ocean requires a bit more graphics support than the line primitives of yore. In order to make Ocean the default we needed to make sure performance was in line with the old theme (Steel). All of our internal tests show that we succeeded, Ocean performs as well and better in some cases, than Steel. How did we do this? Before I get into the solution let's first look at how the gradients that Ocean makes such extensive use of are painted. The gradient is not a single gradient, but is made up of four distinct areas: 1. Gradient from color1 - color2 Here's a monsterously large button showing the distinct regions:
We placed all this functionality into the package private abstract class javax.swing.plaf.metal.CachedPainter (in 1.6 this has been moved to sun.swing), take a look if you're curious. This class hides all the image management, and SoftReference trickyness from subclasses, a subclass only need override a single method to do the actual painting. CachedPainter makes use of varags to allow for seamless passing of arguments to the method responsible for the actual painting. It results in lossing some type safety, but it works. One last thing, for compatability Ocean still uses bold fonts, but this can easily be turned off with the system property swing.boldMetal=false. Oh ya! Special thank to Romain for helping with the image. Fun with RobotPosted by zixle on May 02, 2005 at 06:18 AM | Permalink | Comments (9)In my last blog I mentioned we're nearly done with the baseline API for mustang. In verifying the baseline API work I initially took the approach of grabbing screenshots, dumping them in Paint and zooming in. Painful to say the least! There must be a better way. Enter RobotIn addition to being able to inject mouse and keyboard events into the system java.awt.Robot gives you the ability render part of the desktop into an image. Exactly what I want. The following code grabs a screen shot returning a BufferedImage:Robot robot = new Robot(); BufferedImage image = robot.createScreenCapture(new Rectangle(x, y, width, height));So Robot gives me an image of part of the desktop, but I don't really want to continually do a keyboard gesture to create the image. I want the image to update as I move the mouse around. Enter AWTEventListenerAWTEventListener can be used to listen to all events. Think of it as a way to globally install a *Listener. Using AWTEventListener I can determine when the mouse moves over any component. Here's the code for that:
Toolkit toolkit = Toolkit.getDefaultToolkit();
AWTEventListener listener = new AWTEventListener() {
public void eventDispatched(AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_MOVED) {
mouseMoved(event);
}
}
};
toolkit.addAWTEventListener(new , AWTEvent.MOUSE_MOTION_EVENT_MASK);
Combining AWTEventListener with Robot I have a way to zoom in on part
of my window as the cursor is being moved. Excellent! The following
screen shot shows this in effect. The bottom area is the zoomed in
component.
Here's the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MagnifyComponent extends JComponent {
private int factor;
private Image image;
private int w;
private int h;
private Robot robot;
MagnifyComponent() {
try {
robot = new Robot();
} catch (AWTException e) {
}
this.factor = 6;
Toolkit.getDefaultToolkit().addAWTEventListener(
new EventHandler(), AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
public void reshape(int x, int y, int w, int h) {
super.reshape(x, y, w, h);
updateImageSize();
}
private void updateImageSize() {
int w = getWidth();
int h = getHeight();
image = null;
this.w = w / factor / 2;
this.h = h / factor / 2;
}
public void paintComponent(Graphics g) {
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
if (image != null) {
g.drawImage(image, 0, 0, w * factor * 2, h * factor * 2,
0, 0, w + w, h + h, null);
}
}
private void updateImage(int x, int y) {
if (w > 0 && h > 0) {
image = robot.createScreenCapture(new Rectangle(
x - w, y - h, w + w, h + h));
}
repaint();
}
private class EventHandler implements AWTEventListener {
public void eventDispatched(AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_MOVED) {
MouseEvent me = (MouseEvent)event;
Component source = me.getComponent();
int x = me.getX();
int y = me.getY();
Point location = source.getLocationOnScreen();
x += location.x;
y += location.y;
updateImage(x, y);
}
}
}
}
I'm aware of at least one accessibility helper in Windows that zooms in on the desktop as you move the mouse, but I've found it a bit intrusive. In so far as the app always takes up a big chunk of the desktop, where as I really only wanted the zooming feature for this window. Enjoy!
Warning: I'm sure there are performance issues with this code on slower/older machines. As this isn't for production and was only needed in testing, I did no testing other than on my machine. If you're to incorporate something like this in a real app do performance analysis first!
Baseline LayoutPosted by zixle on April 25, 2005 at 06:31 AM | Permalink | Comments (31)In many of the past Swing team brainstorming sessions we've lamented the layout process. Layout managers are a necessary evil of cross platform layout and resolution independence in that they allow components to adjust in size and position based on external criteria. Unfortunately the existing layout managers are not the easiest thing to use and force the developer to write code that can be difficult to read for a process that should be visual. A friend speculated that Perl is best suited as a write once language. That is, Perl code tends to be rather cryptic to read and hard to maintain so that you end up writing the code once and praying you never come back to it. Much of the layout code I've seen can be lumped into that category. You write it once and if you ever come back to it you have to pull out your hair trying to figure out the intricacies of the containment hierarchy and layout managers being used. Ugh! All this for a process that is inherently visual and should be done in a tool. This process has improved some what in recent years. In particular forms layout helps for much cleaner code, and some of the recent GUI builders have been getting better. We've been working closely with the Netbeans folks as well and will have an announcement soon. It's going to be fantastic. Stay tuned! One area that we have been actively working on in Mustang is the ability to get the baseline for various components. This doesn't help developers laying out components, rather it's meant for those writing layout managers. Aligning components along their baseline makes for more polished apps. For example, if you have a label next to a button you do not want them top aligned or bottom aligned, rather you want them aligned on the baseline of the text. Aligning components along their baseline also makes for great demos. Look at Apple's Interface Builder, or recent versions of Visual Studio for examples of GUI builders using baseline information to help developers create visually pleasing apps. Accomplishing baseline layout with Swing has been tough. This is for a number of reasons, but primarily because Swing doesn't offer API to determine where the baseline for a particular component is! Sure, we do have the getAlignmentY method, but that really doesn't do all you need and it was never wired up. I'm happy to report that we're nearly done with the baseline API for Swing. If you want a sneak peek at what the API is looking like head over to the javadesktop, in particular the thread on baseline. Give us your two cents! Swing Painting Improvements: No More Gray Rect!Posted by zixle on April 18, 2005 at 06:37 AM | Permalink | Comments (62)In scoping out various performance related projects for mustang we wanted to tackle one of Swing's long standing problem areas that has contributed to bad perceived performance. That is, when a Swing based app is exposed after being hidden by another application there is a noticeable delay between when the background of the window is erased and when the actual contents are painted. We've come to call this the 'gray rect' problem. I'm happy to report that with the promotion of mustang build 32 this bug (4967886) has been fixed! Download it now and give us feedback! The fix involved adding true double buffering support to Swing. That is, each window will now have an off-screen image that is kept in sync with the on-screen image. When a window is exposed we copy directly from the off-screen image, on the toolkit thread, to the screen. That's it! Your application will no longer see paint events in this scenario! I've been running various Netbeans 4.1 builds for a while now with this fix and while subtle, it makes a big difference! I no longer see the Netbeans window flash when alt-tab'ing back and forth between Netbeans and other apps. Painting of exposed windows is nearly instantaneous. An added bonus of this fix is that if your application is blocking the event dispatch thread, say you're contacting a server and a user hides your window and exposes it the app will still paint. Nice! This fix is the most substantial change to Swing's painting architecture since pre 1.0. I'll have a follow on article that discusses the changes in more details. Until then, download the beta, kick the tires and give us feedback! We're not only interested in feedback on this change, but other areas that effect runtime performance you feel we should be focusing on. For the hackers that want to see the Swing side of the code changes grab the latest source from the JDK source snapshot and look in javax.swing.BufferStrategyPaintManager, javax.swing.SwingPaintEventDispatcher and javax.swing.RepaintManager. The fine print. For compatibility sake there are a handful of situations that will trigger turning off true double buffering. Most notably if you install your own RepaintManager. Additionally, true double buffering is currently only enabled on Windows. The code works on all platforms, but we have as yet to enable it everywhere. Yes, we're evaluating that now! | ||
|
|