2

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);
}
}
lycosis
  • 131
  • 2
  • 12
  • A JLabel doesn't stretch even if it is added to a scrollpane. That is the image is alway painted at its actual size. Post your [SSCCE](http://www.sscce.org) that demonstrates your problem. – camickr Nov 11 '11 at 02:19
  • 1
    please learn java naming conventions and stick to them – kleopatra Nov 11 '11 at 08:55
  • In edit 2, you need `fooLabel.setOpaque(true);` and `pack()` _after_ `add()`. – trashgod Nov 11 '11 at 21:51

2 Answers2

2

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();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • The value suggested for `setMaximumSize()` is not ideal _ad hoc_; I'd welcome any better suggestion(s). – trashgod Nov 11 '11 at 07:54
  • simply don't use setXXSize ;-) dontdontdont .. the label _has_ a max which is content dependent (very near the pref), no need to fiddle with it – kleopatra Nov 11 '11 at 09:47
  • Thanks for the help, but this still has the same problem as my code does. Namely that when the internal frame is larger than the picture within, the space between the picture and the internalFrame is still part of the label. I might not be explaining this well and for that I'm sorry, example: Label is 100x100 and the internalFrame is 200x200. There is going to be space between the edge of the label and the internal frame. I want the mouse events to only fire if I'm over the 100x100 label rather than upon entering the 200x200 internalFrame because the label was stretched to fit the frame. – lycosis Nov 11 '11 at 16:33
  • Yes, that's why I suggested adding a line to set the internal frame's maximum size to the label's preferred size. Of course, @kleopatra is right about layouts and `setXXX()`. – trashgod Nov 11 '11 at 16:44
  • @user1038988 'I want the mouse events to only fire if I'm over the 100x100 label' which is exactly what happens in my setup (dont forget to remove all those manual XXsizing, you'll only confuse the manager). For debugging, add a pink border and let the scrollpane show its bars - I suspect your image might be bigger than you assume – kleopatra Nov 11 '11 at 17:20
  • I've updated the post to show my SSCCE modified with your suggestions, Kleopatra. Is that what your intended modifications are? Because, ideally, I want that pink right on the edge of the image itself, not on the outer border near the edge of the internalFrame when the internalFrame is larger than the label. – lycosis Nov 11 '11 at 17:40
  • it's a bit late, so I might miss something - but as far as I see right now you did _not_ follow all of my suggestions. The other part (besides setting the layoutManager which is equally important) was to subclass JScrollPane, override its getMax to return getPref – kleopatra Nov 11 '11 at 23:30
  • I apologize for my recalcitrance. My sscce was an interim attempt to pin down the actual requirement, given two possibilities that I see as mutually incompatible. I _did_ get rid of `setXXXSize()`, and I think `setSize()` may be reasonable in this context. Sleep now, and correct me over the weekend at leisure. – trashgod Nov 12 '11 at 00:10
  • darn ... it was too late for _me_, indeed. A lame excuse only for forgetting to address my comment at @user1038988 as intended :-( Sorry! – kleopatra Nov 12 '11 at 09:40
1

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
Community
  • 1
  • 1
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • +1 I think this may be closer to what the questioner actually wanted. – trashgod Nov 11 '11 at 13:50
  • Yes, that's what I'm after but when I add this to my code it doesn't fix the problem. Because the label is still stretched to the size of the internalFrame and the mouse events trigger upon entering the internalFrame rather than over the picture itself. I want an internalFrame like Photoshop has: when you have a 100x100 picture and you resize the picture's internalFrame to 200x200 there's that "space" between the picture and the edge of the internalFrame that doesn't count as the picture. – lycosis Nov 11 '11 at 16:52
  • no, it doesn't in my version. Dont forget to remove all those setXXSize in your code, then it behaves exactly like you want (there is free space between the picture and the edge of the internal frame if resized bigger than the picture) - just removed that part about sscce, your's is, except for a publicly accessible image .. – kleopatra Nov 11 '11 at 17:13
  • One problem is that the internal frame may start out larger than the desktop pane for a large image. On platforms that resize from the bottom-right corner, the bottom of the image may become inaccessible until the desktop pane is resized. – trashgod Nov 11 '11 at 18:01
  • could be but that's not the base problem here (didn't try, though, so there's leeway :-) – kleopatra Nov 11 '11 at 23:32