/* * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.apple.laf; import java.awt.*; import java.util.Enumeration; import javax.swing.*; import javax.swing.border.Border; import javax.swing.plaf.*; import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.table.*; import com.apple.laf.ClientPropertyApplicator; import com.apple.laf.ClientPropertyApplicator.Property; import com.apple.laf.AquaUtils.RecyclableSingleton; public class AquaTableHeaderUI extends BasicTableHeaderUI { private int originalHeaderAlignment; protected int sortColumn; protected int sortOrder; public static ComponentUI createUI(final JComponent c) { return new AquaTableHeaderUI(); } public void installDefaults() { super.installDefaults(); final TableCellRenderer renderer = header.getDefaultRenderer(); if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) { final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer; originalHeaderAlignment = defaultRenderer.getHorizontalAlignment(); defaultRenderer.setHorizontalAlignment(SwingConstants.LEADING); } } public void uninstallDefaults() { final TableCellRenderer renderer = header.getDefaultRenderer(); if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) { final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer; defaultRenderer.setHorizontalAlignment(originalHeaderAlignment); } super.uninstallDefaults(); } final static RecyclableSingleton> TABLE_HEADER_APPLICATORS = new RecyclableSingleton>() { @Override protected ClientPropertyApplicator getInstance() { return new ClientPropertyApplicator( new Property("JTableHeader.selectedColumn") { public void applyProperty(final JTableHeader target, final Object value) { tickle(target, value, target.getClientProperty("JTableHeader.sortDirection")); } }, new Property("JTableHeader.sortDirection") { public void applyProperty(final JTableHeader target, final Object value) { tickle(target, target.getClientProperty("JTableHeader.selectedColumn"), value); } } ); } }; static ClientPropertyApplicator getTableHeaderApplicators() { return TABLE_HEADER_APPLICATORS.get(); } static void tickle(final JTableHeader target, final Object selectedColumn, final Object direction) { final TableColumn tableColumn = getTableColumn(target, selectedColumn); if (tableColumn == null) return; int sortDirection = 0; if ("ascending".equalsIgnoreCase(direction+"")) { sortDirection = 1; } else if ("descending".equalsIgnoreCase(direction+"")) { sortDirection = -1; } else if ("decending".equalsIgnoreCase(direction+"")) { sortDirection = -1; // stupid misspelling that GM'ed in 10.5.0 } final TableHeaderUI headerUI = target.getUI(); if (headerUI == null || !(headerUI instanceof AquaTableHeaderUI)) return; final AquaTableHeaderUI aquaHeaderUI = (AquaTableHeaderUI)headerUI; aquaHeaderUI.sortColumn = tableColumn.getModelIndex(); aquaHeaderUI.sortOrder = sortDirection; final AquaTableCellRenderer renderer = aquaHeaderUI.new AquaTableCellRenderer(); tableColumn.setHeaderRenderer(renderer); } @SuppressWarnings("serial") // Superclass is not serializable across versions class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource { public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { if (localTable != null) { if (header != null) { setForeground(header.getForeground()); setBackground(header.getBackground()); setFont(UIManager.getFont("TableHeader.font")); } } setText((value == null) ? "" : value.toString()); // Modify the table "border" to draw smaller, and with the titles in the right position // and sort indicators, just like an NSSave/Open panel. final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder(); final boolean thisColumnSelected = localTable.getColumnModel().getColumn(column).getModelIndex() == sortColumn; cellBorder.setSelected(thisColumnSelected); if (thisColumnSelected) { cellBorder.setSortOrder(sortOrder); } else { cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE); } setBorder(cellBorder); return this; } } protected static TableColumn getTableColumn(final JTableHeader target, final Object value) { if (value == null || !(value instanceof Integer)) return null; final int columnIndex = ((Integer)value).intValue(); final TableColumnModel columnModel = target.getColumnModel(); if (columnIndex < 0 || columnIndex >= columnModel.getColumnCount()) return null; return columnModel.getColumn(columnIndex); } protected static AquaTableHeaderBorder getAquaBorderFrom(final JTableHeader header, final TableColumn column) { final TableCellRenderer renderer = column.getHeaderRenderer(); if (renderer == null) return null; final Component c = renderer.getTableCellRendererComponent(header.getTable(), column.getHeaderValue(), false, false, -1, column.getModelIndex()); if (!(c instanceof JComponent)) return null; final Border border = ((JComponent)c).getBorder(); if (!(border instanceof AquaTableHeaderBorder)) return null; return (AquaTableHeaderBorder)border; } protected void installListeners() { super.installListeners(); getTableHeaderApplicators().attachAndApplyClientProperties(header); } protected void uninstallListeners() { getTableHeaderApplicators().removeFrom(header); super.uninstallListeners(); } private int getHeaderHeightAqua() { int height = 0; boolean accomodatedDefault = false; final TableColumnModel columnModel = header.getColumnModel(); for (int column = 0; column < columnModel.getColumnCount(); column++) { final TableColumn aColumn = columnModel.getColumn(column); // Configuring the header renderer to calculate its preferred size is expensive. // Optimise this by assuming the default renderer always has the same height. if (aColumn.getHeaderRenderer() != null || !accomodatedDefault) { final Component comp = getHeaderRendererAqua(column); final int rendererHeight = comp.getPreferredSize().height; height = Math.max(height, rendererHeight); // If the header value is empty (== "") in the // first column (and this column is set up // to use the default renderer) we will // return zero from this routine and the header // will disappear altogether. Avoiding the calculation // of the preferred size is such a performance win for // most applications that we will continue to // use this cheaper calculation, handling these // issues as `edge cases'. // Mac OS X Change - since we have a border on our renderers // it is possible the height of an empty header could be > 0, // so we chose the relatively safe number of 4 to handle this case. // Now if we get a size of 4 or less we assume it is empty and measure // a different header. if (rendererHeight > 4) { accomodatedDefault = true; } } } return height; } private Component getHeaderRendererAqua(final int columnIndex) { final TableColumn aColumn = header.getColumnModel().getColumn(columnIndex); TableCellRenderer renderer = aColumn.getHeaderRenderer(); if (renderer == null) { renderer = header.getDefaultRenderer(); } return renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, columnIndex); } private Dimension createHeaderSizeAqua(long width) { // None of the callers include the intercell spacing, do it here. if (width > Integer.MAX_VALUE) { width = Integer.MAX_VALUE; } return new Dimension((int)width, getHeaderHeightAqua()); } /** * Return the minimum size of the header. The minimum width is the sum of the minimum widths of each column (plus * inter-cell spacing). */ public Dimension getMinimumSize(final JComponent c) { long width = 0; final Enumeration enumeration = header.getColumnModel().getColumns(); while (enumeration.hasMoreElements()) { final TableColumn aColumn = enumeration.nextElement(); width = width + aColumn.getMinWidth(); } return createHeaderSizeAqua(width); } /** * Return the preferred size of the header. The preferred height is the maximum of the preferred heights of all of * the components provided by the header renderers. The preferred width is the sum of the preferred widths of each * column (plus inter-cell spacing). */ public Dimension getPreferredSize(final JComponent c) { long width = 0; final Enumeration enumeration = header.getColumnModel().getColumns(); while (enumeration.hasMoreElements()) { final TableColumn aColumn = enumeration.nextElement(); width = width + aColumn.getPreferredWidth(); } return createHeaderSizeAqua(width); } }