swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/JXTableHeaderIssues.java
/*
 * Created on 28.06.2006
 *
 */
package org.jdesktop.swingx;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SortOrder;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.metal.MetalBorders;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
import org.jdesktop.swingx.table.TableColumnExt;
import org.jdesktop.test.AncientSwingTeam;

public class JXTableHeaderIssues extends JXTableHeaderTest {
    @SuppressWarnings("all")
    private static final Logger LOG = Logger
            .getLogger(JXTableHeaderIssues.class.getName());
    public static void main(String args[]) {
        
        JXTableHeaderIssues test = new JXTableHeaderIssues();
//        setSystemLF(true);
        try {
          test.runInteractiveTests();
         //   test.runInteractiveTests("interactive.*Siz.*");
         //   test.runInteractiveTests("interactive.*Render.*");
//            test.runInteractiveTests("interactive.*Auto.*");
        } catch (Exception e) {
            System.err.println("exception when executing interactive tests:");
            e.printStackTrace();
        } 
    }
    
    
    /**
     * Issue #683-swingx: autoscroll if columns are dragged outside.
     * 
     * Enabled if autoscroll property is true. Changed default to true.
     * Temporarily disabled - clashes with table's horizontalScrollEnabled.
     */
    public void testAutoscrollsDefaultEmptyConstructor() {
        JXTableHeader header = new JXTableHeader();
        assertTrue(header.getAutoscrolls());
    }
    
    /**
     * Issue #1154-swingx: Regression after switching to Mustang.
     * was: Issue #31 (swingx): clicking header must not sort if table !enabled.
     *
     * OOOKayyyy ... this is a core issue: mouse listener in BasicTableHeaderUI
     * don't respect the header's enabled property (reported with review id 1605216).
     * It's possible to drag, resize, sort even if disabled. The only "normal"
     * behaviour is that the resizing cursor isn't showing.
     */
    public void interactiveTestDisabledTableSorting() {
        final JXTable table = new JXTable(new AncientSwingTeam());
        table.setEnabled(false);
        table.setColumnControlVisible(true);
        Action toggleAction = new AbstractAction("Toggle Enabled") {

            public void actionPerformed(ActionEvent e) {
                table.setEnabled(!table.isEnabled());
                
            }
            
        };
        JXFrame frame = wrapWithScrollingInFrame(table, "Disabled tabled: no sorting");
        addAction(frame, toggleAction);
        frame.setVisible(true);  
    }
    
    /**
     * Quick proof-of-concept: JXTableHeader can go dirty and
     * suppress moving of "docked" column.
     *
     */
    public void interactiveDockColumn() {
        final JXTableHeader header = new JXTableHeader() {

            private int oldDistance;


            @Override
            public void setDraggedDistance(int distance) {
                oldDistance = getDraggedDistance();
                if (isDocked(getDraggedColumn())) {
                    distance = 0;
                } 
                if (isDraggedOverDocked(distance)) {
                    distance = 0;
                    setDraggedColumn(null);
                }
                super.setDraggedDistance(distance);
            }

            private boolean isDraggedOverDocked(int distance) {
                if (getDraggedColumn() == null) return false;
                int dragPosition = getDragX(distance);
                int columnUnder = columnAtPoint(new Point(dragPosition, 0));
                if (columnUnder >= 0) {
                    return  isDocked(getColumnModel().getColumn(columnUnder));
                }
                return false;
            }
            
            private int getDragX(int distance) {
                DefaultTableColumnModelExt columnModel = (DefaultTableColumnModelExt) getColumnModel();
                List<TableColumn> columns = columnModel.getColumns(false);
                int startX = 0;
                for (int i = 0; i < columns.size(); i++) {
                    if (columns.get(i) == getDraggedColumn()) {
                        // at the wrong column the very moment 
                        // before the dock neighbour is 
                        // actually replaced with the second next dock neighbour
                        // need to add the stop-column width as well
                        if (distance * oldDistance < 0) {
                            startX += columns.get(i).getWidth();
                        }
                         break;
                    }
                    startX += columns.get(i).getWidth();
                }
                return startX + distance;
            }

            
            private boolean isDocked(TableColumn column) {
                if (column instanceof TableColumnExt) {
                    return Boolean.TRUE.equals(((TableColumnExt) column).getClientProperty("docked"));
                }
                return false;
            }
        };
        final TableColumnModel cModel = new DefaultTableColumnModelExt() {

            @Override
            public void moveColumn(int columnIndex, int newIndex) {
                if (isDocked(columnIndex) || isDocked(newIndex)) return;
                super.moveColumn(columnIndex, newIndex);
            }

            private boolean isDocked(int columnIndex) {
                TableColumnExt column = getColumnExt(columnIndex);
                if (column != null) {
                    return Boolean.TRUE.equals(column.getClientProperty("docked"));
                }
                return false;
            }
            
        };
        JXTable table = new JXTable(10, 5) {

            @Override
            protected JTableHeader createDefaultTableHeader() {
                header.setColumnModel(columnModel);
                return header;
            }

            @Override
            protected TableColumnModel createDefaultColumnModel() {
                return cModel;
            }
            
            
        };
        table.getColumnExt(0).putClientProperty("docked", Boolean.TRUE);
        showWithScrollingInFrame(table, "block dragging");
    }

