net.sf.cuf.ui.table.SortDialog Maven / Gradle / Ivy
package net.sf.cuf.ui.table;
import net.sf.cuf.ui.SwingDecorator;
import java.awt.Container;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
/**
* A modal sorting dialog for a table.
* The dialog allows the selection of sorting criteria for a table.
* Several sorting criteria may be selected by the user.
* The user may choose from all currently visible columns.
*
* The dialog will be centered over the parent window the first time it is opened.
* Later on it will keep its last position.
*
* @author Hendrik Wördehoff, sd&m AG
*/
public class SortDialog extends BasicDialog
{
/** Number of supported sort criteria. */
private static final int CRITERIA_COUNT = 3;
/** Number of additional entries for column headers. Currently only empty string. */
private static final int COLUMN_HEADER_OFFSET = 1;
/** List of combo boxes for the sort criteria. */
private JComboBox[] mCriteria;
/** List of radio buttons for the sorting direction. */
private JRadioButton[] mAscending;
/** List of radio buttons for the sorting direction. */
private JRadioButton[] mDescending;
/**
* Reference to the table we are working for.
* This attribute is only valid during {@link #show(JTable,TableSortInfo)}.
*/
private JTable mCurrentTable = null;
/**
* Result of showing the dialog.
*/
private TableSortInfo mResult;
/**
* Constructor.
* @param pOwner parent window (must not be null
)
*/
public SortDialog(final Frame pOwner)
{
super(pOwner, "TABLESORT_DIALOG");
addComponents();
}
/**
* Create all the components of the dialog.
*
* The dialog features the following interesting points:
*
* - the OK button acts as the default button and can be activated with the ENTER key
* - in order to get the default button working properly the keyboard action on ENTER of the combo boxes has to be removed
* - the default button behaviour has to be modified to resolve conflicts with combo box popups
* - Cancel becomes default button when it gets the focus so the default button has to be reset to OK as soon as the focus moves away
* - the focus sequence has to be set manually
* - {@link net.sf.cuf.ui.SwingDecorator} is used for initialising the components
*
*/
private void addComponents()
{
// define layout
Container pane = getContentPane();
pane.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.ipadx = 0;
gbc.ipady = 0;
Insets insetsLabel = new Insets(2, 4, 2, 4);
Insets insetsCombo = new Insets(2, 4, 2, 4);
Insets insetsRadio = new Insets(2, 10, 2, 4);
Insets insetsButtons = new Insets(15, 4, 2, 4);
// add combo boxes to choose selection criteria
mCriteria = new JComboBox [CRITERIA_COUNT];
mAscending = new JRadioButton[CRITERIA_COUNT];
mDescending = new JRadioButton[CRITERIA_COUNT];
for (int i = 0; i < CRITERIA_COUNT; i++)
{
JLabel l = new JLabel();
SwingDecorator.initialize(l,"TABLESORT_DIALOG_CRITERIA" + (i + 1));
gbc.gridx = 0;
gbc.insets = insetsLabel;
pane.add(l, gbc);
mCriteria[i] = new JComboBox(); // combo box model will be set later
SwingDecorator.initialize(mCriteria[i], "TABLESORT_DIALOG_CRITERIAINPUT" + (i + 1)); // set tool tip
mCriteria[i].unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); // let the ENTER key trigger the default button
gbc.gridx = 1;
gbc.insets = insetsCombo;
pane.add(mCriteria[i], gbc);
Box box = Box.createVerticalBox();
gbc.gridx = 2;
gbc.insets = insetsRadio;
pane.add(box, gbc);
ButtonGroup bg = new ButtonGroup();
mAscending[i] = new JRadioButton();
SwingDecorator.initialize(mAscending[i], "TABLESORT_DIALOG_ASCENDING");
box.add(mAscending[i]);
bg.add(mAscending[i]);
box.add(Box.createVerticalStrut(-3)); // reduce gap between the radio buttons; if they get any nearer the focus frame will be cut off unter Metal L&F
mDescending[i] = new JRadioButton();
SwingDecorator.initialize(mDescending[i], "TABLESORT_DIALOG_DESCENDING");
box.add(mDescending[i]);
bg.add(mDescending[i]);
gbc.gridy++;
}
// add OK and Cancel buttons
gbc.gridx = 0;
gbc.gridwidth = 3;
gbc.anchor = GridBagConstraints.SOUTHEAST;
gbc.insets = insetsButtons;
pane.add(createOkCancelButtons(), gbc);
// resolve conflicts between default button and combo boxes
JRootPane root = getRootPane();
KeyStroke k1 = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);
KeyStroke k2 = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
ActionListener a1 = root.getActionForKeyStroke(k1);
ActionListener a2 = root.getActionForKeyStroke(k2);
root.unregisterKeyboardAction(k1);
root.unregisterKeyboardAction(k2);
root.registerKeyboardAction(new FilteredAction(a1), k1, JComponent.WHEN_IN_FOCUSED_WINDOW);
root.registerKeyboardAction(new FilteredAction(a2), k2, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
/**
* Show the modal dialog.
* This method blocks until all the user closes the dialog.
*
* We need a reference to the table in order to determine the visible
* columns and to convert between model and view column indizes.
* @param pTable current table (must not be null
)
* @param pSortInfo current sorting in the table (must not be null
)
* @return the selected ordering or null
, if user cancelled dialog
*/
public TableSortInfo show(final JTable pTable, final TableSortInfo pSortInfo)
{
// check preconditions
if (pTable == null)
throw new IllegalArgumentException("table must not be null");
if (pSortInfo == null)
throw new IllegalArgumentException("tableSortInfoList must not be null");
// fill lists in combo boxes
Object[] headers = fetchVisibleColumnHeaders(pTable.getColumnModel(), COLUMN_HEADER_OFFSET);
for (int i = 0; i < CRITERIA_COUNT; i++)
{
mCriteria[i].setModel(new DefaultComboBoxModel(headers));
}
// initialize components with respect to current sorting info
initializeComponents(pTable, pSortInfo);
mCriteria[0].requestFocus();
// show dialog
mCurrentTable = pTable;
showDialog(); // will block until hide() gets called.
mCurrentTable = null;
// return the result
return mResult;
}
/**
* Initialize components of dialog with respect to current tableSortInfoList.
* @param pTable current table
* @param pSortInfo current sorting information
*/
private void initializeComponents(final JTable pTable, final TableSortInfo pSortInfo)
{
// Number of Components to initialize
int numInitialize = Math.min(CRITERIA_COUNT, pSortInfo.size());
for (int i = 0; i < CRITERIA_COUNT; i++)
{
int sel = 0;
boolean asc = true;
if ( i < numInitialize ) // we use the above values for any additional sorting criteria in the dialog
{
// we have to convert the model index from the sort info to a view index
// we have to add an offset because the first entries in criteria-ComboBox are empty Strings
sel = pTable.convertColumnIndexToView(pSortInfo.getColumn(i)) + COLUMN_HEADER_OFFSET;
asc = pSortInfo.isAscending(i);
}
mCriteria[i].setSelectedIndex(sel);
mAscending[i].setSelected(asc);
mDescending[i].setSelected(!asc);
}
}
/**
* Close the dialog and extract the result from the user input.
*/
protected void processDialogOk()
{
setVisible(false);
// set result
mResult = new TableSortInfo();
for (int i = 0; i < CRITERIA_COUNT; i++)
{
int selectedIndex = mCriteria[i].getSelectedIndex();
if (selectedIndex >= COLUMN_HEADER_OFFSET)
{
mResult.sortByColumn(mCurrentTable.convertColumnIndexToModel(selectedIndex - COLUMN_HEADER_OFFSET), mAscending[i].isSelected());
}
}
}
/**
* Close the dialog and clear the result.
*/
protected void processDialogCancel()
{
setVisible(false);
// set no result
mResult = null;
}
/**
* Helper class to filter actions.
* It only processes the actions if all popups from the combo boxes are closed.
*
* We use this to filter the activation of the default button with ENTER.
* Otherwise there would be conflicts with the popups of the combo boxes
* which react on the ENTER key as well.
*/
protected class FilteredAction
implements ActionListener
{
/** Action to perform. */
private ActionListener mAction;
/**
* Constructor.
* @param pAction action to perform
*/
public FilteredAction(final ActionListener pAction)
{
this.mAction = pAction;
}
/**
* Perform the filtering on the action.
* @param pEvent action event
*/
public void actionPerformed(final ActionEvent pEvent)
{
if (!isAnyComboBoxPopupVisible())
{
mAction.actionPerformed(pEvent);
}
}
/**
* Determine if any of the combo box popups are open.
* @return true
on any open popup
*/
public boolean isAnyComboBoxPopupVisible()
{
boolean res = false;
for (int i = 0; i < CRITERIA_COUNT; i++)
{
res |= mCriteria[i].isPopupVisible();
}
return res;
}
}
}