FireFox Personas for the NetBeans Platform

While thinking about Henry's Nimbus challenges, I received a great tip from NetBeans Platform window system guru Stan Aubrecht:
There's a small API change in openide.windows module (see version 6.38 in core-main repo) which allows you to make most of the main window components non-opaque (menu bar, toolbars, slide bars, status bar etc), so you can provide your own JFrame window to be used as the main window and have a custom painted background in that frame. The custom painted image/gradient will be showing through the main window controls, just like when using personas in FireFox.

To get the point, go to http://www.getpersonas.com. "Personas are easy-to-use themes that let you personalize the look of your Firefox." The page shows you a bunch of different appearances for FireFox and, when you hover over any of them, the whole appearance of FireFox changes, as you can see here:

Wouldn't it be cool if you could do the same thing in a NetBeans Platform application? Well, now you can. By "now" I mean "after NetBeans 7.0 Beta 2", i.e., this has not made it into Beta 2, you'll need to get a very recent daily build or just rebuild your local Mercurial check out, which is what I did today.

Here is the result of the code I will show below. When I mouse over one of the buttons in my Personas window, everything changes to reflect the colors displayed in the button:

Nice, right?

Here's how:

  1. Get the right distribution of NetBeans IDE. I.e., if you are using NetBeans IDE 7.0 Beta 2 or anything before that, the code below will NOT work. You need something more recent, a daily build from yesterday, or rebuild your Mercural checkout.

  2. Create a "PersonaButton", which reads its colors from a preference that will be set when the mouse enters the button, with a border that changes depending on whether the mouse enters the button or exits it:
    public class PersonaButton extends JButton {
        
        private final Color color1;
        private final Color color2;
    
        public PersonaButton(final Color color1, final Color color2) {
            
            this.color1=color1;
            this.color2=color2;
            
            setPreferredSize(new Dimension(130,100));
            
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    NbPreferences.forModule(PersonaButton.class).put("color1", Integer.toString(color1.getRGB()));
                    NbPreferences.forModule(PersonaButton.class).put("color2", Integer.toString(color2.getRGB()));
                    setBorder(new LineBorder(Color.RED,2));
                }
                @Override
                public void mouseExited(MouseEvent e) {
                    setBorder(new LineBorder(Color.BLACK));
                }
            });
            
        }
    
        @Override
        public void paintComponent(Graphics g) {
            Paint p = new GradientPaint(0, 0, color1, getWidth(), 0, color2);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setPaint(p);
            g2d.fillRect(0, 0, getWidth(), getHeight());
        }
        
    }

  3. Create a "PersonasTopComponent", which is where the user will select their persona by entering the button with their mouse:
    ...
    ...
    ...
    private Color color1 = Color.RED;
    private Color color2 = Color.BLUE;
    
    public PersonasTopComponent() {
    
        initComponents();
        setName(NbBundle.getMessage(PersonasTopComponent.class, "CTL_PersonasTopComponent"));
        setToolTipText(NbBundle.getMessage(PersonasTopComponent.class, "HINT_PersonasTopComponent"));
    
        setLayout(new MigLayout());
    
        //Here the buttons are hardcoded, but they could come from the layer file,
        //so that all of this could be pluggable, potentially...
        add(new PersonaButton(Color.RED, Color.BLUE), "wrap");
        add(new PersonaButton(Color.YELLOW, Color.GREEN));
    
        final Preferences pref = NbPreferences.forModule(PersonaButton.class);
    
        pref.addPreferenceChangeListener(new PreferenceChangeListener() {
            @Override
            public void preferenceChange(PreferenceChangeEvent evt) {
                if (evt.getKey().equals("color1") || evt.getKey().equals("color2")) {
                    String colorString1 = pref.get("color1", "-65536");
                    String colorString2 = pref.get("color2", "-16776961");
                    color1 = new Color(Integer.parseInt(colorString1));
                    color2 = new Color(Integer.parseInt(colorString2));
                    repaint();
                }
            }
        });
    
    }
    
    @Override
    public void paintComponent(Graphics g) {
        Paint p = new GradientPaint(0, 0, color1, getWidth(), 0, color2);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setPaint(p);
        g2d.fillRect(0, 0, getWidth(), getHeight());
    }
    
    ...
    ...
    ...

  4. Create a new JFrame which will be your application's main window, instead of the default one. That's another new NetBeans Platform feature, as described here.

    The JFrame's colors are set based on the preferences that you set via the JButtons above.

    public class RootFrame extends javax.swing.JFrame {
    
        private Color color1 = Color.RED;
        private Color color2 = Color.BLUE;
    
        public RootFrame() {
            
            setRootPane(new CustomRootPane());
            initComponents();
            
            setName("NbMainWindow");
            
            final Preferences pref = NbPreferences.forModule(PersonaButton.class);
    
            pref.addPreferenceChangeListener(new PreferenceChangeListener() {
                @Override
                public void preferenceChange(PreferenceChangeEvent evt) {
                    if (evt.getKey().equals("color1") || evt.getKey().equals("color2")) {
                        String colorString1 = pref.get("color1", "-65536");
                        String colorString2 = pref.get("color2", "-16776961");
                        color1 = new Color(Integer.parseInt(colorString1));
                        color2 = new Color(Integer.parseInt(colorString2));
                        repaint();
                    }
                }
            });
            
        }
    
        static void init() {
            new RootFrame();
        }
    
        private class CustomRootPane extends JRootPane {
            @Override
            public void paintComponent(Graphics g) {
                Paint p = new GradientPaint(0, 0, color1, getWidth(), 0, color2);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setPaint(p);
                g2d.fillRect(0, 0, getWidth(), getHeight());
            }
        }
    
        ...
        ...
        ...

  5. Finally, create a new ModuleInstall class, where you specify that the background of the main window should be customized (i.e., this is the tip with which this blog entry started) and initialize the JFrame, because that needs to happen somewhere, i.e., the JFrame needs to be initialized somewhere, otherwise it will not be part of the application:
    public class Installer extends ModuleInstall {
    
        @Override
        public void restored() {
            try {
                UIManager.setLookAndFeel( new MetalLookAndFeel() );
            } catch( UnsupportedLookAndFeelException ex ) {
                Exceptions.printStackTrace( ex );
            }
            UIManager.put( "NbMainWindow.showCustomBackground", Boolean.TRUE);
            RootFrame.init();
        }
        
    }

Why is MetalLookAndFeel used? That's because Stan told me: "It looks best under MetalLookAndFeel, otherwise the menu bar is painted using regular look and feel." That's an interesting comment, maybe somehow related to Henry's problems. Trying to figure out still why the menu bar is special, maybe someone can advise on this point?

Finally, best of all, thanks to the NetBeans Platform's module system, the FireFox Personas for the NetBeans Platform are... pluggable, like anything else in the NetBeans Platform. I.e., I foresee a future where contributors provide new personas (i.e., "skins") that would be loaded into a centralized Personas window. When moused over, the contributed persona would automatically update the main window components, as well as anything else you have specified.

Comments:

[Trackback] Tweet Reading Geetjan’s post (FireFox Personas for the NetBeans Platform) today, I thought I could use the same technique in my application. Java 6 added System Tray Functionality and it would be nice if we could minimize our NetBeans Platform ap...

Posted by BluestormSoftware Blog on February 17, 2011 at 04:47 AM PST #

Hi Geertjan,

Great to learn this new functionality. Using this post I was able to solve my trayicon problem, see http://blog.bluestormsoftware.com/?p=40 .

Thanks !

Ralph

Posted by Ralph Benjamin Ruijs on February 17, 2011 at 04:53 AM PST #

Now what we need is a plugin that actually uses http://www.getpersonas.com/ :-)

Posted by Emilian Bold on February 17, 2011 at 05:52 PM PST #

Would you be able to style individual components, like just the tabs, or can you only do the entire window?

Ideally I would like to color the tabs differently based on a regular expression pattern match of the file location.

Posted by Jeremy Fowler on February 18, 2011 at 02:16 AM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
24
25
26
27
28
29
30
   
       
Today