All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.cuf.ui.table.TableContextMenu Maven / Gradle / Ivy

There is a newer version: 2.5.3
Show newest version
package net.sf.cuf.ui.table;

import net.sf.cuf.ui.SwingDecorator;

import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;


/**
 * Context menu for a table with pluggable actions.
 * The context menu has to be bound to the table separately. Any {@link JTable} can be used
 * although there are additional functionalities for {@link SortingTable}s.
 * The supported actions can be freely defined as {@link ContextMenuAction}s. There are
 * several predefined actions available which are used as the default actions for the
 * context menu.
 *
 * @author  Hendrik Wördehoff, sd&m AG
 */
public class TableContextMenu
extends MouseAdapter
implements AncestorListener
{
    /** Activate the context menu in the table body. */
    public static final int CONTEXT_MENU_IN_TABLE = 1;
    /** Activate the context menu in the table header. */
    public static final int CONTEXT_MENU_IN_HEADER = 2;
    /** Activate the context menu in the {@link JViewport} around the table (e.g. from a {@link javax.swing.JScrollPane}). */
    public static final int CONTEXT_MENU_IN_VIEWPORT = 4;

    /**
     * Activate the context menu in the table header only (== {@link #CONTEXT_MENU_IN_HEADER}).
     * This constant is only provided for convenience.
     */
    public static final int CONTEXT_MENU_IN_TABLE_HEADER = CONTEXT_MENU_IN_HEADER;
    /**
     * Activate the context menu in the table header and the table body  (== {@link #CONTEXT_MENU_IN_TABLE} | {@link #CONTEXT_MENU_IN_HEADER}).
     * This constant is only provided for convenience.
     */
    public static final int CONTEXT_MENU_IN_WHOLE_TABLE = CONTEXT_MENU_IN_TABLE | CONTEXT_MENU_IN_HEADER;
    /**
     * Activate the context menu in the whole table and its surrounding viewport
     * (== {@link #CONTEXT_MENU_IN_TABLE} | {@link #CONTEXT_MENU_IN_HEADER} | {@link #CONTEXT_MENU_IN_VIEWPORT}).
     * This constant is only provided for convenience.
     */
    public static final int CONTEXT_MENU_IN_TABLE_AND_PARENT = CONTEXT_MENU_IN_TABLE | CONTEXT_MENU_IN_HEADER | CONTEXT_MENU_IN_VIEWPORT;

    /**
     * Separator entry for the context menu.
     */
    public static final ContextMenuAction SEPARATOR = null;

    /** Prefix of identifier for the {@link net.sf.cuf.ui.SwingDecorator}. */
    private static final String KENNUNG = "TABLESORT_CONTEXTMENU";

    /**
     * Table this context menu is dealing with.
     */
    private JTable mTable;

    /**
     * Mode of the context menu.
     * Legal values are combinations of {@link #CONTEXT_MENU_IN_TABLE_HEADER}, {@link #CONTEXT_MENU_IN_WHOLE_TABLE} and {@link #CONTEXT_MENU_IN_TABLE_AND_PARENT}.
     */
    private int mMode = 0;

    /**
     * Actions in this context menu.
     */
    private ContextMenuAction[] mActions;

    /**
     * This context menu may also be attached to the parent of the table.
     * The parent must be a {@link JViewport} for this.
     */
    private Container mTableParent = null;

    /**
     * Context menu for the table.
     * Will be instantiated when needed.
     */
    private JPopupMenu mPopup = null;

    /**
     * Index of the view column in which the trigger of the popup menu occured.
     * This value is only valid while the popup menu is handled.
     */
    private int mViewIndexOfColumnUnderMouse;

    /**
     * Index of the model column in which the trigger of the popup menu occured.
     * This value is only valid while the popup menu is handled.
     */
    private int mModelIndexOfColumnUnderMouse;

    /**
     * Index of the row in which the trigger of the popup menu occured.
     * This value is only valid while the popup menu is handled.
     */
    private int mIndexOfRowUnderMouse;

    /**
     * Create the default actions for a context menu.
     * In this implementation they are:
     * 
* sort column ascending
* sort column descending
* hide column
* move column to left of table
* move column to right of table
* resize column to optimal width
* show all columns
* bring columns into their original order
*
* perform the sorting dialog for the table
* perform the visibility dialog for the table *
* * @param pTable table to generate the actions for * @return list of default actions */ private static ContextMenuAction[] createDefaultActions(final JTable pTable) { ContextMenuAction[] actions; if ( pTable instanceof SortingTable ) { // additional sorting and visibility actions actions = new ContextMenuAction[]{new ContextMenuActionSortColumn(true), new ContextMenuActionSortColumn(false), new ContextMenuActionHideColumn(), new ContextMenuActionColumnToEdge(ContextMenuActionColumnToEdge.EDGE_LEFT), new ContextMenuActionColumnToEdge(ContextMenuActionColumnToEdge.EDGE_RIGHT), new ContextMenuActionOptimalColumnWidth(), new ContextMenuActionShowAll(), new ContextMenuActionResetColumns(), SEPARATOR, new ContextMenuActionSortDialog(), new ContextMenuActionVisibilityDialog()}; } else { // standard actions for any table actions = new ContextMenuAction[]{new ContextMenuActionColumnToEdge(ContextMenuActionColumnToEdge.EDGE_LEFT), new ContextMenuActionColumnToEdge(ContextMenuActionColumnToEdge.EDGE_RIGHT), new ContextMenuActionOptimalColumnWidth(), new ContextMenuActionResetColumns()}; } return actions; } /** * Constructor to install this context menu in a table. * The mode is assumed to be == {@link #CONTEXT_MENU_IN_TABLE} | {@link #CONTEXT_MENU_IN_HEADER} | {@link #CONTEXT_MENU_IN_VIEWPORT}. * The actions are assumed to be the default actions. * @param pTable table this context menu works on (must not be null) */ public TableContextMenu(final JTable pTable) { this(pTable, CONTEXT_MENU_IN_TABLE | CONTEXT_MENU_IN_HEADER | CONTEXT_MENU_IN_VIEWPORT); } /** * Constructor to install this context menu in a table. * The actions are assumed to be the default actions. * @param pTable table this context menu works on (must not be null) * @param pMode a bitflag consisting of {@link #CONTEXT_MENU_IN_TABLE}, {@link #CONTEXT_MENU_IN_HEADER} and/or {@link #CONTEXT_MENU_IN_VIEWPORT} */ public TableContextMenu(final JTable pTable, final int pMode) { this(pTable, pMode, createDefaultActions(pTable)); } /** * Constructor to install this context menu in a table. * @param pTable table this context menu works on (must not be null) * @param pMode a bitflag consisting of {@link #CONTEXT_MENU_IN_TABLE}, {@link #CONTEXT_MENU_IN_HEADER} and/or {@link #CONTEXT_MENU_IN_VIEWPORT} * @param pActions actions to include in the context menu (must not be null) */ public TableContextMenu(final JTable pTable, final int pMode, final ContextMenuAction[] pActions) { // check preconditions if ( pTable == null ) throw new IllegalArgumentException("table must not be null"); if ( pActions == null || pActions.length == 0 ) throw new IllegalArgumentException("actions must not be null or empty"); this.mTable = pTable; this.mActions = pActions; // initialize the pActions ContextMenuAdapter adapter = new Adapter(); for (final ContextMenuAction action : pActions) { if (action != null) action.initialize(adapter); } // install context menu in pTable and possibly surrounding JScrollPane pTable.addAncestorListener(this); // we need to notice when the pTable is placed into a JViewport setMode(pMode); } /** * Disconnect the context menu from its table. * Use this method if you need to get rid of this context menu instance. */ public void dispose() { // remove listeners setMode(0); // remove all mouse listeners mTable.removeAncestorListener(this); // break this instance mTable = null; } /** * Return the mode as a bitflag consisting of * {@link #CONTEXT_MENU_IN_TABLE}, {@link #CONTEXT_MENU_IN_HEADER} and {@link #CONTEXT_MENU_IN_VIEWPORT}. * @return current mode */ public int getMode() { return mMode; } /** * Set the mode as a bitflag consisting of * {@link #CONTEXT_MENU_IN_TABLE}, {@link #CONTEXT_MENU_IN_HEADER} and {@link #CONTEXT_MENU_IN_VIEWPORT}. * @param pMode new mode */ public void setMode(final int pMode) { // remove old listeners if ( (this.mMode & CONTEXT_MENU_IN_HEADER) != 0 ) mTable.getTableHeader().removeMouseListener(this); if ( (this.mMode & CONTEXT_MENU_IN_TABLE) != 0 ) mTable.removeMouseListener(this); if ( mTableParent != null ) // tableParent is only set when there is a listener installed in it { mTableParent.removeMouseListener(this); mTableParent = null; } // add new listeners this.mMode = pMode; if ( (pMode & CONTEXT_MENU_IN_HEADER) != 0 ) mTable.getTableHeader().addMouseListener(this); if ( (pMode & CONTEXT_MENU_IN_TABLE) != 0 ) mTable.addMouseListener(this); checkTableParent(); } /** * Process add/remove of the table to/from a parent. * We have to install mouse listeners in order to get the context menu working properly * for tables that are embedded into a {@link javax.swing.JScrollPane}. */ private void checkTableParent() { if ( (mMode & CONTEXT_MENU_IN_VIEWPORT) != 0 ) // only install mouse listener in corresponding mode { Container currentParent = mTable.getParent(); if ( currentParent != mTableParent) { // remove contect menu mouse listener from previous parent if ( mTableParent != null ) { mTableParent.removeMouseListener(this); mTableParent = null; } // add context menu mouse listener to new parent if ( currentParent instanceof JViewport ) { mTableParent = currentParent; mTableParent.addMouseListener(this); } } } } /** * Activate the context menu if the mouse click represents a popup trigger. * @param pEvent event to process */ private void processMouseClickInTable(final MouseEvent pEvent) { if ( !pEvent.isConsumed() ) { if ( pEvent.isPopupTrigger() ) { SwingUtilities.invokeLater(new ShowPopup(pEvent)); // delay opening the popup until this mouse event is completely processed (otherwise we get display artifacts) pEvent.consume(); } } } /** * Instantiate the context menu lazily. * @return the popup menu */ private JPopupMenu getPopup() { if ( mPopup == null ) { mPopup = new JPopupMenu(SwingDecorator.getTitle(KENNUNG)); JMenuItem mi; for (final ContextMenuAction action : mActions) { if (action == null) { // add separator mPopup.addSeparator(); } else { // add action entry mi = new JMenuItem(); SwingDecorator.initialize(mi, action.getKennung()); mi.addActionListener(action); mPopup.add(mi); } } } return mPopup; } /*** Interface MouseListener **********************************************/ /** * React on mouse pressed events. * This may be a popup trigger in certain Look&Feels. */ public void mousePressed(final MouseEvent pEvent) { processMouseClickInTable(pEvent); } /** * React on mouse released events. * This may be a popup trigger in certain Look&Feels. */ public void mouseReleased(final MouseEvent pEvent) { processMouseClickInTable(pEvent); } /*** Interface AncestorListener *******************************************/ /** * Check if the parent of our table has changed. */ public void ancestorAdded(final AncestorEvent pEvent) { checkTableParent(); } /** * Check if the parent of our table has changed. */ public void ancestorRemoved(final AncestorEvent pEvent) { checkTableParent(); } /** * We are not interested in this event. */ public void ancestorMoved(final AncestorEvent pEvent) { } /*** Inner classes ********************************************************/ /** * Helper class to show the popup. * We need this helper to delay showing the popup until the * triggering event has been fully processed. */ private class ShowPopup implements Runnable { /** Component from which we received the mouse click. */ private Component mComponent; /** Position of the mouse click relative to {@link #mComponent}. */ private Point mPosition; /** * Constructor. * @param pEvent event to get {@link #mComponent}and {@link #mPosition} from. */ public ShowPopup(final MouseEvent pEvent) { mComponent = pEvent.getComponent(); mPosition = (Point)pEvent.getPoint().clone(); // just to be on the safe side if somebody changes this value } /** * Perform all necessary tasks and open the popup. *
    *
  • determine in which column the mouse click occured
  • *
  • enable/disable the popup menu entries according to their actions
  • *
  • adjust the popup menu position and show it with {@link GuiUtilities#showPopupAdjusted(JPopupMenu, Component, Point)}
  • *
*/ public void run() { // calculate position of mouse click in table mViewIndexOfColumnUnderMouse = -1; // column in which the mouse click occured if ( mComponent != mTableParent) { mViewIndexOfColumnUnderMouse = mTable.columnAtPoint(mPosition); } mModelIndexOfColumnUnderMouse = mTable.convertColumnIndexToModel(mViewIndexOfColumnUnderMouse); mIndexOfRowUnderMouse = -1; if ( mComponent == mTable) { mIndexOfRowUnderMouse = mTable.rowAtPoint(mPosition); } // enable/disable popup for ( int i = 0; i < mActions.length; i++ ) { if ( mActions[i] != SEPARATOR ) { getPopup().getComponent(i).setEnabled(mActions[i].isEnabled()); } } // show popup GuiUtilities.getDefault().showPopupAdjusted(getPopup(), mComponent, mPosition); } } /** * We do not want to give everybody access to our internal values so this adapter is implemented as a inner class. */ private class Adapter implements ContextMenuAdapter { /** * @return identifier of this popup menu for the {@link net.sf.cuf.ui.SwingDecorator} to be used as a prefix in the action * identifiers */ public String getContextMenuKennung() { return KENNUNG; } /** * @return reference to our table */ public JTable getTable() { return mTable; } /** * This value is only valid while the popup is being processed. * @return view index of the current column or -1 */ public int getViewColumnIndex() { return mViewIndexOfColumnUnderMouse; } /** * This value is only valid while the popup is being processed. * @return model index of the current column or -1 */ public int getModelColumnIndex() { return mModelIndexOfColumnUnderMouse; } /** * Return index of the row to work on. * This value is only valid during event dispatching. * @return row index or -1 if there is no applicable row */ public int getRowIndex() { return mIndexOfRowUnderMouse; } /** * Determine the {@link Frame} the table resides in. * An exception is thrown if the table is not in a Frame. * @return current frame * @throws IllegalStateException if the table does not (yet) reside in a Frame */ public Frame getFrameForTable() throws IllegalStateException { Window frame = SwingUtilities.windowForComponent(getTable()); if ( frame == null || !(frame instanceof Frame) ) { throw new IllegalStateException("the table this context menu works on must reside in a java.awt.Frame"); } return (Frame)frame; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy