februari 17, 2011

Put a wallpaper in the root frame of a Netbeans RCP application

A few days ago I had dinner with Geertjan Wielenga and Henry Arousell and Henry did show me his application he is working on. He did have a background image in one of his topcomponents and I really loved that idea. (After finishing this blog post I saw that this will actually be easier in Netbeans 7!  Really looking forward for that)

But I wanted to take it even further. My application is showing a complete empty window when it's opened until the user has opened up something. And the background is dark grey and booring. So I want to put a background image there showing the company name and the application name at the bottom. A discrete background image off course.. nothing big with lots of colors.

But how can I do that? There is nothing in the plattform allowing me to set the background image and the only way I know off to do it in a good way is to override the paintComponent(Graphics g) method which I can't override since that is the root window of the netbeans platform application.

I'm using OfficeLAF and I did know they did a few tricks in the Installer restored() method that I did modify a bit. What they are doing is that they get the mainWindow trough WindowManager.getDefault().getMainWindow(); Then they add a panel to the mainwindow that overrides the add method. So when the platform is adding it's own panel it's picked up in the add method and modified.

So with my modification this is how it looks:

public void restored() {
            bgimage = (Image) UIManager.get("Nb.Explorer.Folder.icon");
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JFrame main = (JFrame) WindowManager.getDefault().getMainWindow();
                    JPanel mainPanel = new JPanel(new BorderLayout()) {
                        @Override
                        public void add(Component comp, Object constraints) {
                            super.add(comp, constraints);
                            if (constraints == BorderLayout.CENTER) {
                                comp.setBackground(GRAY_76);
                                if (comp instanceof JPanel) {
                                    final JPanel panel = (JPanel) comp;
                                    panel.setBorder(BorderFactory.createEmptyBorder());
                                    panel.add(new BackgroundLabel(new ImageIcon(bgimage)));
                                }
                            }
                        }
                    };
                    main.setContentPane(mainPanel);
            } 

What I have added here that isn't in OfficeLAF is the new BackgroundLabel() creation. This will add a Label with an ImageIcon to the rootframe.

My first version of BackgroundLabel looked like this:

    private class BackgroundLabel extends JLabel {
        public BackgroundLabel(ImageIcon imageIcon) {
            super(imageIcon);
        }
   }

    @Override
        public void paintComponent(Graphics g) {
            g.drawImage(((ImageIcon) getIcon()).getImage(), 0, 0, getParent().getParent().getSize().width, getParent().getParent().getSize().height, this);
        }
}

This almost worked.. the biggest problem was that it's painting the image ABOVE my Topcomponents instead of in the background. So I needed to tweak this a bit. Only paint when there is no TopComponents open. So the final version looks like this:


    private class BackgroundLabel extends JLabel {
        private boolean showBgImage = true;
        public BackgroundLabel(ImageIcon imageIcon) {
            super(imageIcon);
            TopComponent.getRegistry().addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (TopComponent.getRegistry().getOpened().isEmpty()) {
                        showBgImage = true;
                        repaint();
                    } else {
                        showBgImage = false;
                    }
                }
            });
        }
   }

    @Override
        public void paintComponent(Graphics g) {
            if (showBgImage) {
                g.drawImage(((ImageIcon) getIcon()).getImage(), 0, 0, getParent().getParent().getSize().width, getParent().getParent().getSize().height, this);
            }
        }
}

What I added is a propertychangelistener to the TopComponent registry. Each time something happens with a topcomponent I'm checking how many topcomponents are open. If none are open I will set the showBgImage boolean to true and call for a repaint(). And if there are TopComponents showing again the showBgImage are set to false again. (no repaint is needed now).

The reason I listen to all propertychanges is because the propertychanges seems to not be trusted. At the same time I got closed events I also got open events so I listen to all instead.

My only problem left is that the image isn't resized properly if the application gets bigger than the initial size. Otherwise the resize works just great. Performance wise there is no problem. The paintComponent() method is only called on the first paint and then on resizes. If it works when not using OfficeLAF and/or Mac I have no answer to.

And below you can see a small screenshot with my temporary image which is just a (Image) UIManager.get("Nb.Explorer.Folder.icon") that I picked by random. Also see Part 2.