    /**
     * Issue #281-swingx, Issue #334-swing: 
     * header should be auto-repainted on changes to
     * header title, value. Must update size if appropriate.
     * 
     * still not solved: core #4292511 - autowrapping is weird,
     * even with the swingx fix 
     *
     */
    public void interactiveUpdateHeaderAndSizeRequirements() {
        
        final String[] alternate = { 
//                "simple", 
                "<html><b>This is a test of a large label to see if it wraps </font></b>",
                "simple", 
                //                "<html><center>Line 1<br>Line 2</center></html>" 
                };
        final JXTable table = new JXTable(10, 2);
        table.getColumn(0).setHeaderValue(alternate[0]);
        table.getColumn(1).setHeaderValue(alternate[1]);
        
        JXFrame frame = wrapWithScrollingInFrame(table, "update header");
        Action action = new AbstractAction("update headervalue") {
            boolean first;
            public void actionPerformed(ActionEvent e) {
                table.getColumn(1).setHeaderValue(first ? alternate[0] : alternate[1]);
                first = !first;
                
            }
            
        };
        addAction(frame, action);
        frame.setVisible(true);
        
    }

    /**
     * Issue 337-swingx: header heigth depends on sort icon (for ocean only?) 
     * Looks like a problem in MetalBorders.TableHeaderBorder: extends AbstractBorder but
     * does not override getBorderInsets(comp, insets) which is used by the labelUI getPrefSize
     * to determine the insets and calc the view rect.
     * 
     */
    @SuppressWarnings("unused")
    public void interactiveSortedPreferredHeight() {
        final JXTable table = new JXTable(10, 1);
        table.getColumnExt(0).setPreferredWidth(200);
        final JTable other = new JTable(10, 1);
//        other.setAutoCreateRowSorter(true);
        other.getColumnModel().getColumn(0).setPreferredWidth(200);
        final DefaultTableCellRenderer renderer =(DefaultTableCellRenderer) other.getTableHeader().getDefaultRenderer(); 
        Component comp = renderer.getTableCellRendererComponent(other, "A", false, false, -1, 0);
        final Border border = renderer.getBorder();
        final Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 20);
        TableCellRenderer wrapper = new TableCellRenderer() {

            public Component getTableCellRendererComponent(JTable mtable,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                JComponent comp = (JComponent) renderer.getTableCellRendererComponent(mtable, value, 
                        isSelected, hasFocus, row, column);
                if (table.getSortedColumn() == null) {
                    comp.setBorder(border);
                } else {
                    comp.setBorder(new CompoundBorder(border, emptyBorder));
                }
                return comp;
            }
            
        };
        other.getTableHeader().setDefaultRenderer(wrapper);
        Action action = new AbstractActionExt("toggle sorter order") {

            public void actionPerformed(ActionEvent e) {
                if (table.getSortedColumn() == null) {
                    table.toggleSortOrder(0);
                } else {
                    table.resetSortOrder();
                }
                table.getTableHeader().revalidate();
                other.getTableHeader().revalidate();
                
            }
        };
        JXFrame frame = wrapWithScrollingInFrame(table, other, "xheader <--> header border height");
        addAction(frame, action);
        frame.setVisible(true);
    }


    /**
     * Issue #271-swingx: make sort triggering mouseEvents
     * customizable.
     * 
     * added SortGestureRecognizer.
     * 
     * No longer supported - need to re-think (maybe could push core addition?)
     * Anyway, re-opened the old issue.
     *
     */
