How do I set hard limit on a JComponent when setMaximumSize() and setPrefferedSize() don't work?

I'm trying to make an image processing frame similar to one found in something like Photoshop or Paint Shop Pro and I'm running into problems.

Right now I have a JFrame window with a JDesktopPane. When I click a button, a JInternalFrame is made with the following components in it:

imageLabel = new JLabel("picture.png");
scrollPane.setViewPort(imageLabel);
internalFrame.add(scrollPane);  // I also tried with a BorderLayout()
desktopPane.add(internalFrame);

My problem is this: I don't want the JLabel or the JScrollPane to stretch to the size of the JInternalFrame if the JLabel is smaller than the JInternalFrame.

I've tried padding the space around the JLabel with "empty" JLabels. I've tried switching layout styles of the JScrollPane. I've tried setting the preferred and maximum sizes of the JLabel and the JScrollPane to that of picture.png. None of it works for what I need. I don't want the blank "space" around the JLabel to be a part of the JScrollPane or the JLabel so that I can use various MouseEvents to trigger on the picture itself rather than the space left by the "stretched" JLabel or JScrollPane whenever I resize the JInternalFrame.

Thanks in advance.

Edit1: Here is a bit of code that highlights the problem.

import java.awt.*;
import java.awt.event.*;

class fooFrame extends JFrame implements MouseListener
{
private static fooFrame frame;
JLabel fooLabel;

public fooFrame()
{ 
    JDesktopPane background = new JDesktopPane();

    JInternalFrame internalFrame = new JInternalFrame("Internal Frame", true, true, true, true);
    internalFrame.setSize(500, 500);
    internalFrame.setLocation(20, 20);
    internalFrame.setVisible(true);

    Image image = Toolkit.getDefaultToolkit().getImage("test.gif");

    fooLabel = new JLabel(new ImageIcon("test.gif"));
    fooLabel.setPreferredSize(new Dimension(image.getWidth(null), image.getHeight(null)));

    JScrollPane fooScrollPane = new JScrollPane(fooLabel, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    fooScrollPane.setPreferredSize(new Dimension(fooLabel.getWidth(), fooLabel.getHeight()));

    fooScrollPane.setViewportView(fooLabel);    // add JLabel to JScrollPane
    internalFrame.add(fooScrollPane);           // add JScrollPane to JInternalFrame
    background.add(internalFrame);              // add JInternalFrame to JDesktopPanel
    this.setContentPane(background);            // add JDesktopPanel to JFrame

    fooLabel.addMouseListener(this);
}

public void mouseClicked(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Clicked the picture.");
}
public void mouseEntered(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Entered the picture.");
}
public void mouseExited(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Exited the picture.");
}
public void mousePressed(MouseEvent me){}
public void mouseReleased(MouseEvent me){}

public static void createAndShowGUI()
{
    try 
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch (Exception e) { }

    frame = new fooFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setTitle("foo");
    frame.setSize(800,600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true); 
    }
}

You will have to get your own "test.gif" and if you make the internalFrame larger than the picture it fills the remaining space with the label. As all the mouseEvents fire when I cross the internalFrame rather than onto the picture like I want to have happen.

Edit2: Code modified with Kleopatra's suggestions.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;

public class a1
{
    public static void main(String[] args)
    {
            fooFrame.createAndShowGUI();
    }
}
class fooFrame extends JFrame implements MouseListener
{
private static fooFrame frame;
JLabel fooLabel;

public fooFrame()
{ 
    JDesktopPane background = new JDesktopPane();

    JInternalFrame internalFrame = new JInternalFrame("Internal Frame", true, true, true, true);
    internalFrame.setLocation(20, 20);
    internalFrame.setVisible(true);     
    internalFrame.pack();

    Image image = Toolkit.getDefaultToolkit().getImage("test.gif");
    fooLabel = new JLabel(new ImageIcon(image));
    fooLabel.setBorder(new LineBorder(Color.PINK));
    JScrollPane fooScrollPane = new JScrollPane((fooLabel), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    internalFrame.setLayout(new BoxLayout(internalFrame.getContentPane(), BoxLayout.LINE_AXIS));

    fooScrollPane.setViewportView(fooLabel);    // add JLabel to JScrollPane
    internalFrame.add(fooScrollPane);           // add JScrollPane to JInternalFrame
    background.add(internalFrame);              // add JInternalFrame to JDesktopPanel
    this.setContentPane(background);            // add JDesktopPanel to JFrame

    fooLabel.addMouseListener(this);
}

public void mouseClicked(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Clicked the picture.");
}

public void mouseEntered(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Entered the picture.");
}

public void mouseExited(MouseEvent me)
{
    if (me.getSource() == fooLabel)
        System.out.println("Exited the picture.");
}

public void mousePressed(MouseEvent me)
{
}

public void mouseReleased(MouseEvent me)
{
}

@Override
public Dimension getMaximumSize()
{
    return getPreferredSize();
}

public static void createAndShowGUI()
{
    try
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (Exception e)
    {
    }

    frame = new fooFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setTitle("foo");
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}
}

In this example, the internal frame will initially be no more than MAX_SIZE pixels. Smaller pictures will be sized so that scrolling is not required.

Addendum: Reading the title more closely, you may also want to limit the internal frame's maximum size:

internalFrame.setMaximumSize(new Dimension(fooLabel.getPreferredSize()));

Addendum: This variation may help clarify the problem. With the default layout, BorderLayout.CENTER, the scroll bars appear as required, but the MouseListener does not behave as desired. With a layout that respects preferred sizes, FlowLayout, the MouseListener reports correctly, but the the scroll bars never appear, as the label's preferred size never changes. I've added synthetic images for convenience.

enter image description here

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

class PictureFrame extends JFrame {

