alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
/*
 * BaseTable.java
 *
 * Created on 13 October 2003, 12:52
 */

package org.openide.explorer.propertysheet;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Event;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.EventObject;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.plaf.TableUI;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.text.JTextComponent;
import org.openide.util.NbBundle;

/** A base class for property-sheet style tables.  This class handles all of
 * the non-property specific behaviors of the property sheet.  It is not
 * intended for subclassing except by SheetTable - it exists mainly to keep
 * orthagonal code separate and maintainable, and was factored out of the
 * original implementation of SheetTable.  Basically it provides focus
 * handling, row painting and some generic actions used by the property
 * sheet, constituting those customizations to a standard JTable which
 * the property sheet requires.
 *
 * @author  Tim Boudreau
 */
abstract class BaseTable extends JTable implements FocusListener {
    
    /** Action key for the action that will move to the next row via TAB,
     * or to the next focusable component if on the last row */
    protected static final String ACTION_NEXT = "next"; //NOI18N
    /** Action key for the action that will move to the previous row via TAB,
     * or to the next focusable component if on the first row */
    protected static final String ACTION_PREV = "prev"; //NOI18N
    /** Action key for the action that will start editing the cell via the
     * keyboard */
    protected static final String ACTION_INLINE_EDITOR = "invokeInlineEditor";  //NOI18N
    /** Action key for cancelling an edit by pressing escape */
    protected static final String ACTION_CANCEL_EDIT = "cancelEditing";  //NOI18N
    /** Action key for user pressing enter when not in edit mode */
    protected static final String ACTION_ENTER = "enterPressed";  //NOI18N
    /** Action key for up/down focus action */
    protected static final String ACTION_FOCUS_NEXT = "focusNext"; //NOI18N
    /** Listener for drag events on the center line, for resizing columns when
     * there are no headers */
    protected LineDragListener dragListener;
    /** Number of pixels on each side of the column split in which the mouse
     *  cursor should be the resize cursor and mouse events should be
     *  interpreted as initiating a drag. */
    private static final int centerLineFudgeFactor = 3;
    /**A change event for reuse */
    private transient final ChangeEvent chEvent = new ChangeEvent(this);
    /** The list of change listeners */
    private transient List changeListenerList;
    /** Flag which, if true, means that the next call to paint() should trigger
     * calculating the fixed row height based on the font size */
    boolean needCalcRowHeight = true;
    /** Static start-an-edit action shared by all instances */
    protected static Action editAction=null;
    /** Static cancel-an-edit action shared by all instances */
    protected static Action cancelAction=null;
    /** Action which will try to invoke the default button if the table is in
     * a dialog */
    protected static Action enterAction=null;
    
    /** Flag used by addFocusListener to block the UI delegate from adding
     * a focus listener (which will repaint the table incorrectly) */
    private boolean inSetUI=false;
    
    //Variables used by subclasses to track when edit requests start/end,
    //to determine when it's appropriate to repaint.  A sort of reference
    //counting.
    private int editRequests=0;
    private int editorRemoveRequests=0;
    private int editorChangeRequests=0;
    
    /** Creates a new instance of BaseTable. */
    public BaseTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
        super(dm, cm, sm);
        //set single selection mode
        getSelectionModel().setSelectionMode(
        ListSelectionModel.SINGLE_SELECTION);
        
        
        setSurrendersFocusOnKeystroke(true);
        
        setCellSelectionEnabled(false);
        setRowSelectionAllowed(true);
        setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
        