//    public void interactiveSortGestureRecognizer() {
//        final JXTable table = new JXTable(10, 2);
//        JXFrame frame = wrapWithScrollingInFrame(table, "Sort Gesture customization");
//        Action action = new AbstractAction("toggle default/custom recognizer") {
//            boolean hasCustom;
//            public void actionPerformed(ActionEvent e) {
//                SortGestureRecognizer recognizer = null;
//                if (!hasCustom) {
//                    hasCustom = !hasCustom;
//                    recognizer = new SortGestureRecognizer() {
//                        /**
//                         * allow double clicks to trigger a sort.
//                         */
//                        @Override
//                        public boolean isSortOrderGesture(MouseEvent e) {
//                            return e.getClickCount() <= 2;
//                        }
//
//                        /**
//                         * Disable reset gesture.
//                         */
//                        @Override
//                        public boolean isResetSortOrderGesture(MouseEvent e) {
//                            return false;
//                        }
//
//                        /**
//                         * ignore modifiers.
//                         */
//                        @Override
//                        public boolean isToggleSortOrderGesture(MouseEvent e) {
//                            return isSortOrderGesture(e);
//                        }
//                        
//                        
//                        
//                    };
//                }
//                ((JXTableHeader) table.getTableHeader()).setSortGestureRecognizer(recognizer);
//                
//            }
//            
//        };
//        addAction(frame, action);
//        frame.setVisible(true);
//        
//    }

    

    /**
     * Issue 337-swingx: header heigth depends on sort icon (for ocean only?) 
     * Looks like a problem in MetalBorders.TableHeaderBorder: extends AbstractBorder but
     * does not override getBorderInsets(comp, insets) which is used by the labelUI getPrefSize
     * to determine the insets and calc the view rect.
     * 
     */
    public void testMetalBorderInsets() {
        JLabel label = new JLabel("sometext");
        AbstractBorder metalBorder = new MetalBorders.TableHeaderBorder();
        assertEquals(metalBorder.getBorderInsets(label), 
                metalBorder.getBorderInsets(label, new Insets(0,0,0,0)));
    }
    
    /**
     * Issue 337-swingx: header heigth depends on sort icon (for ocean only?) 
     * Looks like a problem in MetalBorders.TableHeaderBorder: extends AbstractBorder but
     * does not override getBorderInsets(comp, insets) which is used by the labelUI getPrefSize
     * to determine the insets and calc the view rect.
     * 
     * Here we compound the default metal border
     */
    public void testMetalBorderInsetsHack() {
        JLabel label = new JLabel("sometext");
        AbstractBorder metalBorder = new MetalBorders.TableHeaderBorder();
        CompoundBorder compound = new CompoundBorder(metalBorder, BorderFactory.createEmptyBorder());
        assertEquals(compound.getBorderInsets(label), 
                compound.getBorderInsets(label, new Insets(0,0,0,0)));
    }
    
    /**
     * Issue 337-swingx: header heigth depends on sort icon (for ocean only?) 
     * NOTE: this seems to be independent of the tweaks to xTableHeaders
     *   prefSize.
     */
    public void testSortedPreferredHeight() {
        JXTable table = new JXTable(10, 2);
        JXTableHeader tableHeader = (JXTableHeader) table.getTableHeader();
        Dimension dim = tableHeader.getPreferredSize();
        table.setSortOrder(0, SortOrder.ASCENDING);
        assertEquals("Header pref height must be unchanged if sorted",
                dim.height, tableHeader.getPreferredSize().height);
    }

}