    private static final String NAME = "image.jpg";

    public PictureFrame() {
        JDesktopPane dtp = new JDesktopPane();
        dtp.add(new MyFrame("Large", FauxImage.create(300, 500), 50));
        dtp.add(new MyFrame("Small", FauxImage.create(200, 200), 25));
        this.add(dtp);
    }

    private static class MyFrame extends JInternalFrame {

        private static final int MAX_SIZE = 256;

        public MyFrame(String title, Image image, int offset) {
            super(title, true, true, true, true);
            //this.setLayout(new FlowLayout());
            final JLabel label = new JLabel(new ImageIcon(image));
            this.add(new JScrollPane(label));
            this.pack();
            int w = Math.min(MAX_SIZE, image.getWidth(null));
            int h = Math.min(MAX_SIZE, image.getHeight(null));
            Insets i = this.getInsets();
            this.setSize(w + i.left + i.right, h + i.top + i.bottom);
            this.setLocation(offset, offset);
            this.setVisible(true);
            label.addMouseListener(new MouseAdapter() {

                @Override
                public void mouseEntered(MouseEvent me) {
                    if (me.getSource() == label) {
                        System.out.println("Entered.");
                    }
                }

                @Override
                public void mouseExited(MouseEvent me) {
                    if (me.getSource() == label) {
                        System.out.println("Exited.");
                    }
                }
            });
        }
    }

    private static class FauxImage {

        static public Image create(int w, int h) {
            BufferedImage bi = new BufferedImage(
                w, h, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = bi.createGraphics();
            g2d.setPaint(Color.lightGray);
            g2d.fillRect(0, 0, w, h);
            g2d.setColor(Color.black);
            String s = w + "\u00D7" + h;
            int x = (w - g2d.getFontMetrics().stringWidth(s)) / 2;
            g2d.drawString(s, x, 24);
            g2d.dispose();
            return bi;
        }
    }

    public static void createAndShowGUI() {
        PictureFrame frame = new PictureFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("PictureFrame");
        frame.setSize(640, 400);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

The answer to layout problems is LayoutManager. Always. It's never setXXSize (though nothing is absolutely absolute :-)

Just to be sure I understand what you are after:

  • always have the label at its pref size (which by default is the size of its icon)
  • allow scrolling if the internalFrame is smaller than the pref, that is the scrollpane is resized to smaller, label remains at its pref
  • disallow strectching of the scrollPane and its content if the internalframe is larger than pref

The important LayoutManager in this scenario is the one which control the size of the scrollpane: that's the LayoutManager of the internalFrame's contentPane. The default manager is a BorderLayout: sizing its center to whatever space is available. We need to replace that with one that respects max and make the center component (the scrollpane) report a max (which typically is Short.MAX)

    internalFrame.setLayout(new BoxLayout(internalFrame.getContentPane(), BoxLayout.LINE_AXIS));
    Image image = Toolkit.getDefaultToolkit().getImage("test.gif");

    fooLabel = new JLabel(new ImageIcon(image));
    JScrollPane fooScrollPane = new JScrollPane(fooLabel),
            JScrollPane.VERTICAL_SCROLLBAR_NEVER,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
        @Override
        public Dimension getMaximumSize() {
            return getPreferredSize();
        }

    }; 
    internalFrame.add(fooScrollPane); // add JScrollPane to JInternalFrame