/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ package gov.nasa.arc.mct.table.view; import gov.nasa.arc.mct.components.AbstractComponent; import gov.nasa.arc.mct.table.model.ComponentTableModel; import gov.nasa.arc.mct.util.LafColor; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Shape; import java.awt.font.TextAttribute; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.table.TableCellRenderer; /** * <p>Implements a table cell renderer for the tabular display view * role. The cell is rendered, usually, from a {@link DisplayedValue} * object that is the value of the table cell. A {@link TableCellFormatter} * is used to determine the locations of the various parts of the table * cell value. */ @SuppressWarnings("serial") public final class TableViewCellRenderer extends LightweightLabel implements TableCellRenderer { /** * An empty <code>Border</code>. This field might not be used. To change the * <code>Border</code> used by this renderer override the * <code>getTableCellRendererComponent</code> method and set the border * of the returned component directly. */ private static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER; // We need a place to store the color the JLabel should be returned // to after its foreground and background colors have been set // to the selection background color. // These ivars will be made protected when their names are finalized. private Color unselectedForeground; private Color unselectedBackground; /** The color of an active cell. */ private static final Color ACTIVE_COLOR = LafColor.TREE_SELECTION_BACKGROUND; /** The cell formatter used to determine the layout of the parts of the * cell value. */ private TableCellFormatter formatter = new TableCellFormatter(); /** The last font that was used to draw a cell. The widths of certain * fixed strings are only calculated when this font changes. */ private Font cellFont = null; /** * Creates a table cell renderer that initially has a border that shows * no focus. Save away the default label color, in case values to display * do not define their own colors. Also set the component name to be the * same as the default table cell renderer used by JTable, to make UI * automation more compatible with the default JTable behavior. */ public TableViewCellRenderer() { super(); setBorder(getNoFocusBorder()); setForeground(Color.BLACK); //Default color - usually gets replaced setName("Table.cellRenderer"); formatter.setBorderColor(getColor("userGrid")); } private Color getColor(String key) { return UIManager.getColor(key, getLocale()); } private Border getBorder(String key) { return UIManager.getBorder(key, getLocale()); } private Border getNoFocusBorder() { Border border = getBorder("Table.cellNoFocusBorder"); if (System.getSecurityManager() != null) { if (border != null) return border; return SAFE_NO_FOCUS_BORDER; } else if (border != null) { if (noFocusBorder == null || noFocusBorder == DEFAULT_NO_FOCUS_BORDER) { return border; } } return noFocusBorder; } /** * Overrides <code>JComponent.setForeground</code> to assign * the unselected-foreground color to the specified color. * * @param c set the foreground color to this value */ @Override public void setForeground(Color c) { super.setForeground(c); unselectedForeground = c; } /** * Overrides <code>JComponent.setBackground</code> to assign * the unselected-background color to the specified color. * * @param c set the background color to this value */ @Override public void setBackground(Color c) { super.setBackground(c); unselectedBackground = c; } /** * Notification from the <code>UIManager</code> that the look and feel * [L&F] has changed. * Replaces the current UI object with the latest version from the * <code>UIManager</code>. * * @see JComponent#updateUI */ @Override public void updateUI() { super.updateUI(); setForeground(null); setBackground(null); } /** * Returns the table cell renderer. * <p> * During a printing operation, this method will be called with * <code>isSelected</code> and <code>hasFocus</code> values of * <code>false</code> to prevent selection and focus from appearing * in the printed output. To do other customization based on whether * or not the table is being printed, check the return value from * {@link javax.swing.JComponent#isPaintingForPrint()}. * * @param table the <code>JTable</code> * @param value the value to assign to the cell at * <code>[row, column]</code> * @param isSelected true if cell is selected * @param hasFocus true if cell has focus * @param row the row of the cell to render * @param column the column of the cell to render * @return the default table cell renderer * @see javax.swing.JComponent#isPaintingForPrint() */ @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Color fg = null; Color bg = null; JTable.DropLocation dropLocation = table.getDropLocation(); if (dropLocation != null && !dropLocation.isInsertRow() && !dropLocation.isInsertColumn() && dropLocation.getRow() == row && dropLocation.getColumn() == column) { fg = getColor("Table.dropCellForeground"); bg = getColor("Table.dropCellBackground"); isSelected = true; } if (isSelected) { super.setForeground(fg == null ? table.getSelectionForeground() : fg); super.setBackground(bg == null ? table.getSelectionBackground() : bg); } else { Color background = unselectedBackground != null ? unselectedBackground : table.getBackground(); if (background == null || background instanceof javax.swing.plaf.UIResource) { Color alternateColor = getColor("Table.alternateRowColor"); if (alternateColor != null && row % 2 == 0) background = alternateColor; } super.setForeground(unselectedForeground != null ? unselectedForeground : table.getForeground()); super.setBackground(background); } /* Set up a border for the drawn component */ assert table.getModel() instanceof ComponentTableModel; ComponentTableModel m = ComponentTableModel.class.cast(table.getModel()); AbstractComponent comp = (AbstractComponent)m.getStoredValueAt(row, column); Color cellBackgroundColor = null; Font cellFont = table.getFont(); setFont(cellFont); if (comp != null) { TableCellSettings s = m.getCellSettings(m.getKey(comp)); if (s.getCellFont() != null) { cellFont = new Font(s.getCellFont().name(),s.getFontStyle(),s.getFontSize()); } if (s.getTextAttributeUnderline() == TextAttribute.UNDERLINE_ON) { cellFont = cellFont.deriveFont(TableFormattingConstants.underlineMap); } setFont(cellFont); formatter.getFixedStringWidths(getFontMetrics(getFont())); if (s.getBackgroundColor() != null) { cellBackgroundColor = s.getBackgroundColor(); } BorderState b = s.getCellBorderState(); assert b != null; boolean hasNorth = b.hasNorthBorder(); boolean hasWest = b.hasWestBorder(); boolean hasSouth = b.hasSouthBorder(); boolean hasEast = b.hasEastBorder(); int w = TableCellFormatter.getBorderWidth(); Border outside = BorderFactory.createMatteBorder(hasNorth ? w : 0, hasWest ? w : 0, hasSouth ? w : 0, hasEast ? w : 0, formatter.getBorderColor()); Border inside = BorderFactory.createEmptyBorder(hasNorth ? 0 : w, hasWest ? 0 : w, hasSouth ? 0 : w, hasEast ? 0 : w); setBorder(BorderFactory.createCompoundBorder(outside,inside)); } else { setBorder(BorderFactory.createEmptyBorder()); } /* Set background appropriate to our selection state */ setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); if (cellBackgroundColor != null) { setBackground(cellBackgroundColor); } /* Add a border inside of that if we have focus */ if (hasFocus) { Border baseBorder = getBorder(); Border focusBorder = BorderFactory.createLineBorder(table.getSelectionBackground().brighter().brighter(), 1); setBorder(BorderFactory.createCompoundBorder(baseBorder, focusBorder)); } if (!(value instanceof DisplayedValue)) { formatter.setCellValue(value!=null ? value.toString() : ""); formatter.setCellLabel(""); formatter.setStatusCode(""); formatter.setValueColor(getForeground()); formatter.setAlignment(ContentAlignment.RIGHT); formatter.setNumberOfDecimals(0); } else { DisplayedValue dv = (DisplayedValue) value; formatter.setCellValue(dv.getValue()); formatter.setCellLabel(dv.getLabel()); formatter.setStatusCode(dv.getStatusText()); formatter.setValueColor(dv.hasColor() ? dv.getColor() : getForeground()); formatter.setAlignment(dv.getAlignment()); formatter.setNumberOfDecimals(dv.getNumberOfDecimals()); } if (table.getModel() instanceof ComponentTableModel) { formatter.setMaxNumberOfDecimals(ComponentTableModel.class.cast(table.getModel()).getMaxDecimalsForColumn(column)); } else { formatter.setMaxNumberOfDecimals(0); } return this; } @Override public void paint(Graphics g) { super.paint(g); FontMetrics fm = getFontMetrics(getFont()); int height = fm.getHeight(); int ascent = fm.getAscent(); int y = ascent + (getHeight() - height) / 2; int width = g.getClip() == null ? getWidth() : (int) g.getClip().getBounds().getWidth(); formatter.layoutCell(fm, width); g.setColor(getForeground()); g.clipRect(0, 0, getWidth(), getHeight()); if (formatter.getLabelLocation() >= 0) { drawStringWithEllipsis(g, formatter.getCellLabel(), formatter.getLabelLocation(), formatter.getLabelClipWidth(), height, y); } g.setColor(adjust(formatter.getValueColor(), getBackground())); if (formatter.getValueLocation() >= 0) { drawStringWithEllipsis(g, formatter.getCellValue(), formatter.getValueLocation(), formatter.getValueClipWidth(), height, y); } if (formatter.getStatusLocation() >= 0 && formatter.getStatusCode().length() > 0) { g.drawString(formatter.getStatusCode(), formatter.getStatusLocation(), y); } } /** * Adjust the color for visibility on the specified base */ private Color adjust(Color c, Color base) { /* Nothing to change for black, * and don't try to adjust up for "bright" colors */ if (base.getRGB() == Color.black.getRGB() || base.getRGB() == Color.white.getRGB()) return c; double r = c.getRed() / 255.0; double g = c.getGreen() / 255.0; double b = c.getBlue() / 255.0; /* Brighten above dark backgrounds; darken below bright ones */ r = (base.getRed() < 128) ? (255 - base.getRed()) * r + base.getRed() : (base.getRed()) * r; g = (base.getGreen() < 128) ? (255 - base.getGreen()) * g + base.getGreen() : (base.getGreen()) * g; b = (base.getBlue() < 128) ? (255 - base.getBlue()) * b + base.getBlue() : (base.getBlue()) * b; return new Color ((int) r, (int) g, (int) b); } /** * Draws a string, truncating and showing an ellipsis if the string should be * clipped to a maximum width. * * @param g the graphics in which to display the string * @param s the string to display * @param x the horizontal position at which to display the string (character baseline is drawn at position x,y) * @param clipWidth the width to which we should clip, or -1 if no clipping should occur * @param height the font height used for drawing * @param y the vertical position at which to display the string (character baseline is drawn at position x,y) */ private void drawStringWithEllipsis(Graphics g, String s, int x, int clipWidth, int height, int y) { if (s.length() == 0) { return; } if (clipWidth < 0) { g.drawString(s, x, y); } else if (clipWidth > 0) { Shape origClip = g.getClip(); // Draw the truncated value if there's room to draw a portion while leaving // room for the ellipsis. int ellipsisWidth = formatter.getEllipsisWidth(); if (clipWidth > ellipsisWidth) { g.clipRect(x, 0, clipWidth-ellipsisWidth, getHeight()); g.drawString(s, x, y); } g.setClip(null); g.clipRect(0, 0, clipWidth, getHeight()); g.drawString(TableCellFormatter.ELLIPSIS, x + clipWidth - ellipsisWidth, y); g.setClip(origClip); } } }