10

how can I prevent triggering and showing JPopupMenu only if is Mouse Cursor over selected JTable'Row

my question: if is there another way as getBounds from selected row and determine/compare that with Mouse position...

my simple sscce demonstrated just un-wanted opposite status, any row could be selected and JPopupMenu is triggered from whole JTable

import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableCheckBox extends JFrame {

    private static final long serialVersionUID = 1L;
    private JTable table;

    public TableCheckBox() {
        Object[] columnNames = {"Type", "Company", "Shares", "Price", "Boolean"};
        Object[][] data = {
            {"Buy", "IBM", new Integer(1000), new Double(80.50), false},
            {"Sell", "MicroSoft", new Integer(2000), new Double(6.25), true},
            {"Sell", "Apple", new Integer(3000), new Double(7.35), true},
            {"Buy", "Nortel", new Integer(4000), new Double(20.00), false}
        };
        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        table = new JTable(model) {

            private static final long serialVersionUID = 1L;

            @Override
            public Class getColumnClass(int column) {
                return getValueAt(0, column).getClass();
            }
        };
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        createPopupMenu();
    }

    private void createPopupMenu() {
        JPopupMenu popup = new JPopupMenu();
        JMenuItem myMenuItem1 = new JMenuItem("cccccccccccccccccccccc");
        JMenuItem myMenuItem2 = new JMenuItem("bbbbbbbbbbbbbbbbbbbbbb");
        popup.add(myMenuItem1);
        popup.add(myMenuItem2);
        MouseListener popupListener = new PopupListener(popup);
        table.addMouseListener(popupListener);
    }

    private class PopupListener extends MouseAdapter {

        private JPopupMenu popup;

        PopupListener(JPopupMenu popupMenu) {
            popup = popupMenu;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            maybeShowPopup(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (table.getSelectedRow() != -1) {
                maybeShowPopup(e);
            }
        }

        private void maybeShowPopup(MouseEvent e) {
            if (e.isPopupTrigger()) {
                popup.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }

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

            @Override
            public void run() {
                TableCheckBox frame = new TableCheckBox();
                frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocation(150, 150);
                frame.setVisible(true);
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319

2 Answers2

12

Are you looking for something like this perhaps?

To show popup over selected row(s) only

  private void maybeShowPopup(MouseEvent e) {
     if (e.isPopupTrigger()) {

        // get row that pointer is over
        int row = table.rowAtPoint(e.getPoint());

        // if pointer is over a selected row, show popup
        if (table.isRowSelected(row)) {
           popup.show(e.getComponent(), e.getX(), e.getY());
        }
     }
  }

Or the converse, to prevent popup from showing over selected rows only:

  private void maybeShowPopup(MouseEvent e) {
     if (e.isPopupTrigger()) {
        int row = table.rowAtPoint(e.getPoint());
        int[] selectedRows = table.getSelectedRows();

        if (!table.isRowSelected(row)) {
           popup.show(e.getComponent(), e.getX(), e.getY());
        }
     }
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 1
    +1 despite my different point of view :) . In effect yours is an easy an quick nice solution. – Heisenbug Sep 14 '11 at 23:11
  • 1
    +1 - once you'll have replaced all that looping and manual checking by isRowSelected(row) :-) – kleopatra Sep 15 '11 at 06:28
  • 1
    sorry to all. I was completely wrong. It seems that what I want to do, isn't possible. But @kleopatra, I'd like to know: is there any good reason for the fact that cell renderers can't handle mouse event? – Heisenbug Sep 15 '11 at 08:18
  • @Heisenbug - they are not part of the container hierarchy – kleopatra Sep 15 '11 at 08:19
  • @Hovercraft Full Of Eels thanks your post directly solved my question, but Jeanette's answer solved another my issue too, just +1 – mKorbel Sep 15 '11 at 09:15
8

It's an interesting question, because it highlights missing api on JComponent :-)

As we all know, the recommended way to register popupMenus is to use the componentPopupMenu property. Related api is

 void setComponentPopupMenu(JPopupMenu);
 JPopupMenu getComponentPopupMenu();
 Point getPopupLocation(MouseEvent);

what is missing (and actually needed for this requirement) is

JPopupMenu getComponentPopupMenu(MouseEvent);

this lack is all the more annoying, as the getPopupLocation is called (by AWTEventHelper deep in the LAF) after getComponentPopup(). So there's no leeway for a hack like storing the last mouse event which might have triggered the popup and then decide which/if to return popup. And returning null for the location will only result in showing it at the mouse location

The only (dirty) hack (around my utter reluctance to get my hands dirty with a MouseListener ;-) is to override getComponentPopup and decide there whether or not to return it based on current mouse position

    table = new JTable(model) {

        /** 
         * @inherited <p>
         */
        @Override
        public JPopupMenu getComponentPopupMenu() {
            Point p = getMousePosition();
            // mouse over table and valid row
            if (p != null && rowAtPoint(p) >= 0) {
                // condition for showing popup triggered by mouse
                if (isRowSelected(rowAtPoint(p))) {
                    return super.getComponentPopupMenu();
                } else {
                    return null;
                }
            }
            return super.getComponentPopupMenu();
        }

    };

the side-effect is that popup showing isn't triggered by keyboard as long as the mouse is anywhere above the table, which might or not be a problem.

kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • thanks for elaborating, how easy is learning.... hehehe, sure your post is answer and closing another my issue, my +1 – mKorbel Sep 15 '11 at 08:47
  • There may be an additional problem, I think, due to the use of a `JScrollPane` embedding the `JTable`. In the past, I remember having problems with events not occurring on the `JTable` when clicking outside any row (but *inside* the `JScrollPane`, in the empty space): I had to listen for events on the `JScrollPane` itself (or the `JViewport` can't remember exactly). So maybe @mKorbel, you'll also have to consider that (that should happen whenever the `JScrollPane` is higher than the `JTable`). – jfpoilpret Sep 15 '11 at 11:46
  • @jfpoilpret no idea how is possible to consume events from Mouse to JViewPort, can you provide an example about consiming MouseEvents from/to JViewPort, because I consider this to be completely deaf JComponent :-) – mKorbel Sep 15 '11 at 11:57
  • @jfpoilpret hmm ... don't quite understand which problem you mean: you can adjust the logic of if-show-what as needed. Nowadays, all tables are configured to fit the viewport height anyway (SwingX does by default and nobody complained - which is a rather remarkable fact in itself, because most are the born-complaining type :-) – kleopatra Sep 15 '11 at 11:58
  • 1
    @kleopatra you can set a minimum number of visible rows for `JTable` so that it fits the layout you want for your form. However, sometimes the actual table content is smaller than this minimum, then an empty area appears below the table, inside the `JScrollPane` boundaries; this empty space doesn't belong to the `JTable` but you may need it for popup display (eg a popup with "Create New Row..." item). Note that I have mentioned I couldn't remember if the `Mouselistener` had to be added to `JScrollPane` or `JViewport`, but I needed to add it somewhere else, in addition to, the `JTable` itself. – jfpoilpret Sep 15 '11 at 12:06
  • Found it! In HiveGUI (well, about 4 years ago...), for popup handling in JTable, I added the same MouseListener to the `JTable` and to the `JViewport` it was embedded in, so I the user could show the popup even when clicking ouside a row. – jfpoilpret Sep 15 '11 at 12:28
  • @jfpoilpret ahh, see what mean, thanks. Just: that empty space (not belonging to the table) doesn't happen anymore (with trackViewportHeight set to true) – kleopatra Sep 15 '11 at 12:28
  • OK but I can still have a `JScrollPane` area that shows more than the number of actual rows in the `JTable`, right? In this case, there's still some empty space appearing, where does it belong? The `JTable`? – jfpoilpret Sep 15 '11 at 12:31
  • @jfpoilpret .... ehh ... no, dont think so: the height is completely filled by the table, viewport stretches its until an exact fit – kleopatra Sep 15 '11 at 12:36
  • @kleopatra you mean `setFillsViewportHeight()` I guess; it didn't exist at the time of HiveGUI, and I hadn't paid much attention to this new API. – jfpoilpret Sep 15 '11 at 12:49
  • @jfpoilpret exactly, thanks for looking up the correct name :-) – kleopatra Sep 15 '11 at 12:51