The Source for Java Technology Collaboration
User: Password:



Chris Adamson

Chris Adamson's Blog

I Must Be Dim

Posted by invalidname on April 13, 2007 at 07:19 PM | Comments (6)

So, a day after I finished my AB5k desklet, Josh goes and totally breaks it with a wildly ambitious new version of the desklet container, which "virtualizes" all the components. In other words, all the components really live off-screen in memory and blit their pixels into buffers that Josh can play with, render on a 3D surface, whatever.

Among the breakage is an animated dimming effect I was using. It doesn't seem like desklets should be able to spawn their own windows or dialogs, so for open and save dialogs, and some HTML "about" text, what I decided to do was to use a glass-pane hack: I'd "dim" the glasspane to darken the desklet, then insert the JFileChooser or JScrollPane, with large insets, in the center of the glass-pane. By "darken", I mean that I'd set its background to a black color with a non-trivial alpha value (ie, a translucent black), and fill the pane. I posted a screenshot of it to the Desklet Contributors group back when it worked.

But like I said, the virtualized component scheme broke this, because it depended on my component climbing the heirarchy to find a RootPaneContainer, which would provide access to the glass-pane. In the virtualized world, the components don't live in real frames or root-pane containers anymore, so this doesn't work.

OK, plan B: do my own JLayeredPane. The contents of a RootPaneContainer are just a big JLayeredPane so this should work, right?

Well, um, no. Not like you'd think it would. It seems to break in really interesting ways. Specifically, if the "dim box" -- the component that fills the layer above the background -- is less than the size of the JLayeredPane in both dimensions, it works as before. On the other hand, if it equals the container's size exactly, or equals or exceeds in both dimensions, then the dim box loses its translucence.

Weird? Let's play. Here's an applet version. You turn on the dim layer with the checkbox, then set its size with the text areas. You'll need to turn it on and set non-zero dimensions in order to see anything. You can also download dimlayertest.jar and run it as an application with java -classpath dimlayertest.jar DimLayerTest.

If you can see this, your browser doesn't support applets. Really?

So, here are a few screenshots, in case you'd rather not play with the applet. First, a 200x200 dim box, just to show it works:

dimlayer-test-200-200.png

Next, I max out the horizontal dimension, but stay one pixel back from maxing out the vertical.

dimlayer-test-344-373.png

And finally, a dim box that is exactly the dimensions of the container:

dimlayer-test-344-374.png

Oops! So much for that approach. Actually, I'm probably screwed anyways, for two reasons. In the desklet, the "too big" dimensions seem to be a good 5 or 6 pixels in, which leaves a weird un-dimmed area. Second, it seems the translucence may not be cross-platform. Here's what happened when I tried it in Windows XP on Virtual PC (my Boot Camp seems hosed at the moment):

dimlayer-test-windows.png

Yuck! Ptui! So much for alpha channels, Duke!

Anyways, time to punt. It was a neat idea, but it doesn't work, I've spent too much time on it, and Josh and Cooper now have a simple "get me a dialog" approach that will suffice for the file chooser and the about HTML. Enough of this wild layer chase...

Oh, for the record, here's source: it's only 150 lines, so it shouldn't be too painful to just in-line:


import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.*;
import java.applet.*;

public class DimLayerTest extends Applet {

    JLayeredPane layeredPane;
    JComponent dimBox;
    JLabel iconLabel;
    JTextField widthField, heightField;
    JCheckBox showDimBoxCheckBox;

    public static void main (String[] arrrImAPirate) {
        JFrame f = new JFrame ("Dim Layer Test");
        f.getContentPane().add (new DimLayerTest());
        f.pack();
        f.setVisible(true);
    }

    public DimLayerTest () {
        doMyLayout();
    }

