001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.KeyboardFocusManager;
005import java.awt.event.ActionEvent;
006import java.awt.event.KeyEvent;
007
008import javax.swing.AbstractAction;
009import javax.swing.JComponent;
010import javax.swing.JTable;
011import javax.swing.KeyStroke;
012import javax.swing.ListSelectionModel;
013import javax.swing.table.TableColumnModel;
014import javax.swing.table.TableModel;
015
016/**
017 * Generic table offering custom cell navigation features.
018 * @since 9497
019 */
020public abstract class JosmTable extends JTable {
021
022    private int colEnd;
023
024    protected SelectNextColumnCellAction selectNextColumnCellAction;
025    protected SelectPreviousColumnCellAction selectPreviousColumnCellAction;
026
027    protected JosmTable(TableModel dm, TableColumnModel cm) {
028        this(dm, cm, null);
029    }
030
031    protected JosmTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
032        super(dm, cm, sm);
033    }
034
035    protected void installCustomNavigation(int colEnd) {
036        // make ENTER behave like TAB
037        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
038                KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
039
040        // install custom navigation actions
041        this.colEnd = colEnd;
042        selectNextColumnCellAction = new SelectNextColumnCellAction();
043        selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
044        getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
045        getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
046    }
047
048    /**
049     * Action to be run when the user navigates to the next cell in the table, for instance by
050     * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
051     * <li>it jumps over cells in the first column</li>
052     * <li>it automatically add a new empty row when the user leaves the last cell in the table</li></ul>
053     */
054    protected class SelectNextColumnCellAction extends AbstractAction {
055        @Override
056        public void actionPerformed(ActionEvent e) {
057            int col = getSelectedColumn();
058            int row = getSelectedRow();
059            if (getCellEditor() != null) {
060                getCellEditor().stopCellEditing();
061            }
062
063            if (col == colEnd && row < getRowCount() - 1) {
064                row++;
065            } else if (row < getRowCount() - 1) {
066                col = colEnd;
067                row++;
068            } else {
069                // go to next component, no more rows in this table
070                KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
071                manager.focusNextComponent();
072                return;
073            }
074            changeSelection(row, col, false, false);
075            if (editCellAt(getSelectedRow(), getSelectedColumn())) {
076                getEditorComponent().requestFocusInWindow();
077            }
078        }
079    }
080
081    /**
082     * Action to be run when the user navigates to the previous cell in the table, for instance by
083     * pressing Shift-TAB
084     */
085    protected class SelectPreviousColumnCellAction extends AbstractAction {
086
087        @Override
088        public void actionPerformed(ActionEvent e) {
089            int col = getSelectedColumn();
090            int row = getSelectedRow();
091            if (getCellEditor() != null) {
092                getCellEditor().stopCellEditing();
093            }
094
095            if (col <= 0 && row <= 0) {
096                // change nothing
097            } else if (row > 0) {
098                col = colEnd;
099                row--;
100            }
101            changeSelection(row, col, false, false);
102            if (editCellAt(getSelectedRow(), getSelectedColumn())) {
103                getEditorComponent().requestFocusInWindow();
104            }
105        }
106    }
107}