-1

I'm attempting to create a customized JProgressBar that utilizes the JLayer class so that it can be colored differently depending on the situation, a la this solution. The thing is that I want to wrap it up as a JComponent of some sort since it makes it more manageable. I would expose it as a customized JLayer, but that class is sealed, so nothing doing.

I've tried just making it a JComponent, but nothing is drawn on the screen (probably because I have no idea how to make a custom JComponent that contains other components inside of it?). I've tried a JFrame, which works, but the sizing is all wrong, likely because the progress bar is using the layout manager of the JFrame I made instead of the layout manager containing the JFrame. I've tried JProgressBar, which draws, but then I have no way of returning the JLayer and preserving the correct hierarchy without additional method calls after the constructor, which just doesn't seem elegant.

Here's my sample code, based heavily on the code from the question I linked to above:

public class Test {
    public JComponent makeUI() {
        final BoundedRangeModel model = new DefaultBoundedRangeModel();
        model.setValue(20);

        final JPanel p = new JPanel(new GridLayout(4, 1, 12, 12));
        p.setBorder(BorderFactory.createEmptyBorder(24,24,24,24));

        // This does not draw
        final ColorProgressBar pb4 = new ColorProgressBar(model);
        p.add(pb4);

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(p, BorderLayout.NORTH);
        return panel;
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch(Exception e) {
            e.printStackTrace();
        }
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new Test().makeUI());
        f.setSize(320, 240);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class ColorProgressBar extends JComponent {
    private static final long serialVersionUID = -1265489165072929352L;

    private BlockedColorLayerUI colorUI = new BlockedColorLayerUI();
    private JLayer<JProgressBar> layer;
    private JProgressBar bar;
    private PropertyChangeSupport supporter = new PropertyChangeSupport(new WeakReference<ColorProgressBar>(this));

    public ColorProgressBar(BoundedRangeModel model) {
        bar = new JProgressBar(model);
        layer = new JLayer<JProgressBar>(bar, colorUI);
        this.add(layer);
    }

    public Color getColor() {
        if (colorUI == null)
            return null;

        return colorUI.color;
    }

    public void setColor(Color color) {
        Color oldColor = colorUI.color;
        colorUI.color = color;
        supporter.firePropertyChange("color", oldColor, color);
    }

    @Override
    public void paintComponents(Graphics g) {
        layer.paintComponents(g);
    }

    class BlockedColorLayerUI extends LayerUI<JProgressBar> {
        public Color color = null;
        private BufferedImage bi;
        private int prevw = -1;
        private int prevh = -1;
        @Override public void paint(Graphics g, JComponent c) {
            if(color != null) {
                JLayer<?> jlayer = (JLayer<?>)c;
                JProgressBar progress = (JProgressBar)jlayer.getView();
                int w = progress.getSize().width;
                int h = progress.getSize().height;

                if(bi==null || w!=prevw || h!=prevh) {
                    bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
                }
                prevw = w;
                prevh = h;

                Graphics2D g2 = bi.createGraphics();
                super.paint(g2, c);
                g2.dispose();

                Image image = c.createImage(
                        new FilteredImageSource(bi.getSource(),
                                new ColorFilter(color)));
                g.drawImage(image, 0, 0, c);
            } else {
                super.paint(g, c);
            }
        }
    }
    class ColorFilter extends RGBImageFilter {
        Color color;

        public ColorFilter(Color color) {
            this.color = color;
        }

        @Override public int filterRGB(int x, int y, int argb) {
            int r = (int)((argb >> 16) & 0xff);
            int g = (int)((argb >>  8) & 0xff);
            int b = (int)((argb      ) & 0xff);
            return (argb & color.getRGB()) | (g<<16) | (r<<8) | (b);
        }
    }
}

Anyone know where I'm going wrong? Thanks!

EDIT: I've trimmed down the example slightly and modified it to better express my issue. Sorry for the confusion.

Community
  • 1
  • 1
Jesse
  • 253
  • 1
  • 3
  • 12
  • For better help sooner, post an [SSCCE](http://sscce.org/). – Andrew Thompson Nov 27 '13 at 23:28
  • I wasn't entirely sure how to make it shorter and make its behavior diagnosable. If you have any recommendations please feel free to share them; otherwise it's certainly self-contained and compilable. – Jesse Dec 02 '13 at 15:21
  • *"I wasn't entirely sure how to make it shorter"* There is more to SSCCE than 'short'. Go back and read it again. Also WTF does `UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());` have to do with the problem?!? You should have experimented with removing it. – Andrew Thompson Dec 02 '13 at 15:25
  • By removing that line the effect of the code is actually _harder to see_. The default Swing look-and-feel barely changes visually when the color overlay effects are applied. I suppose I could have used one of the packaged LaFs instead of the system one, but I honestly did not think anyone would find that single line of code to be so objectionable. – Jesse Dec 02 '13 at 16:44
  • *"By removing that line the effect of the code is actually harder to see."* Do you realize that the native PLAF is different on different OS' right? *"single line of code"* `` Don't think of it as a 'single' LOC, but **the *'first'* LOC.** – Andrew Thompson Dec 02 '13 at 23:18

1 Answers1

1

You may need to call super(model); and p.add(pb4.layer);

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.beans.*;
import java.lang.ref.WeakReference;
import javax.swing.*;
import javax.swing.plaf.LayerUI;

public class Test2 {
    public JComponent makeUI() {
        final BoundedRangeModel model = new DefaultBoundedRangeModel();
        final JPanel p = new JPanel(new GridLayout(4, 1, 12, 12));
        p.setBorder(BorderFactory.createEmptyBorder(24,24,24,24));

        final JProgressBar pb1 = new JProgressBar(model);
        pb1.setStringPainted(true);
        p.add(pb1);
        final JProgressBar pb2 = new JProgressBar(model);
        pb2.setStringPainted(true);
        p.add(pb2);

        p.add(new JProgressBar(model));
        final ColorProgressBar pb4 = new ColorProgressBar(model);
        p.add(pb4.layer);

        JPanel box = new JPanel();
        box.add(new JButton(new AbstractAction("+10") {
            private int i = 0;
            @Override public void actionPerformed(ActionEvent e) {
                model.setValue(i = (i>=100) ? 0 : i + 10);
            }
        }));
        //http://msdn.microsoft.com/en-us/library/windows/desktop/aa511486.aspx
        box.add(new JCheckBox(new AbstractAction(
                "<html>Turn the progress bar red<br />"+
                        " when there is a user recoverable condition<br />"+
                " that prevents making further progress.") {
            @Override public void actionPerformed(ActionEvent e) {
                boolean b = ((JCheckBox)e.getSource()).isSelected();
                pb2.setForeground(b? new Color(255,0,0,100) : new Color(255,255,0,100));
                if (b)
                    pb4.setColor(Color.RED);
                else
                    pb4.setColor(Color.YELLOW);
                p.repaint();
            }
        }));

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(p, BorderLayout.NORTH);
        panel.add(box, BorderLayout.SOUTH);
        return panel;
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch(Exception e) {
            e.printStackTrace();
        }
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new Test2().makeUI());
        f.setSize(320, 240);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class ColorProgressBar extends JProgressBar {
    private static final long serialVersionUID = -1265489165072929352L;

    private BlockedColorLayerUI colorUI = new BlockedColorLayerUI();
    public JLayer<JProgressBar> layer;
    private PropertyChangeSupport supporter = new PropertyChangeSupport(new WeakReference<ColorProgressBar>(this));

    public ColorProgressBar(BoundedRangeModel model) {
        super(model);
        layer = new JLayer<JProgressBar>(this, colorUI);
    }

    public Color getColor() {
        if (colorUI == null)
            return null;

        return colorUI.color;
    }

    public void setColor(Color color) {
        Color oldColor = colorUI.color;
        colorUI.color = color;
        supporter.firePropertyChange("color", oldColor, color);
    }

//     @Override
//     public void paintComponents(Graphics g) {
//         layer.paintComponents(g);
//     }

    class BlockedColorLayerUI extends LayerUI<JProgressBar> {
        public Color color = null;
        private BufferedImage bi;
        private int prevw = -1;
        private int prevh = -1;
        @Override public void paint(Graphics g, JComponent c) {
            if(color != null) {
                JLayer<?> jlayer = (JLayer<?>)c;
                JProgressBar progress = (JProgressBar)jlayer.getView();
                int w = progress.getSize().width;
                int h = progress.getSize().height;

                if(bi==null || w!=prevw || h!=prevh) {
                    bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
                }
                prevw = w;
                prevh = h;

                Graphics2D g2 = bi.createGraphics();
                super.paint(g2, c);
                g2.dispose();

                Image image = c.createImage(
                        new FilteredImageSource(bi.getSource(),
                                new ColorFilter(color)));
                g.drawImage(image, 0, 0, c);
            } else {
                super.paint(g, c);
            }
        }
    }
    class ColorFilter extends RGBImageFilter {
        Color color;

        public ColorFilter(Color color) {
            this.color = color;
        }

        @Override public int filterRGB(int x, int y, int argb) {
            int r = (int)((argb >> 16) & 0xff);
            int g = (int)((argb >>  8) & 0xff);
            int b = (int)((argb      ) & 0xff);
            return (argb & color.getRGB()) | (g<<16) | (r<<8) | (b);
        }
    }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
  • Is there a way to write it so that I don't have to refer to the .layer field? I feel like even if I documented the method, there's a pretty good chance that someone else in the organization may hook it up improperly since Swing is more or less designed so you add the JComponents themselves and not a field within them. – Jesse Dec 02 '13 at 15:33