        //See the sources for JTable for what these do
        putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); //NOI18N
        putClientProperty("terminateEditOnFocusLost", PropUtils.psCommitOnFocusLoss ? Boolean.FALSE : Boolean.TRUE); //NOI18N
        
        //create a listener for dragging the grid center line to resize columns
        dragListener = new LineDragListener();
        addMouseListener(dragListener);
        addMouseMotionListener(dragListener);
        
        //If we are not focus cycle root, when an editor is removed, focus
        //will get set to a random component which is usually not the property
        //sheet
        setFocusCycleRoot(true);
        
        enableEvents(AWTEvent.FOCUS_EVENT_MASK); //JDK 1.5 
        if (getClass() != SheetTable.class) {
            throw new NoClassDefFoundError(
            "Only SheetTable may subclass BaseTable, for good reasons"); //NOI18N
        }
    }
    
    /** Initialize keystrokes and actions */
    protected void initKeysAndActions() {
        //Kill off the focus traversal keys.  NavigationAction will find the
        //next/previous components if the keyboard moves the position beyond
        //the ends of the table, and manage the focus thus.
        setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
        Collections.EMPTY_SET);
        setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
        Collections.EMPTY_SET);
        
        //Next two lines do not work using inputmap/actionmap, but do work
        //using the older API.  We will process ENTER to skip to next row,
        //not next cell
        unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
        unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
        Event.SHIFT_MASK));
        
        InputMap imp = getInputMap();
        ActionMap am = getActionMap();
        
        
        //Issue 37919, reinstate support for up/down cycle focus transfer.
        //being focus cycle root mangles this in some dialogs
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | KeyEvent.SHIFT_MASK, false), ACTION_FOCUS_NEXT);
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false), ACTION_FOCUS_NEXT);
        
        Action ctrlTab = new CTRLTabAction();
        am.put(ACTION_FOCUS_NEXT, ctrlTab);
        
        
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), ACTION_NEXT);
        
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
        KeyEvent.SHIFT_DOWN_MASK), ACTION_PREV);
        
        am.put(ACTION_NEXT, new NavigationAction(true));
        am.put(ACTION_PREV, new NavigationAction(false));
        
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),
        ACTION_INLINE_EDITOR); //NOI18N
        am.put(ACTION_INLINE_EDITOR, getEditAction()); //NOI18N
        
        imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
        ACTION_ENTER); //NOI18N
        am.put(ACTION_ENTER, getEnterAction()); //NOI18N
        
        
        InputMap impAncestor = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        
        impAncestor.put(
        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ACTION_CANCEL_EDIT); //NOI18N
        am.put(ACTION_CANCEL_EDIT, new CancelAction()); //NOI18N
        
        impAncestor.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
        ACTION_CANCEL_EDIT);
    }
    
    
    /** Called by the sheet table finalizer */
    protected static final void cleanup() {
        editAction = null;
        cancelAction = null;
        enterAction = null;
    }
    
    /** Overridden to set the flag for recalculating the fixed row height */
    public void setFont(Font f) {
        needCalcRowHeight = true;
        super.setFont(f);
    }
    
    /** Lazily create the edit-on-spacebar action */
    private static Action getEditAction() {
        if (editAction == null) {
            editAction = new EditAction();
        }
        return editAction;
    }
    
    /** Lazily create the cancel-on-escape action */
    /*private static Action getCancelAction() {
        if (cancelAction == null) {
            cancelAction = new CancelAction();
        }
        return cancelAction;
    }*/
    
    private static Action getEnterAction() {
        if (enterAction == null) {
            enterAction = new EnterAction();
        }
        return enterAction;
    }
    
    /** Calculate the height of rows based on the current font.  This is
     *  done when the first paint occurs, to ensure that a valid Graphics
     *  object is available.  */
    private void calcRowHeight(Graphics g) {
        //Users of themes can set an explicit row height, so check for it
        Integer i = (Integer) UIManager.get(PropUtils.KEY_ROWHEIGHT); //NOI18N
        
        int rowHeight;
        if (i != null) {
            rowHeight = i.intValue();
        } else {
            //Derive a row height to accomodate the font and expando icon
            Font f = getFont();
            FontMetrics fm = g.getFontMetrics(f);
            rowHeight = Math.max(fm.getHeight()+3,
            PropUtils.getSpinnerHeight());
        }
        //Clear the flag
        needCalcRowHeight = false;
        //Set row height.  If displayable, this will generate a new call
        //to paint()
        setRowHeight(rowHeight);
    }
    
    protected int getFirstVisibleRow() {
        Insets ins = getInsets();
        return rowAtPoint(new Point(ins.left, ins.top));
    }
    
    protected int getVisibleRowCount() {
        int rowCount = getRowCount();
        int rowHeight = getRowHeight();
        if (rowCount == 0 || rowHeight == 0) {
            return 0;
        }
        if (getParent() instanceof JViewport) {
            JViewport jvp = (JViewport) getParent();
            int result = Math.min (rowCount, jvp.getExtentSize().height / rowHeight);
            return result;
        } else {
            return Math.min(rowCount, getHeight() / rowHeight);
        }
    }
    
    /** Overridden to not allow edits on the names column (0) */
    public boolean isCellEditable(int row, int col) {
        return col != 0;
    }
    
    /** The old window system will force focus back to the table, when an editor
     * becomes visible.  This will cause the combo box to close its popup because
     * it has lost focus, unless we intervene here and make sure focus must be
     * passed directly to the editor if present */
    public final void requestFocus() {
        if (isEditing()) {
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "RequestFocus on table delegating to editor component"); //NOI18N
            }
            editorComp.requestFocus();
        } else {
            if (!inEditorChangeRequest()) {
                if (PropUtils.isLoggable(BaseTable.class)) {
                    PropUtils.log(BaseTable.class, "RequestFocus on table with no editor present"); //NOI18N
                }
                super.requestFocus();
            }
        }
        
    }
    
    /** The old window system will force focus back to the table, when an editor
     * becomes visible.  This will cause the combo box to close its popup because
     * it has lost focus, unless we intervene here and make sure focus must be
     * passed directly to the editor if present */
    public final boolean requestFocusInWindow() {
        if (isEditing()) {
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "RequestFocusInWindow on table delegating to editor"); //NOI18N
            }
            return editorComp.requestFocusInWindow();
        } else {
            if (!inEditorChangeRequest()) {
                if (PropUtils.isLoggable(BaseTable.class)) {
                    PropUtils.log(BaseTable.class, "RequestFocusInWindow on table with no editor present"); //NOI18N
                }
                boolean result = super.requestFocusInWindow();
                if (PropUtils.isLoggable(BaseTable.class)) {
                    PropUtils.log(BaseTable.class, "  RequestFocusInWindow result " + result); //NOI18N
                }
                return result;
            } else {
                return false;
            }
        }
    }
    
    /** Overridden to remove the editor before editing, so a new edit
     * can be started in a single click, to set the selection before editing,
     * so that the editor will be painted with the selection color, and
     * to request focus on the editor component */
    public boolean editCellAt(int row, int col, EventObject e) {
        enterEditRequest();
        if (e instanceof MouseEvent) {
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "editCellAt " + row + "," + col + " triggered by mouse event"); //NOI18N
            }
            //Ensure that the we end up being the focus owner.  In the case
            //of the radio button editor, focus can remain with the previous
            //focus owner
            Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
            if (focusOwner != this) {
                if (!requestFocusInWindow()) {
                    requestFocus();
                }
            }
        } else {
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "editCellAt " + row + "," + col + " triggered by (null = kbd evt)" + e); //NOI18N
            }
        }
        
        boolean wasEditing = isEditing();
        
        //Cancel any current edit.  By default, if you click a cell in
        //a JTable while another cell is being edited, it will change
        //the selection and stop the edit, but it will not initiate a
        //new edit.  So we need to be sure that we are not editing by
        //the time super.editCellAt is called
        if (wasEditing) {
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "  was already editing, removing the editor"); //NOI18N
            }
            removeEditor();
        }
        
        //Update the selection first - we want to change this now, so the
        //row will be painted correctly, rather than when
        //TableCellEditor.shouldSelectCell is called
        
        int prevSel = getSelectedRow();
        changeSelection(row, col, false, false);
        
        boolean result = false;
        //Set a flag - we'll want to behave slightly differently in terms
        //of repaints if we're going from editing -> editing - there's no
        //need for an update to reflect a non-editing state
        final boolean editorChange = wasEditing && isCellEditable(row,col);
        if (editorChange) {
            enterEditorChangeRequest();
        }
        
        try {
            //Do the super call to really start the edit
            
            result = super.editCellAt(row,col,e);
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, "  Result of super.editCellAt is " + result); //NOI18N
            }
            
            //For the sake of the radio button editor, these paints really
            //need to be done synchronously - the editor will be added,
            //painted, handed a mouse event, process it, fire an event and
            //be removed before the next event on the event queue gets handled
            //            paintRow(prevSel);
            //            paintSelectionRow();
            
            //JTable will not set focus to the editor by default, so we
            //need to or it will never get focus when invoked by the
            //keyboard
            if (editorComp != null) {
                Component c =
                KeyboardFocusManager.getCurrentKeyboardFocusManager().
                getFocusOwner();
                
                //If we don't know what has focus, then it's not the editor,
                //so grab focus
                if (!isKnownComponent(c)) {
                    //                    System.out.println("Requesting focus for component");
                    //                    editorComp.requestFocusInWindow();
                }
                
                //Add ourselves as a focus listener to the component
                editorComp.addFocusListener(this);
            }
        } finally {
            //Reset the flags no matter what happened
            try {
                //in its own try-catch in case of assertion failure
                exitEditRequest();
            } finally {
                if (editorChange) {
                    exitEditorChangeRequest();
                }
            }
        }
        return result;
    }
    
    /** Called when an edit request is received, to indicate that some
     * repaints should be blocked while previous editors are removed,
     * selection is changed, etc. */
    protected final void enterEditRequest() {
        editRequests++;
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, " entering edit request"); //NOI18N
        }
    }
    
    protected final void enterEditorRemoveRequest() {
        editorRemoveRequests++;
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, " entering editor remove request"); //NOI18N
        }
    }
    
    protected final void enterEditorChangeRequest() {
        editorChangeRequests++;
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, " entering editor change request"); //NOI18N
        }
    }
    
    protected final void exitEditRequest() {
        editRequests--;
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, " exiting edit change request"); //NOI18N
        }
        assert editRequests >= 0;
    }
    
    protected final void exitEditorRemoveRequest() {
        editorRemoveRequests--;
        PropUtils.log(BaseTable.class, " exiting editor remove request"); //NOI18N
        assert editorRemoveRequests >= 0;
    }
    
    protected final void exitEditorChangeRequest() {
        editorChangeRequests--;
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, " exiting editor change request"); //NOI18N
        }
        assert editorRemoveRequests >= 0;
    }
    
    protected final boolean inEditRequest() {
        return editRequests > 0;
    }
    
    protected final boolean inEditorChangeRequest() {
        return editorChangeRequests > 0;
    }
    
    protected final boolean inEditorRemoveRequest() {
        return editorRemoveRequests > 0;
    }
    
    /** Overridden to set the colors apropriately - we always want the editor
     * to appear selected */
    public Component prepareEditor(TableCellEditor editor, int row, int col) {
        Component result = editor.getTableCellEditorComponent(
        this, getValueAt(row, col), false, row, col);
        
        if (result != null) {
            result.setBackground(getSelectionBackground());
            result.setForeground(getSelectionForeground());
            result.setFont(getFont());
        }
        return result;
    }
    
    /** Overridden to hide the selection when not focused, and paint across the
     * selected row if focused. */
    public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
        Object value = getValueAt(row, col);
        
        Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
        
        boolean isSelected = isSelected(row, focusOwner);
        
        Component result = renderer.getTableCellRendererComponent(this, value,
        isSelected, false,
        row, col);
        return result;
    }
    
    /** Determines if the row should be painted as if it were selected.  This
     * is overridden by SheetTable to also check if the focused component is
     * known to the current inplace editor, if any */
    protected boolean isSelected(int row, Component focusOwner) {
        return (getSelectedRow() == row || (editingRow == row && !inEditorRemoveRequest())) && (hasFocus() ||
        isKnownComponent(focusOwner) || inEditRequest());
    }
    
    public void setUI(TableUI ui) {
        needCalcRowHeight = true;
        inSetUI=true;
        super.setUI(ui);
        inSetUI=false;
    }
    
    /** Overridden to not allow the UI to install a focus listener.  Reason:
     * This focus listener is installed to repaint on focus loss, but it will
     * only repaint the selected *cell*.  Since we don't differentiate selecting
     * only a cell, we need to repaint the entire row that is selected, which
     * we will do from processFocusEvent() */
    public void addFocusListener(FocusListener fl) {
        if (!inSetUI) {
            super.addFocusListener(fl);
        }
    }
    
    public void updateUI() {
        super.updateUI();
        //Initialize keys and actions after updateUI; the UI will overwrite
        //arrow key actions if this is done in the constructor
        initKeysAndActions();
    }
    
    /** Paint the table.  After the super.paint() call, calls paintMargin() to fill
     *  in the left edge with the appropriate color, and then calls paintExpandableSets()
     *  to paint the property sets, which are not painted by the default painting
     *  methods because they need to be painted across two rows.    */
    public void paint(Graphics g) {
        if (needCalcRowHeight) {
            calcRowHeight(g);
            return;
        }
        super.paint(g);
    }
    
    protected void paintRow(int row) {
        if (row == -1) {
            return;
        }
        Rectangle dirtyRect = getCellRect(row, 0, false);
        dirtyRect.x = 0;
        dirtyRect.width = getWidth();
        repaint(dirtyRect);
    }
    
    /** Our own painting code for the selection row - normally the UI delegate
     * would do this, but we specifically block it from adding a focus listener
     * and do it ourselves, since when focus changes, we need to repaint both
     * rows, not just the selected cell.  */
    protected void paintSelectionRow() {
        paintRow(getSelectedRow());
    }
    
    /** Overridden to add the entire row that was being edited to RepaintManager
     * as a dirty region */
    public void removeEditor() {
        enterEditorRemoveRequest();
        try {
            int i = editingRow;
            if (editorComp != null) {
                editorComp.removeFocusListener(this);
            }
            if (PropUtils.isLoggable(BaseTable.class)) {
                PropUtils.log(BaseTable.class, " removing editor"); //NOI18N
            }
            
            super.removeEditor();
            if (i != -1) {
                //Do schedule a repaint for the row just in case
                paintRow(i);
            }
        } finally {
            exitEditorRemoveRequest();
        }
    }
    
    /** Overridden - JTable's implementation of the method will
     *  actually attach (and leave behind) a gratuitous border
     *  on the enclosing scroll pane. */
    protected final void configureEnclosingScrollPane() {
        Container p = getParent();
        if (p instanceof JViewport) {
            Container gp = p.getParent();
            if (gp instanceof JScrollPane) {
                JScrollPane scrollPane = (JScrollPane)gp;
                JViewport viewport = scrollPane.getViewport();
                if (viewport == null || viewport.getView() != this) {
                    return;
                }
                scrollPane.setColumnHeaderView(getTableHeader());
            }
        }
    }
    
    /** Returns true if the passed X axis pixel position is within the
     *  bounds where a drag can be initiated to resize columns */
    protected final boolean onCenterLine(int pos) {
        int line = getColumnModel().getColumn(0).getWidth();
        return (pos > line - centerLineFudgeFactor) &&
        (pos < line + centerLineFudgeFactor);
    }
    
    /** Returns true if the passed event occured within the
     *  bounds where a drag can be initiated to resize columns */
    protected final boolean onCenterLine(MouseEvent me) {
        int pos = me.getPoint().x;
        return (onCenterLine(pos));
    }
    
    /** Overridden to not change the selection if the user is currently
     * dragging the center line */
    public void changeSelection(int row, int column, boolean toggle, boolean extend) {
        //DragListener can be null, because changeSelection is called in
        //superclass constructor
        if (dragListener != null && dragListener.isArmed()) {
            return;
        }
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, "ChangeSelection to " + row + "," + column); //NOI18N
        }
        super.changeSelection(row, column, toggle, extend);
        fireChange();
    }
    
    /** This method exists to support experimental support for commit-on-focus-loss
     * if NetBeans is started with a specific line switch - SheetTable overrides
     * this method to stop cell editing if the flag is true. */
    protected void focusLostCancel() {
        removeEditor();
    }
    
    /** Overridden to remove the editor on focus lost */
    public void processFocusEvent(FocusEvent fe) {
        super.processFocusEvent(fe);
        if (PropUtils.isLoggable(BaseTable.class)) {
            PropUtils.log(BaseTable.class, "processFocusEvent - "); //NOI18N
            PropUtils.log(BaseTable.class, fe);
        }
        if (!isAncestorOf(fe.getOppositeComponent()) || fe.getOppositeComponent() == null) {
            if (isEditing() && fe.getID() == fe.FOCUS_LOST) {
                if (PropUtils.isLoggable(BaseTable.class)) {
                    PropUtils.log(BaseTable.class, "ProcessFocusEvent got focus lost to unknown component, removing editor"); //NOI18N
                }
                
                focusLostCancel();
            }
        }
        if (!inEditorRemoveRequest() && !inEditRequest()) { //XXX inEditRequest probably shouldn't be here
            if (fe.getOppositeComponent() == null && fe.getID() == fe.FOCUS_LOST) {
                //ignore the strange focus to null stuff NetBeans does
                return;
            }
            paintSelectionRow();
        } else {
            paintSelectionRow();
        }
    }
    
    private boolean searchArmed = false;
    
    /** Overridden to allow standard keybinding processing of VK_TAB and
     * abort any pending drag operation on the vertical grid. */
    public void processKeyEvent(KeyEvent e) {
        if (dragListener.isArmed()) {
            dragListener.setArmed(false);
        }
        
        boolean suppressDefaultHandling = (searchField != null && searchField.isShowing()) &&
            (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN);
        
        
        //Manually hook in the bindings for tab - does not seem to get called
        //automatically
        if (e.getKeyCode() != e.VK_TAB) {
            if (!suppressDefaultHandling) {
                //Either the search field or the table should handle up/down, not both
                super.processKeyEvent(e);
            }
            if (!e.isConsumed()) {
                if (e.getID() == KeyEvent.KEY_PRESSED && !isEditing()) {
                   int modifiers = e.getModifiers();
                   int keyCode = e.getKeyCode();
                   if ((modifiers > 0 && modifiers != KeyEvent.SHIFT_MASK) || 
                       e.isActionKey()) {
                       return;
                   }
                   char c = e.getKeyChar();
                   if (!Character.isISOControl(c) && keyCode != 
                    KeyEvent.VK_SHIFT && keyCode != KeyEvent.VK_ESCAPE) {
                        searchArmed = true;
                        e.consume();
                   }
                } else if (searchArmed && e.getID() == KeyEvent.KEY_TYPED) {
                    passToSearchField(e);
                    e.consume();
                    searchArmed = false;
                } else {
                    searchArmed = false;
                }
            }
        } else {
            processKeyBinding(KeyStroke.getKeyStroke(
            e.VK_TAB, e.getModifiersEx(),
            e.getID() == e.KEY_RELEASED),  e,
            JComponent.WHEN_FOCUSED, e.getID() == e.KEY_PRESSED);
        }
    }
    
    void passToSearchField(KeyEvent e) {
        //Don't do anything for normal navigation keys
        if (e.getKeyCode() == KeyEvent.VK_TAB || e.getKeyCode() == KeyEvent.VK_ENTER ||
            ((e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) &&
            (searchField == null || !searchField.isShowing()))) {
                
                return;
        }
        if (getRowCount() == 0) {
            return;
        }
        if (searchField == null || !searchField.isShowing()) {
            showSearchField();
            searchField.setText(String.valueOf(e.getKeyChar()));
        }
    }
    
    private transient SearchField searchField = null;
    private transient JPanel searchpanel = null;
    
    private void showSearchField() {
        if (searchField == null) {
            searchField = new SearchField();
            searchpanel = new JPanel();
            JLabel lbl = new JLabel(NbBundle.getMessage(BaseTable.class, 
                "LBL_QUICKSEARCH")); //NOI18N
            searchpanel.setLayout (
                new BoxLayout(searchpanel, BoxLayout.X_AXIS));
            searchpanel.add (lbl);
            searchpanel.add (searchField);
            lbl.setLabelFor(searchField);
            searchpanel.setBorder (BorderFactory.createRaisedBevelBorder());
            lbl.setBorder (BorderFactory.createEmptyBorder (0, 0, 0, 5));
        }
        JComponent dest = getRootPane().getLayeredPane();
        Point loc = new Point(getColumnModel().getColumn(0).getWidth(),
        getRowHeight() / 2);

        loc = SwingUtilities.convertPoint(this, loc, dest);
        int width = getColumnModel().getColumn(1).getWidth();
        int height = getRowHeight() + 5;
        if (width < 120) {
            //too narrow
            width = 160;
            loc.x -= 160;
        }
        
        
        searchpanel.setBounds(loc.x, loc.y, width, height);
        dest.add(searchpanel);
        searchpanel.setVisible(true);
        searchField.requestFocus();
    }
    
    private void hideSearchField() {
        if (searchField == null) {
            return;
        }
        searchpanel.setVisible(false);
        if (searchpanel.getParent() != null) {
            searchpanel.getParent().remove(searchpanel);
        }
        paintSelectionRow();
    }
    
    private class SearchField extends JTextField implements ActionListener, FocusListener {
        private int selectionBeforeLastShow = -1;
        public SearchField() {
            addActionListener(this);
            addFocusListener(this);
            setFont(BaseTable.this.getFont());
        }
        
        public void addNotify() {
            super.addNotify();
            selectionBeforeLastShow = BaseTable.this.getSelectedRow();
        }
        
        public void processKeyEvent(KeyEvent ke) {
            if (!isShowing()) {
                super.processKeyEvent(ke);
                return;
            }
            //override the default handling so that
            //the parent will never receive the escape key and
            //close a modal dialog
            if (ke.getKeyCode() == ke.VK_ESCAPE) {
                //The focus request will hide the field without focus getting
                //lost to somewhere else in the main window first.
                BaseTable.this.changeSelection (selectionBeforeLastShow, 0, false, false);
                BaseTable.this.requestFocus();
                ke.consume();
            } else if (ke.getKeyCode() == ke.VK_UP && ke.getID() == ke.KEY_PRESSED) {
                reverseSearch(getText());
            } else if (ke.getKeyCode() == ke.VK_DOWN && ke.getID() == ke.KEY_PRESSED) {
                forwardSearch(getText());
            } else {
                super.processKeyEvent(ke);
                if (ke.getKeyCode() != ke.VK_UP && ke.getKeyCode() != ke.VK_DOWN) {
                    processSearchText(getText());
                }
            }
        }
        
        public void keyPressed(KeyEvent ke) {
            if (ke.getKeyCode() == ke.VK_ESCAPE) {
                hideSearchField();
                ke.consume();
            }
        }
        
        public void keyReleased(KeyEvent ke) {
            processSearchText(((JTextField) ke.getSource()).getText());
        }
        
        public void actionPerformed(ActionEvent e) {
            processSearchText(((JTextField) e.getSource()).getText());
            //Use the focus request to hide the field, otherwise focus will
            //be sent to Explorer or some random component
            BaseTable.this.requestFocus();
        }
        
        public void focusGained(FocusEvent e) {
            //it will be the first focus gained event, so go select
            //whatever matches the first character
            processSearchText(((JTextField) e.getSource()).getText());
            repaint();
        }
        
        public void focusLost(FocusEvent e) {
            hideSearchField();
        }
        
        private void processSearchText(String txt) {
            if (txt == null || txt.length() == 0) {
                return;
            }
            int max = getRowCount();
            int pos = getSelectedRow();
            if (pos == max - 1 || pos < 0) {
                pos = 0;
            }
            for (int i=0; i < max; i++) {
                boolean match = matchText(BaseTable.this.getValueAt(i, 0), txt);
                if (match) {
                    changeSelection(i, 0, false, false);
                    //Set renderers can overpaint it, so repaint
                    repaint();
                    break;
                }
                if (pos++ == max -1) {
                    pos = 0;
                }
            }
        }

        private void forwardSearch(String txt) {
            if (txt == null || txt.length() == 0) {
                return;
            }
            int max = getRowCount();
            int pos = getSelectedRow()+1;
            if (pos == max - 1 || pos < 0) {
                pos = 0;
            }
            for (int i=pos; i < max; i++) {
                boolean match = matchText(BaseTable.this.getValueAt(i, 0), txt);
                if (match) {
                    changeSelection(i, 0, false, false);
                    //Set renderers can overpaint it, so repaint
                    repaint();
                    break;
                }
            }
        }

        private void reverseSearch(String txt) {
            if (txt == null || txt.length() == 0) {
                return;
            }
            int max = getRowCount();
            int pos = getSelectedRow();
            if (pos < 1) {
                pos = max-1;
            }
            for (int i=pos-1; i >=0; i--) {
                boolean match = matchText(BaseTable.this.getValueAt(i, 0), txt);
                if (match) {
                    changeSelection(i, 0, false, false);
                    //Set renderers can overpaint it, so repaint
                    repaint();
                    break;
                }
            }
        }

    }
    
    /** Called to determine if the search field text matches an object
     * from column 0 (the passed object value).  Subclasses should override
     * to check specific info - the default implementation simply compares
     * value.toString().startsWith(text) */
    protected boolean matchText(Object value, String text) {
        if (value != null) {
            return value.toString().startsWith(text);
        } else {
            return false;
        }
    }


    public boolean isOptimizedDrawingEnabled() {
        if (searchField != null && searchField.isShowing()) {
            return false;
        } else {
            return super.isOptimizedDrawingEnabled();
        }
    }

    public void paintComponent (Graphics g) {
        super.paintComponent(g);

        //Issue 41546 - bad repaint when scrolling
        if (searchField != null && searchField.isVisible()) {
            searchField.repaint();
        }
    }


    /** Overridden to fire a change event on a change in the table, so the
     * property sheet can refresh the displayed description if necessary */
    public void tableChanged(TableModelEvent e) {
        super.tableChanged(e);
        fireChange();
    }
    
    //****************Change listener support ***************
    
    
    /** Registers ChangeListener to receive events.
     * @param listener The listener to register.  */
    public final synchronized void addChangeListener(ChangeListener listener) {
        if (changeListenerList == null ) {
            changeListenerList = new ArrayList();
        }
        changeListenerList.add(listener);
    }
    
    /** Removes ChangeListener from the list of listeners.
     * @param listener The listener to remove. */
    public final synchronized void removeChangeListener(ChangeListener listener) {
        if (changeListenerList != null ) {
            changeListenerList.remove(listener);
        }
    }
    
    /** Notifies all registered listeners about the event.
     */
    void fireChange() {
        List list;
        synchronized (this) {
            if (changeListenerList == null) return;
            list = (List)((ArrayList)changeListenerList).clone();
        }
        for (int i = 0; i < list.size(); i++) {
            ((ChangeListener)list.get(i)).stateChanged(chEvent);
        }
    }
    
    //****************************** Focus listener implementation ************
    protected boolean isKnownComponent(Component c) {
        if (c == null) return false;
        if (c == this) return true;
        if (c == editorComp) return true;
        if (c == searchField) return true;
        if (c == this.getRootPane()) return true;
        if (c instanceof Container && ((Container) c).isAncestorOf(this)) {
            return true;
        }
        if ((editorComp instanceof Container) && ((Container) editorComp).isAncestorOf(c)) return true;
        return false;
    }
    
    public void focusGained(FocusEvent fe) {
        Component c = fe.getOppositeComponent();
        /*
        //handy for debugging
        System.out.println("Focus gained to " + (fe.getComponent().getName() == null ? fe.getComponent().getClass().getName() : fe.getComponent().getName()) + " temporary: " + fe.isTemporary()
        + " from " + (fe.getOppositeComponent() == null ? "null" :
            (fe.getOppositeComponent().getName() == null ? fe.getOppositeComponent().getClass().getName() : fe.getOppositeComponent().getName()))
        );
         */
        PropUtils.log(BaseTable.class, fe);
        
        if (!isKnownComponent(c)) {
            fireChange();
        }
        
        if (!inEditRequest() && !inEditorRemoveRequest() && fe.getComponent()==this) {
            //            System.out.println("Painting due to focus gain " + fe.getComponent());
            //            repaint(0,0,getWidth(),getHeight());
            paintSelectionRow();
        }
    }
    
    //Focus listener implementation
    public void focusLost(FocusEvent fe) {
        if ((dragListener != null) && dragListener.isDragging()) {
            dragListener.abortDrag();
        }
        
        PropUtils.log(BaseTable.class, fe);
        
        //Ignore temporary focus changes, so sloppy focus middle mouse button
        //cut/paste can work
        if (fe.isTemporary()) {
            return;
        }
        Component c = fe.getOppositeComponent();
        if (!isKnownComponent(c)) {
            //            System.err.println("Painting due to focus lost to an unknown component");
            //TerminateEditOnFocusLost does not always work, ensure it
            PropUtils.log(BaseTable.class, " removing editor due to focus change"); //NOI18N
            if (PropUtils.psCommitOnFocusLoss && isEditing() && (fe.getComponent() instanceof InplaceEditor)) {
                getCellEditor().stopCellEditing();
            } else {
                removeEditor();
            }
            //fire a change if focus did not go to null, so the property
            //sheet will display the node name, not the selected property
            if (c != null) {
                fireChange();
            }
            paintSelectionRow();
        }
    }
    
    /** Action to edit via the keyboard */
    private static class EditAction extends AbstractAction {
        public void actionPerformed(ActionEvent ae) {
            JTable jt = (JTable) ae.getSource();
            int row = jt.getSelectedRow();
            int col = jt.getSelectedColumn();
            if (row != -1 && col != -1) {
                if (PropUtils.isLoggable(BaseTable.class)) {
                    PropUtils.log(BaseTable.class, "Starting edit due to key event for row " + row); //NOI18N
                }
                jt.editCellAt(row, 1, null);
                //Focus will be rerouted to the editor via this call:
                jt.requestFocus();
            }
        }
    }
    
    /** Action to cancel an inline editor */
    private class CancelAction extends AbstractAction {
        public void actionPerformed(ActionEvent ae) {
            JTable jt = (JTable) ae.getSource();
            if (jt != null) {
                if (jt.isEditing()) {
                    TableCellEditor tce = jt.getCellEditor();
                    if (PropUtils.isLoggable(BaseTable.class)) {
                        PropUtils.log(BaseTable.class, "Cancelling edit due to keyboard event"); //NOI18N
                    }
                    
                    if (tce != null) {
                        jt.getCellEditor().cancelCellEditing();
                    }
                } else {
                    //If we're in a dialog, try to close it
                    trySendEscToDialog(jt);
                }
            }
        }
        
        public boolean isEnabled() {
            return isEditing();
        }
        
        private void trySendEscToDialog(JTable jt) {
            //        System.err.println("SendEscToDialog");
            EventObject ev = EventQueue.getCurrentEvent();
            if (ev instanceof KeyEvent && ((KeyEvent) ev).getKeyCode() == KeyEvent.VK_ESCAPE) {
                
                if (ev.getSource() instanceof JComboBox && ((JComboBox) ev.getSource()).isPopupVisible()) {
                    return;
                }
                
                if (ev.getSource() instanceof JTextComponent && ((JTextComponent) ev.getSource()).getParent() instanceof JComboBox &&
                ((JComboBox)((JTextComponent) ev.getSource()).getParent()).isPopupVisible()) {
                    return;
                }
                
                InputMap imp = jt.getRootPane().getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
                ActionMap am = jt.getRootPane().getActionMap();
                
                KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
                Object key = imp.get(escape);
                if (key != null) {
                    Action a = am.get(key);
                    if (a != null) {
                        if (Boolean.getBoolean(
                        "netbeans.proppanel.logDialogActions")) { //NOI18N
                            System.err.println(
                            "Action bound to escape key is " + a); //NOI18N
                        }
                        //Actions registered with deprecated registerKeyboardAction will
                        //need this lookup of the action command
                        String commandKey = (String)a.getValue(Action.ACTION_COMMAND_KEY);
                        if (commandKey == null) {
                            commandKey = "cancel"; //NOI18N
                        }
                        a.actionPerformed(new ActionEvent(this,
                        ActionEvent.ACTION_PERFORMED, commandKey)); //NOI18N
                    }
                }
            }
        }
    };
    
    private static class EnterAction extends AbstractAction {
        public void actionPerformed(ActionEvent ae) {
            if (ae.getSource() instanceof BaseTable) {
                BaseTable bt = (BaseTable) ae.getSource();
                if (bt.isEditing()) {
                    return;
                }
                trySendEnterToDialog(bt);
            }
        }
        
        private void trySendEnterToDialog(BaseTable bt) {
            //        System.err.println("SendEnterToDialog");
            EventObject ev = EventQueue.getCurrentEvent();
            if (ev instanceof KeyEvent && ((KeyEvent) ev).getKeyCode() == KeyEvent.VK_ENTER) {
                
                if (ev.getSource() instanceof JComboBox && ((JComboBox) ev.getSource()).isPopupVisible()) {
                    return;
                }
                
                if (ev.getSource() instanceof JTextComponent && ((JTextComponent) ev.getSource()).getParent() instanceof JComboBox &&
                ((JComboBox)((JTextComponent) ev.getSource()).getParent()).isPopupVisible()) {
                    return;
                }
                
                JRootPane jrp = bt.getRootPane();
                if (jrp != null) {
                    JButton b = jrp.getDefaultButton();
                    if (b != null && b.isEnabled()) {
                        b.doClick();
                    }
                }
            }
        }
    }
    
    /** Enables tab keys to navigate between rows */
    private final class NavigationAction extends AbstractAction {
        private boolean direction;
        public NavigationAction(boolean direction) {
            this.direction = direction;
        }
        
        public void actionPerformed(ActionEvent e) {
            int next = getSelectedRow() + (direction ? 1 : -1);
            //if we're off the end, try to find a sibling component to pass
            //focus to
            if (next >= getRowCount() || next < 0) {
                if (!(BaseTable.this.getTopLevelAncestor() instanceof Dialog)) {
                    //If we're not in a dialog, we're in the main window - don't
                    //send focus somewhere because the winsys won't change the
                    //active mode
                    next = next >= getRowCount() ? 0 : getRowCount() - 1;
                } else
                    if (next >= getRowCount() || next < 0) {
                        //if we're off the end, try to find a sibling component to pass
                        //focus to
                        
                        //This code is a bit ugly, but works
                        Container ancestor = getFocusCycleRootAncestor();
                        //Find the next component in our parent's focus cycle
                        Component sibling = direction ?
                        ancestor.getFocusTraversalPolicy().getComponentAfter(ancestor,
                        BaseTable.this.getParent()) :
                            ancestor.getFocusTraversalPolicy().getComponentBefore(ancestor,
                            BaseTable.this);
                            
                            
                            //Often LayoutFocusTranferPolicy will return ourselves if we're
                            //the last.  First try to find a parent focus cycle root that
                            //will be a little more polite
                            if (sibling == BaseTable.this) {
                                Container grandcestor = ancestor.getFocusCycleRootAncestor();
                                if (grandcestor != null) {
                                    sibling = direction ? grandcestor.getFocusTraversalPolicy().getComponentAfter(grandcestor, ancestor) :
                                        grandcestor.getFocusTraversalPolicy().getComponentBefore(grandcestor, ancestor);
                                        ancestor = grandcestor;
                                }
                            }
                            
                            //Okay, we still ended up with ourselves, or there is only one focus
                            //cycle root ancestor.  Try to find the first component according to
                            //the policy
                            if (sibling == BaseTable.this) {
                                if (ancestor.getFocusTraversalPolicy().getFirstComponent(ancestor) != null) {
                                    sibling = ancestor.getFocusTraversalPolicy().getFirstComponent(ancestor);
                                }
                            }
                            
                            //If we're *still* getting ourselves, find the default button and punt
                            if (sibling == BaseTable.this) {
                                JRootPane rp = getRootPane();
                                JButton jb = rp.getDefaultButton();
                                if (jb != null) {
                                    sibling = jb;
                                }
                            }
                            
                            //See if it's us, or something we know about, and if so, just
                            //loop around to the top or bottom row - there's noplace
                            //interesting for focus to go to
                            if (sibling != null) {
                                if (sibling == BaseTable.this) {
                                    //set the selection if there's nothing else to do
                                    changeSelection(direction ? 0 : getRowCount()-1,
                                    direction ? 0 : getColumnCount()-1,false,false);
                                } else {
                                    //Request focus on the sibling
                                    sibling.requestFocus();
                                }
                                return;
                            }
                    }
                changeSelection(next, getSelectedColumn(), false, false);
            }
            getSelectionModel().setLeadSelectionIndex(next);
        }
    }
    
    /** Listener for drag events that should resize columns */
    final class LineDragListener extends MouseAdapter implements
    MouseMotionListener {
        private long dragStartTime = -1;
        boolean armed;
        boolean dragging;
        public void mouseExited(MouseEvent e) {
            setArmed(false);
        }
        
        public void mousePressed(MouseEvent e) {
            if (isArmed() && onCenterLine(e)) {
                beginDrag();
            }
        }
        
        public void mouseReleased(MouseEvent e) {
            if (isDragging()) {
                finishDrag();
                setArmed(false);
            }
        }
        
        public void mouseMoved(MouseEvent e) {
            setArmed(!isEditing() && onCenterLine(e));
        }
        
        int pos = -1;
        public void mouseDragged(MouseEvent e) {
            if (!armed && !dragging) return;
            int newPos = e.getPoint().x;
            TableColumn c0 = getColumnModel().getColumn(0);
            TableColumn c1 = getColumnModel().getColumn(1);
            int min = Math.max(c0.getMinWidth(), getWidth() -
            c1.getMaxWidth());
            int max = Math.min(c0.getMaxWidth(), getWidth() -
            c1.getMinWidth());
            if ((newPos >= min) && (newPos <= max)) {
                pos = newPos;
                update();
            }
        }
        
        public boolean isArmed() {
            return armed;
        }
        
        public boolean isDragging() {
            return dragging;
        }
        
        public void setArmed(boolean val) {
            if (val != armed) {
                this.armed = val;
                if (armed) {
                    BaseTable.this.setCursor(
                    Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
                } else {
                    BaseTable.this.setCursor(
                    Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                }
            }
        }
        
        private void beginDrag() {
            dragging = true;
            dragStartTime = System.currentTimeMillis();
        }
        
        public void abortDrag() {
            dragging = false;
            setArmed(false);
            repaint();
        }
        
        private void finishDrag() {
            dragging=false;
            if ((System.currentTimeMillis() - dragStartTime) < 400) {
                update();
            } else {
                abortDrag();
            }
        }
        
        private void update() {
            if ((pos < 0) || (pos > getWidth())) {
                repaint();
                return;
            }
            int pos0 = pos;
            int pos1 = getWidth() - pos;
            synchronized (getTreeLock()) {
                getColumnModel().getColumn(0).setWidth(pos0);
                getColumnModel().getColumn(1).setWidth(pos1);
                getColumnModel().getColumn(0).setPreferredWidth(pos0);
                getColumnModel().getColumn(1).setPreferredWidth(pos1);
            }
            BaseTable.this.repaint();
        }
    }
    
    private class CTRLTabAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            setFocusCycleRoot(false);
            try {
                Container con = BaseTable.this.getFocusCycleRootAncestor();
                if (con != null) {
                    Component target = BaseTable.this;
                    if (getParent() instanceof JViewport) {
                        target = getParent().getParent();
                        if (target == con) {
                            target = BaseTable.this;
                        }
                    }
                    
                    EventObject eo = EventQueue.getCurrentEvent();
                    boolean backward = false;
                    if (eo instanceof KeyEvent) {
                        backward =
                        (((KeyEvent) eo).getModifiers() & KeyEvent.SHIFT_MASK)
                        != 0 && (((KeyEvent) eo).getModifiersEx()
                        & KeyEvent.SHIFT_DOWN_MASK) != 0;
                    }
                    
                    Component to = backward ?
                    con.getFocusTraversalPolicy().getComponentAfter(
                    con, BaseTable.this)
                    : con.getFocusTraversalPolicy().getComponentAfter(
                    con, BaseTable.this);
                    
                    
                    if (to == BaseTable.this) {
                        to = backward ?
                        con.getFocusTraversalPolicy().getFirstComponent(con) :
                            con.getFocusTraversalPolicy().getLastComponent(con);
                    }
                    to.requestFocus();
                }
            } finally {
                setFocusCycleRoot(true);
            }
        }
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.