content: /* 
   * This software is provided under the terms 
   * of the GNU Lesser General Public License, Version 2.1. You may not use 
   * this file except in compliance with the license. If you need a copy of the license, 
   * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Pentaho 
   * Data Integration.  The Initial Developer is Pentaho Corporation.
   *
   * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
   * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
   * the license for the specific language governing your rights and limitations.*/
  
  /*
   * VersionPackageConstraint
   * Copyright (c) 2006 Elliot Hughes 
   */
  
  package weka.gui;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.event.MouseEvent;
  
  import javax.swing.BorderFactory;
  import javax.swing.JCheckBox;
  import javax.swing.JComponent;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTable;
  import javax.swing.JViewport;
  import javax.swing.SwingConstants;
  import javax.swing.UIManager;
  import javax.swing.border.Border;
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableCellRenderer;
  import javax.swing.table.TableColumn;
  import javax.swing.table.TableColumnModel;
  
  /**
   * A better-looking table than JTable. In particular, on Mac OS this looks
   * more like a Cocoa table than the default Aqua LAF manages.
   *
   * @author Elliott Hughes
   */
  public class ETable extends JTable {
    /**
     * For serialization
     */
    private static final long serialVersionUID = -3028630226368293049L;
    
    private final Color MAC_FOCUSED_SELECTED_CELL_HORIZONTAL_LINE_COLOR = new Color(0x7daaea);
    private final Color MAC_UNFOCUSED_SELECTED_CELL_HORIZONTAL_LINE_COLOR = new Color(0xe0e0e0);
  
    private final Color MAC_UNFOCUSED_SELECTED_CELL_BACKGROUND_COLOR = new Color(0xc0c0c0);
  
    private final Color MAC_FOCUSED_UNSELECTED_VERTICAL_LINE_COLOR = new Color(0xd9d9d9);
    private final Color MAC_FOCUSED_SELECTED_VERTICAL_LINE_COLOR = new Color(0x346dbe);
    private final Color MAC_UNFOCUSED_UNSELECTED_VERTICAL_LINE_COLOR = new Color(0xd9d9d9);
    private final Color MAC_UNFOCUSED_SELECTED_VERTICAL_LINE_COLOR = new Color(0xacacac);
  
    private final Color MAC_OS_ALTERNATE_ROW_COLOR = new Color(0.92f, 0.95f, 0.99f);
  
    public ETable() {
      // Although it's the JTable default, most systems' tables don't draw a grid by default.
      // Worse, it's not easy (or possible?) for us to take over grid painting ourselves for those LAFs (Metal, for example) that do paint grids.
      // The Aqua and GTK LAFs ignore the grid settings anyway, so this causes no change there.
      setShowGrid(false);
  
      // Tighten the cells up, and enable the manual painting of the vertical grid lines.
      setIntercellSpacing(new Dimension());
  
      // Table column re-ordering is too badly implemented to enable.
      getTableHeader().setReorderingAllowed(false);
  
      if (System.getProperty("os.name").contains("Mac")) {
        // Work-around for Apple 4352937.
        JLabel.class.cast(getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.LEADING);
  
        // Use an iTunes-style vertical-only "grid".
        setShowHorizontalLines(false);
        setShowVerticalLines(true);
      }
    }
  
    /**
     * Paints empty rows too, after letting the UI delegate do
     * its painting.
     */
    public void paint(Graphics g) {
      super.paint(g);
      paintEmptyRows(g);
    }
  
    /**
     * Paints the backgrounds of the implied empty rows when the
     * table model is insufficient to fill all the visible area
     * available to us. We don't involve cell renderers, because
     * we have no data.
     */
    protected void paintEmptyRows(Graphics g) {
      final int rowCount = getRowCount();
      final Rectangle clip = g.getClipBounds();
      final int height = clip.y + clip.height;
      if (rowCount * rowHeight < height) {
        for (int i = rowCount; i <= height/rowHeight; ++i) {
          g.setColor(colorForRow(i));
          g.fillRect(clip.x, i * rowHeight, clip.width, rowHeight);
        }
  
        // Mac OS' Aqua LAF never draws vertical grid lines, so we have to draw them ourselves.
        if (System.getProperty("os.name").contains("Mac") && getShowVerticalLines()) {
          g.setColor(MAC_UNFOCUSED_UNSELECTED_VERTICAL_LINE_COLOR);
          TableColumnModel columnModel = getColumnModel();
          int x = 0;
          for (int i = 0; i < columnModel.getColumnCount(); ++i) {
            TableColumn column = columnModel.getColumn(i);
            x += column.getWidth();
            g.drawLine(x - 1, rowCount * rowHeight, x - 1, height);
          }
        }
      }
    }
  
    /**
     * Changes the behavior of a table in a JScrollPane to be more like
     * the behavior of JList, which expands to fill the available space.
     * JTable normally restricts its size to just what's needed by its
     * model.
     */
    public boolean getScrollableTracksViewportHeight() {
      if (getParent() instanceof JViewport) {
        JViewport parent = (JViewport) getParent();
        return (parent.getHeight() > getPreferredSize().height);
      }
      return false;
    }
  
    /**
     * Returns the appropriate background color for the given row.
     */
    protected Color colorForRow(int row) {
      return (row % 2 == 0) ? alternateRowColor() : getBackground();
    }
  
    private Color alternateRowColor() {
      return UIManager.getLookAndFeel().getClass().getName().contains("GTK") ? Color.WHITE : MAC_OS_ALTERNATE_ROW_COLOR;
    }
  
    /**
     * Shades alternate rows in different colors.
     */
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
      Component c = super.prepareRenderer(renderer, row, column);
      boolean focused = hasFocus();
      boolean selected = isCellSelected(row, column);
      if (selected) {
        if (System.getProperty("os.name").contains("Mac") && focused == false) {
          // Native Mac OS renders the selection differently if the table doesn't have the focus.
          // The Mac OS LAF doesn't imitate this for us.
          c. setBackground(MAC_UNFOCUSED_SELECTED_CELL_BACKGROUND_COLOR);
          c.setForeground(UIManager.getColor("Table.foreground"));
        } else {
          c.setBackground(UIManager.getColor("Table.selectionBackground"));
          c.setForeground(UIManager.getColor("Table.selectionForeground"));
        }
      } else {
        // Outside of selected rows, we want to alternate the background color.
        c.setBackground(colorForRow(row));
        c.setForeground(UIManager.getColor("Table.foreground"));
      }
  
      if (c instanceof JComponent) {
        JComponent jc = (JComponent) c;
  
        // The Java 6 GTK LAF JCheckBox doesn't paint its background by default.
        // Sun 5043225 says this is the intended behavior, though presumably not when it's being used as a table cell renderer.
        if (UIManager.getLookAndFeel().getClass().getName().contains("GTK") && c instanceof JCheckBox) {
          jc.setOpaque(true);
        }
  
        if (getCellSelectionEnabled() == false && isEditing() == false) {
          if (System.getProperty("os.name").contains("Mac")) {
            // Native Mac OS doesn't draw a border on the selected cell.
            // It does however draw a horizontal line under the whole row, and a vertical line separating each column.
            fixMacOsCellRendererBorder(jc, selected, focused);
          } else {
            // FIXME: doesn't Windows have row-wide selection focus?
            // Hide the cell focus.
            jc.setBorder(null);
          }
        }
  
        initToolTip(jc, row, column);
      }
  
      return c;
    }
  
    private void fixMacOsCellRendererBorder(JComponent renderer, boolean selected, boolean focused) {
      Border border;
      if (selected) {
        border = BorderFactory.createMatteBorder(0, 0, 1, 0, focused ? MAC_FOCUSED_SELECTED_CELL_HORIZONTAL_LINE_COLOR : MAC_UNFOCUSED_SELECTED_CELL_HORIZONTAL_LINE_COLOR);
      } else {
        border = BorderFactory.createEmptyBorder(0, 0, 1, 0);
      }
  
      // Mac OS' Aqua LAF never draws vertical grid lines, so we have to draw them ourselves.
      if (getShowVerticalLines()) {
        Color verticalLineColor;
        if (focused) {
          verticalLineColor = selected ? MAC_FOCUSED_SELECTED_VERTICAL_LINE_COLOR : MAC_FOCUSED_UNSELECTED_VERTICAL_LINE_COLOR;
        } else {
          verticalLineColor = selected ? MAC_UNFOCUSED_SELECTED_VERTICAL_LINE_COLOR : MAC_UNFOCUSED_UNSELECTED_VERTICAL_LINE_COLOR;
        }
        Border verticalBorder = BorderFactory.createMatteBorder(0, 0, 0, 1, verticalLineColor);
        border = BorderFactory.createCompoundBorder(border, verticalBorder);
      }
  
      renderer.setBorder(border);
    }
  
    /**
     * Sets the component's tool tip if the component is being rendered smaller than its preferred size.
     * This means that all users automatically get tool tips on truncated text fields that show them the full value.
     */
    private void initToolTip(JComponent c, int row, int column) {
      String toolTipText = null;
      if (c.getPreferredSize().width > getCellRect(row, column, false).width) {
        toolTipText = getValueAt(row, column).toString();
      }
      c.setToolTipText(toolTipText);
    }
  
    /**
     * Places tool tips over the cell they correspond to. MS Outlook does this, and it works rather well.
     * Swing will automatically override our suggested location if it would cause the tool tip to go off the display.
     */
    public Point getToolTipLocation(MouseEvent e) {
      // After a tool tip has been displayed for a cell that has a tool tip, cells without tool tips will show an empty tool tip until the tool tip mode times out (or the table has a global default tool tip).
      // (ToolTipManager.checkForTipChange considers a non-null result from getToolTipText *or* a non-null result from getToolTipLocation as implying that the tool tip should be displayed. This seems like a bug, but that's the way it is.)
      if (getToolTipText(e) == null) {
        return null;
      }
      final int row = rowAtPoint(e.getPoint());
      final int column = columnAtPoint(e.getPoint());
      if (row == -1 || column == -1) {
        return null;
      }
      return getCellRect(row, column, false).getLocation();
    }
  
    /**
     * Improve the appearance of of a table in a JScrollPane on Mac OS, where there's otherwise an unsightly hole.
     */
    @Override
    protected void configureEnclosingScrollPane() {
      super.configureEnclosingScrollPane();
  
      if (System.getProperty("os.name").contains("Mac") == false) {
        return;
      }
  
      Container p = getParent();
      if (p instanceof JViewport) {
        Container gp = p.getParent();
        if (gp instanceof JScrollPane) {
          JScrollPane scrollPane = (JScrollPane)gp;
          // Make certain we are the viewPort's view and not, for
          // example, the rowHeaderView of the scrollPane -
          // an implementor of fixed columns might do this.
          JViewport viewport = scrollPane.getViewport();
          if (viewport == null || viewport.getView() != this) {
            return;
          }
  
          // JTable copy & paste above this point; our code below.
  
          // Put a dummy header in the upper-right corner.
          final Component renderer = new JTableHeader().getDefaultRenderer().getTableCellRendererComponent(null, "", false, false, -1, 0);
          JPanel panel = new JPanel(new BorderLayout());
          panel.add(renderer, BorderLayout.CENTER);
          scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, panel);
        }
      }
    }
  }
  
  