/* * 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); } } } }