
net.sf.cuf.ui.table.TableContextMenu Maven / Gradle / Ivy
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