    private void doMyLayout() {
        setLayout (new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        
        // controls for the dim pane
        gbc.gridx=0;
        gbc.gridy=0;
        gbc.gridwidth=1;
        gbc.gridheight=1;
        gbc.weightx=1;
        gbc.weighty=1;
        gbc.anchor=GridBagConstraints.NORTH;
        gbc.fill=GridBagConstraints.BOTH;

        add (new JLabel ("Dim width:"), gbc);
        widthField = new JTextField ("0", 5);
        gbc.gridx=1;
        add (widthField, gbc);
        gbc.gridx=2;
        add (new JLabel ("Dim height:"), gbc);
        heightField = new JTextField ("0", 5 );
        gbc.gridx=3;
        add (heightField, gbc);

        gbc.gridx=1;
        gbc.gridy=1;
        gbc.gridwidth=4;
        showDimBoxCheckBox = new JCheckBox ("Show Dim Layer", false);
        add (showDimBoxCheckBox, gbc);

        // our layers
        java.net.URL imageURL =
            getClass().getClassLoader().getResource
            ("resources/Duke_library_javax_swing.gif");
        iconLabel = new JLabel (new ImageIcon (imageURL));
        dimBox = new JPanel();
        dimBox.setBackground (new Color (0, 0, 0, 128));
        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize (iconLabel.getPreferredSize());
        layeredPane.add (iconLabel, JLayeredPane.DEFAULT_LAYER);
        iconLabel.setBounds (0, 0,
                             iconLabel.getPreferredSize().width,
                             iconLabel.getPreferredSize().height);
        layeredPane.add (dimBox, JLayeredPane.MODAL_LAYER);
        dimBox.setOpaque (true);

        dimBox.setBounds (0, 0, 0, 0);
        dimBox.setVisible (false);

        gbc.gridx=0;
        gbc.gridy=10;
        gbc.gridwidth=4;
        gbc.gridheight=1;
        gbc.weightx=1;
        gbc.weighty=1;
        gbc.anchor=GridBagConstraints.NORTH;
        gbc.fill=GridBagConstraints.BOTH;

        add (layeredPane, gbc);

        // bottom row shows image size... also a row that stretches
        // to handle extra applet space
        gbc.gridy=20;
        gbc.anchor=GridBagConstraints.NORTHWEST;
        add (new JLabel ("Image is " + 
                         iconLabel.getSize().width + " x " +
                         iconLabel.getSize().height), 
             gbc);
        gbc.gridy=100;
        gbc.weighty=1000000;
        add (Box.createVerticalGlue(), gbc);


        resetDimBox();

        // wire up listeners to reset dim box any time text is typed
        // into fields or button is clicked
        showDimBoxCheckBox.addActionListener (new ActionListener() {
                public void actionPerformed (ActionEvent e) {
                    resetDimBox();
                }
            });

        DocumentListener textFieldListener = new DocumentListener() {
                public void insertUpdate (DocumentEvent e) {
                    resetDimBox();
                }
                public void removeUpdate (DocumentEvent e) {
                    resetDimBox();
                }
                public void changedUpdate (DocumentEvent e) {
                    resetDimBox();
                }
            };
        widthField.getDocument().addDocumentListener (textFieldListener);
        heightField.getDocument().addDocumentListener (textFieldListener);
    }

    
    
    // get settings from control widgets and reset bounds of dimBox
    private void resetDimBox() {
        int oldDimWidth = dimBox.getBounds().width;
        int oldDimHeight = dimBox.getBounds().height;
        int newDimWidth = -1;
        int newDimHeight = -1;
        try {
            newDimWidth = Integer.parseInt(widthField.getText());
        } catch (Exception e) {} // ignore garbage
        try {
            newDimHeight = Integer.parseInt(heightField.getText());
        } catch (Exception e) {} // ignore garbage
        boolean newDimVisible = showDimBoxCheckBox.isSelected();

        dimBox.setVisible (newDimVisible);
        dimBox.setBounds (0, 0,
                          ((newDimWidth > 0) ? newDimWidth : oldDimWidth),
                          ((newDimHeight > 0) ? newDimHeight : oldDimHeight));

        repaint();
    }


}

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • You are breaking two major rules of Swing here. First, your dimBox is set to be opaque (check out your line that reads dimBox.setOpaque(true), which is unnecessary by the way since a JPanel is opaque by default.) Swing does not like that when the component is translucent. Change it to setOpaque(false). Then you are mixing heavyweight and lightweight components. Ok, almost :-) Why extend from java.awt.Applet when all you use are Swing components? Extend JApplet instead.

    Anyway, set your dimBox to be non-opaque and all of these bugs should be gone. So it's not JLayeredPane's fault, it's your fault :-))) (And probably Apple VM's which lead you to believe that your code was right.)

    Posted by: gfx on April 14, 2007 at 02:21 AM

  • By the way, the reason why it works when the dimBox is not of the same size as the underlying container must be the following: the dimBox is said to be opaque so Swing doesn't try to repaint what's underneath first; but because the underlying container is larger than the dimBox, Swing knows it's directly exposed and still paints it. When the dimBox covers the whole container, not a single pixel from the underlying container is visible and Swing does not bother with painting it.

    Posted by: gfx on April 14, 2007 at 02:24 AM

  • Romain: Thanks. Sound reasoning about the Swing painting strategy, just took a little more to make it work. When I changed to JApplet (duh, that was just a typo) and setOpaque(false), I saw nothing because this approach sets the background color, and a non-opaque background doesn't need to be painted. Ergo, no dim layer at all. So I set the dimBox's foreground color, and overrode its paintComponent() to fill its current size with the foreground color. And hey, that works. Hilarious.

    Posted by: invalidname on April 14, 2007 at 03:14 AM

  • "a non-opaque background doesn't need to be painted"

    Actually, that's not true. When a component is non-opaque, it just means that the component will not fill all of its allocated pixel with a solid color. The fact that JPanel doesn't paint its background with setOpaque(false) is an ill side-effect. JPanel shouldn't do that. For instance, JButton got it right. The background disappears only when you call setContentAreaFilled(false).

    Posted by: gfx on April 14, 2007 at 02:07 PM

  • When I implemented something like this, found some inspiration from the flamingo project :

    https://flamingo.dev.java.net/

    I looked at how the "Progress Glass Pane" worked and applied that to a JLayeredPane. If I remember correctly I had to set the panel in the upper layer opaque(false), then override paintComponent() and paint a gray box the entire size of the of the panel. In the end I used a gradient paint for the "dimming" layer, which looked nice.

    Posted by: aberrant on April 17, 2007 at 04:39 AM

  • Opps, I looked it up I use this for my paint on my "dim layer". I used the gradient someplace else.


    public void paint(Graphics g)
    {
    Graphics2D g2d = ( (Graphics2D) g);
    Composite compOld = g2d.getComposite();
    Color clrOld = g2d.getColor();

    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f));
    g2d.setColor(clrOverlay);

    g2d.fillRect(0, 0, getWidth(), getHeight());

    g2d.setComposite(compOld);
    g2d.setColor(clrOld);
    super.paint(g);
    }

    Posted by: aberrant on April 17, 2007 at 04:54 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds