JTextArea with popup menu – JPopupTextArea

I recently worked on a project which was using the old AWT TextArea component.
OK, TextArea is pretty basic, but at least it gives you a right click popup menu with simple copy, cut paste functionality, out of the box.

The JTextArea, whilst far more powerful in theory, does not give you this basic functionality, which is a major step backwards, in my book. To achieve the same thing you have to roll your own popup menu. Once you have done this, you would think that simply adding your new JPopupMenu to the JTextArea using the helpful looking add(PopupMenu p) method would be enough.

But nooooo. That method is a hangover from the old AWT days, and that would be way too easy. This appears to be a red herring.

In fact, if you want it to actually show up, you have to write your own MouseListener to listen for the popup trigger event, register it with the JTextArea, and display it yourself.

So, how is this a step forward from the old AWT days, you may well ask?

After all, 99% of all developers out there would probably just like a simple right click popup menu on their text area, to cover the basic copy cut paste actions, without jumping through too many hoops along the way. Don’t get me wrong, I am mad keen on Swing, it is just so flexible. I would just like it to provide a better solution out of the box for the most common cases, more of the time.

Well, in any case, here is a simple subclass of JTextArea which makes this easy.
If you wanted, you could easily modify it to add other actions from the DefaultEditorKit to the menu, such as undo/redo, or set Icons on the Actions.

JPopupTextArea

public class JPopupTextArea extends JTextArea
{
    private HashMap actions;

    public JPopupTextArea()
    {
        addPopupMenu();
    }

    private void addPopupMenu()
    {
        createActionTable();

        JPopupMenu menu = new JPopupMenu();
        menu.add(getActionByName(DefaultEditorKit.copyAction, "Copy"));
        menu.add(getActionByName(DefaultEditorKit.cutAction, "Cut"));
        menu.add(getActionByName(DefaultEditorKit.pasteAction, "Paste"));
        menu.add(new JSeparator());
        menu.add(getActionByName(DefaultEditorKit.selectAllAction, "Select All"));
        add(menu);

        addMouseListener(
           new PopupTriggerMouseListener(
                   menu,
                   this
           )
        );

        //no need to hold the references in the map,
        // we have used the ones we need.
        actions.clear();
    }

    private Action getActionByName(String name, String description) {
        Action a = (Action)(actions.get(name));
        a.putValue(Action.NAME, description);
        return a;
    }


    private void createActionTable() {
        actions = new HashMap();
        Action[] actionsArray = getActions();
        for (int i = 0; i < actionsArray.length; i++) {
            Action a = actionsArray[i];
            actions.put(a.getValue(Action.NAME), a);
        }
    }

    public static class PopupTriggerMouseListener extends MouseAdapter
    {
        private JPopupMenu popup;
        private JComponent component;

        public PopupTriggerMouseListener(JPopupMenu popup, JComponent component)
        {
            this.popup = popup;
            this.component = component;
        }

        //some systems trigger popup on mouse press, others on mouse release, we want to cater for both
        private void showMenuIfPopupTrigger(MouseEvent e)
        {
            if (e.isPopupTrigger())
            {
               popup.show(component, e.getX() + 3, e.getY() + 3);
            }
        }

        //according to the javadocs on isPopupTrigger, checking for popup trigger on mousePressed and mouseReleased
        //should be all  that is required
        //public void mouseClicked(MouseEvent e)  
        //{
        //    showMenuIfPopupTrigger(e);
        //}

        public void mousePressed(MouseEvent e)
        {
            showMenuIfPopupTrigger(e);
        }

        public void mouseReleased(MouseEvent e)
        {
            showMenuIfPopupTrigger(e);
        }

    }

}




You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

3 Responses to “JTextArea with popup menu – JPopupTextArea”

  1. HI, my name is Nelson. Honestly, I don’t know much about Java, but I found out that is not that complicate to create a TextArea’s PopupMenu after all. Check this code and let me know if it helps you.

    private JTextArea getJTextArea() {
    if (jTextArea == null) {
    jTextArea = new JTextArea();
    jTextArea.add(getJPopupMenuEdicion());
    jTextArea.addMouseListener(new java.awt.event.MouseAdapter() {
    public void mouseClicked(java.awt.event.MouseEvent e) {
    if (e.getButton()==MouseEvent.BUTTON3){
    //getJPopupMenuEdicion();
    jPopupMenuEdicion.show(jTextArea, e.getX(), e.getY());
    jPopupMenuEdicion.setVisible(true);
    }
    }
    });
    }
    return jTextArea;
    }

    private JPopupMenu getJPopupMenuEdicion() {
    if (jPopupMenuEdicion == null) {
    jPopupMenuEdicion = new JPopupMenu();
    jPopupMenuEdicion.add(getJPopupMenuItemCopiar());
    jPopupMenuEdicion.add(getJPopupMenuItemCortar());
    jPopupMenuEdicion.add(getJPopupMenuItemPegar());

    }
    return jPopupMenuEdicion;
    }

    Sorry if it is a little messed up. By the way, I’m using Eclipse, but on this code I had to figure it out by myself.

    Let me know your opinion, thanks.
    P.D.: I’m from Argentina, so all the unknown words are in Spanish:
    Copiar = Copy
    Cortar = Cut
    Pegar = Paste
    Edicion = Edition

    NAS.QWERTY@HOTMAIL.COM

  2. Hi Nelson. Thanks for the feedback! Yes using an anonymous inner class mouse adapter to trigger the popup works just as well. At the time I wrote this blog article I think I must have wanted a drop in replacement for the old AWT TextArea class which provided a default popup menu without having to code up extra mouse listeners etc – which is what the code above attempts to provide.

    If you check the mouse event with e.isPopupTrigger() that is a platform independent way to detect whether the correct mouse button has been pressed, since different OS may have different conventions relating to how a user triggers the popup menu. If you read the javadocs on MouseEvent.isPopupTrigger it mentions that you need to check this on both mousePressed() and mouseReleased(). I’ll update my code to do that exactly as it suggests!

  3. Hi Nick,

    First off I want to thank you for providing this code. I have noticed a few problems and want to provide their solutions. I was getting a NullPointerExceptions when using your code for a second instance of the JPopupTextArea in a JPanel when adding to the menu.

    I removed getActionByName, removed the actions HashMap, and
    made the following changes:

       final static String COPY = "Copy";
       final static String CUT = "Cut";
       final static String PASTE = "Paste";
       final static String SELECTALL = "Select All";

       private void addPopupMenu() {
        final JPopupMenu menu = new JPopupMenu();
        final JMenuItem copyItem = new JMenuItem();
     copyItem.setAction(getActionMap().get(DefaultEditorKit.copyAction));
        copyItem.setText(COPY);

        final JMenuItem cutItem = new JMenuItem();
        cutItem.setAction(getActionMap().get(DefaultEditorKit.cutAction));
        cutItem.setText(CUT);

        final JMenuItem pasteItem = new JMenuItem(PASTE);
        pasteItem.setAction(getActionMap().get(DefaultEditorKit.pasteAction));
        pasteItem.setText(PASTE);

        final JMenuItem selectAllItem = new JMenuItem(SELECTALL);
        selectAllItem.setAction(getActionMap().get(DefaultEditorKit.selectAllAction));
        selectAllItem.setText(SELECTALL);

        menu.add(copyItem);
        menu.add(cutItem);
        menu.add(pasteItem);
        menu.add(new JSeparator());
        menu.add(selectAllItem);

        add(menu);
        addMouseListener(new PopupTriggerMouseListener(menu, this));
    }

    Best Regards,
    David

Leave a Reply