检测当前鼠标位置上的图像仅适用于图像的一部分

时间:2014-06-04 11:51:32

标签: java swing jtextpane

我有一个JTextPane,我添加了文本,而一些文本通过StyleConstants.setIcon()设置了图像。我还向JTextPane添加了一个鼠标监听器,以检测鼠标在图像上单击/悬停的时间,但它只在图像的左侧部分检测到它。我做错了吗?

Screenshot of hovering over different parts of the image

SSCCE(将鼠标悬停在图像上会更改鼠标光标以指示何时检测到图像):

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

/**
 * SSCCE to show how detecting an image under the current mouse position only
 * works on part of the image. It adds a simple image to the document of the
 * JTextPane and changes the mouse cursor when it detects the mouse hovering
 * over the image.
 */
public class JTextPaneImage {

    private static void createWindow() {

        // Create window
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create JTextPane and add to window
        final JTextPane textPane = new JTextPane();
        textPane.setEditable(false);
        textPane.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                AttributeSet style = getAttributes(e);
                if (style != null && StyleConstants.getIcon(style) != null) {
                    textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                } else {
                    textPane.setCursor(Cursor.getDefaultCursor());
                }
            }
        });
        frame.add(new JScrollPane(textPane));

        try {
            StyledDocument doc = (StyledDocument)textPane.getDocument();

            // Add some text
            doc.insertString(0, "Some text ", null);

            // Add the image
            SimpleAttributeSet style = new SimpleAttributeSet();
            StyleConstants.setIcon(style, createImage());
            doc.insertString(doc.getLength(), "test", style);
        } catch (BadLocationException ex) {
            Logger.getLogger(JTextPaneImage.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Display everything
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /**
     * Retrieves the style of where the mouse is positioned (assuming this is
     * a JTextPane).
     * 
     * @param e The mouse event containing the mouse position
     * @return The AttributeSet or null if none could be found
     */
    private static AttributeSet getAttributes(MouseEvent e) {
        JTextPane text = (JTextPane)e.getSource();
        Point mouseLocation = new Point(e.getX(), e.getY());
        int pos = text.viewToModel(mouseLocation);

        if (pos >= 0) {
            StyledDocument doc = text.getStyledDocument();
            Element element = doc.getCharacterElement(pos);
            return element.getAttributes();
        }
        return null;
    }

    /**
     * Creates a single 28x28 image filled with a single color.
     * 
     * @return The created ImageIcon
     */
    public static ImageIcon createImage() {
        BufferedImage image = new BufferedImage(28,28, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, 28, 28);
        g.dispose();
        return new ImageIcon(image);
    }

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

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

2 个答案:

答案 0 :(得分:4)

使用强text.viewToModel(mouseLocation)检测偏移量,然后从获得的偏移量中检索样式。

逻辑是返回更接近鼠标位置的偏移量。因此,当您单击视图的右半部分时,将返回下一个偏移(视图后的偏移)。您可以尝试相同的设置一个大字母(例如m大字体)。当你clikc接近字母结束时,插入符号设置在字母后面。这里的逻辑是一样的。

所以你在图像后面有位置并从位置获取样式但在图像视图文本元素之后没有在属性中有图标而你没有图像。

更新:

为了提供正确的行为,我建议使用modelToView()并传递获得的偏移量。从矩形中你可以弄清楚你的X位置是否已经过时了。矩形的X.如果插入矩形的x大于鼠标X,你可以尝试以前的偏移。

UPDATE2:您可以覆盖IconView并使用paint()方法存储图像视图的最后一个绘制矩形。存储在最后绘制的地图中。在鼠标移动/单击时,检查地图以查找其中一个矩形是否包含该点。

OR

您可以使用View的方法getChildAllocation()类似的东西被描述为here来计算图像边界。从根视图开始,然后向下直到叶子计算X,Y的正确视图。如果叶子视图是IconView,那么你的图像已经过了。

答案 1 :(得分:1)

基于answer of StanislavL我更改了SSCCE以实现他的第一个解决方案,在此处对获得的偏移量使用modelToView()来检查鼠标位置是否实际位于该偏移量上元素的位置,如果不是,则使用之前的偏移量。

我不仅检查x位置,还检查y位置,因为如果没有其他内容,图像下方/左侧的鼠标位置也可以识别为图像。

SSCCE有一个按钮来启用解决方案,以展示差异:

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

/**
 * SSCCE to show how detecting an image under the current mouse position only
 * works on part of the image. It adds a simple image to the document of the
 * JTextPane and changes the mouse cursor when it detects the mouse hovering
 * over the image.
 * 
 * To demonstrate the difference, you can turn the solution on and off by
 * clicking the button.
 */
public class JTextPaneImage {

    private static boolean enableSolution;

    private static void createWindow() {

        // Create window
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create JTextPane and add to window
        final JTextPane textPane = new JTextPane();
        textPane.setEditable(false);
        textPane.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                AttributeSet style = getAttributes(e);
                if (style != null && StyleConstants.getIcon(style) != null) {
                    textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                } else {
                    textPane.setCursor(Cursor.getDefaultCursor());
                }
            }
        });
        frame.add(new JScrollPane(textPane));

        // Just to disable/enable solution for demonstrating the difference
        final JToggleButton button = new JToggleButton("Enable solution");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                enableSolution = button.isSelected();
            }
        });
        frame.add(button, BorderLayout.SOUTH);

        // Add some text and images
        try {
            StyledDocument doc = (StyledDocument)textPane.getDocument();

            SimpleAttributeSet style = new SimpleAttributeSet();
            StyleConstants.setIcon(style, createImage());

            doc.insertString(doc.getLength(), "Some text ", null);
            doc.insertString(doc.getLength(), "test", style);
            doc.insertString(doc.getLength(), "abc\n", null);
            doc.insertString(doc.getLength(), "another image", style);
        } catch (BadLocationException ex) {
            Logger.getLogger(JTextPaneImage.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Display everything
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /**
     * Retrieves the style of where the mouse is positioned (assuming this is
     * a JTextPane).
     * 
     * @param e The mouse event containing the mouse position
     * @return The AttributeSet or null if none could be found
     */
    private static AttributeSet getAttributes(MouseEvent e) {
        JTextPane text = (JTextPane)e.getSource();
        Point mouseLocation = new Point(e.getX(), e.getY());
        int pos = text.viewToModel(mouseLocation);

        if (pos >= 0) {
            if (enableSolution) {

                /**
                 * Solution, this is basicially what is different:
                 * 
                 * Check if the found position is actually located where the
                 * mouse is located, or else use the previous one. It doesn't
                 * only check the x position (which would already help), but
                 * also the y position, because else the area below/left of the
                 * image may also be recognized as image (if there is no other
                 * content there).
                 */
                try {
                    Rectangle rect = text.modelToView(pos);
                    int lowerCorner = rect.y + rect.height;
                    if (e.getX() < rect.x && e.getY() < lowerCorner && pos > 0) {
                        pos--;
                    }
                } catch (BadLocationException ex) {
                    Logger.getLogger(JTextPaneImage.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            StyledDocument doc = text.getStyledDocument();
            Element element = doc.getCharacterElement(pos);
            return element.getAttributes();
        }
        return null;
    }

    /**
     * Creates a single 28x28 image filled with a single color.
     * 
     * @return The created ImageIcon
     */
    public static ImageIcon createImage() {
        BufferedImage image = new BufferedImage(28,28, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, 28, 28);
        g.dispose();
        return new ImageIcon(image);
    }

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

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