1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.apple.laf; 27 28 import java.awt.*; 29 import java.util.Enumeration; 30 31 import javax.swing.*; 32 import javax.swing.border.Border; 33 import javax.swing.plaf.*; 34 import javax.swing.plaf.basic.BasicTableHeaderUI; 35 import javax.swing.table.*; 36 import com.apple.laf.ClientPropertyApplicator; 37 import com.apple.laf.ClientPropertyApplicator.Property; 38 import com.apple.laf.AquaUtils.RecyclableSingleton; 39 40 public class AquaTableHeaderUI extends BasicTableHeaderUI { 41 private int originalHeaderAlignment; 42 protected int sortColumn; 43 protected int sortOrder; 44 45 public static ComponentUI createUI(final JComponent c) { 46 return new AquaTableHeaderUI(); 47 } 48 49 public void installDefaults() { 50 super.installDefaults(); 51 52 final TableCellRenderer renderer = header.getDefaultRenderer(); 53 if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) { 54 final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer; 55 originalHeaderAlignment = defaultRenderer.getHorizontalAlignment(); 56 defaultRenderer.setHorizontalAlignment(SwingConstants.LEADING); 57 } 58 } 59 60 public void uninstallDefaults() { 61 final TableCellRenderer renderer = header.getDefaultRenderer(); 62 if (renderer instanceof UIResource && renderer instanceof DefaultTableCellRenderer) { 63 final DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)renderer; 64 defaultRenderer.setHorizontalAlignment(originalHeaderAlignment); 65 } 66 67 super.uninstallDefaults(); 68 } 69 70 final static RecyclableSingleton<ClientPropertyApplicator<JTableHeader, JTableHeader>> TABLE_HEADER_APPLICATORS = new RecyclableSingleton<ClientPropertyApplicator<JTableHeader, JTableHeader>>() { 71 @Override 72 protected ClientPropertyApplicator<JTableHeader, JTableHeader> getInstance() { 73 return new ClientPropertyApplicator<JTableHeader, JTableHeader>( 74 new Property<JTableHeader>("JTableHeader.selectedColumn") { 75 public void applyProperty(final JTableHeader target, final Object value) { 76 tickle(target, value, target.getClientProperty("JTableHeader.sortDirection")); 77 } 78 }, 79 new Property<JTableHeader>("JTableHeader.sortDirection") { 80 public void applyProperty(final JTableHeader target, final Object value) { 81 tickle(target, target.getClientProperty("JTableHeader.selectedColumn"), value); 82 } 83 } 84 ); 85 } 86 }; 87 static ClientPropertyApplicator<JTableHeader, JTableHeader> getTableHeaderApplicators() { 88 return TABLE_HEADER_APPLICATORS.get(); 89 } 90 91 static void tickle(final JTableHeader target, final Object selectedColumn, final Object direction) { 92 final TableColumn tableColumn = getTableColumn(target, selectedColumn); 93 if (tableColumn == null) return; 94 95 int sortDirection = 0; 96 if ("ascending".equalsIgnoreCase(direction+"")) { 97 sortDirection = 1; 98 } else if ("descending".equalsIgnoreCase(direction+"")) { 99 sortDirection = -1; 100 } else if ("decending".equalsIgnoreCase(direction+"")) { 101 sortDirection = -1; // stupid misspelling that GM'ed in 10.5.0 102 } 103 104 final TableHeaderUI headerUI = target.getUI(); 105 if (headerUI == null || !(headerUI instanceof AquaTableHeaderUI)) return; 106 107 final AquaTableHeaderUI aquaHeaderUI = (AquaTableHeaderUI)headerUI; 108 aquaHeaderUI.sortColumn = tableColumn.getModelIndex(); 109 aquaHeaderUI.sortOrder = sortDirection; 110 final AquaTableCellRenderer renderer = aquaHeaderUI.new AquaTableCellRenderer(); 111 tableColumn.setHeaderRenderer(renderer); 112 } 113 114 @SuppressWarnings("serial") // Superclass is not serializable across versions 115 class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource { 116 public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { 117 if (localTable != null) { 118 if (header != null) { 119 setForeground(header.getForeground()); 120 setBackground(header.getBackground()); 121 setFont(UIManager.getFont("TableHeader.font")); 122 } 123 } 124 125 setText((value == null) ? "" : value.toString()); 126 127 // Modify the table "border" to draw smaller, and with the titles in the right position 128 // and sort indicators, just like an NSSave/Open panel. 129 final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder(); 130 final boolean thisColumnSelected = localTable.getColumnModel().getColumn(column).getModelIndex() == sortColumn; 131 132 cellBorder.setSelected(thisColumnSelected); 133 if (thisColumnSelected) { 134 cellBorder.setSortOrder(sortOrder); 135 } else { 136 cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE); 137 } 138 setBorder(cellBorder); 139 return this; 140 } 141 } 142 143 protected static TableColumn getTableColumn(final JTableHeader target, final Object value) { 144 if (value == null || !(value instanceof Integer)) return null; 145 final int columnIndex = ((Integer)value).intValue(); 146 147 final TableColumnModel columnModel = target.getColumnModel(); 148 if (columnIndex < 0 || columnIndex >= columnModel.getColumnCount()) return null; 149 150 return columnModel.getColumn(columnIndex); 151 } 152 153 protected static AquaTableHeaderBorder getAquaBorderFrom(final JTableHeader header, final TableColumn column) { 154 final TableCellRenderer renderer = column.getHeaderRenderer(); 155 if (renderer == null) return null; 156 157 final Component c = renderer.getTableCellRendererComponent(header.getTable(), column.getHeaderValue(), false, false, -1, column.getModelIndex()); 158 if (!(c instanceof JComponent)) return null; 159 160 final Border border = ((JComponent)c).getBorder(); 161 if (!(border instanceof AquaTableHeaderBorder)) return null; 162 163 return (AquaTableHeaderBorder)border; 164 } 165 166 protected void installListeners() { 167 super.installListeners(); 168 getTableHeaderApplicators().attachAndApplyClientProperties(header); 169 } 170 171 protected void uninstallListeners() { 172 getTableHeaderApplicators().removeFrom(header); 173 super.uninstallListeners(); 174 } 175 176 private int getHeaderHeightAqua() { 177 int height = 0; 178 boolean accomodatedDefault = false; 179 180 final TableColumnModel columnModel = header.getColumnModel(); 181 for (int column = 0; column < columnModel.getColumnCount(); column++) { 182 final TableColumn aColumn = columnModel.getColumn(column); 183 // Configuring the header renderer to calculate its preferred size is expensive. 184 // Optimise this by assuming the default renderer always has the same height. 185 if (aColumn.getHeaderRenderer() != null || !accomodatedDefault) { 186 final Component comp = getHeaderRendererAqua(column); 187 final int rendererHeight = comp.getPreferredSize().height; 188 height = Math.max(height, rendererHeight); 189 // If the header value is empty (== "") in the 190 // first column (and this column is set up 191 // to use the default renderer) we will 192 // return zero from this routine and the header 193 // will disappear altogether. Avoiding the calculation 194 // of the preferred size is such a performance win for 195 // most applications that we will continue to 196 // use this cheaper calculation, handling these 197 // issues as `edge cases'. 198 199 // Mac OS X Change - since we have a border on our renderers 200 // it is possible the height of an empty header could be > 0, 201 // so we chose the relatively safe number of 4 to handle this case. 202 // Now if we get a size of 4 or less we assume it is empty and measure 203 // a different header. 204 if (rendererHeight > 4) { 205 accomodatedDefault = true; 206 } 207 } 208 } 209 return height; 210 } 211 212 private Component getHeaderRendererAqua(final int columnIndex) { 213 final TableColumn aColumn = header.getColumnModel().getColumn(columnIndex); 214 TableCellRenderer renderer = aColumn.getHeaderRenderer(); 215 if (renderer == null) { 216 renderer = header.getDefaultRenderer(); 217 } 218 return renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, columnIndex); 219 } 220 221 private Dimension createHeaderSizeAqua(long width) { 222 // None of the callers include the intercell spacing, do it here. 223 if (width > Integer.MAX_VALUE) { 224 width = Integer.MAX_VALUE; 225 } 226 return new Dimension((int)width, getHeaderHeightAqua()); 227 } 228 229 /** 230 * Return the minimum size of the header. The minimum width is the sum of the minimum widths of each column (plus 231 * inter-cell spacing). 232 */ 233 public Dimension getMinimumSize(final JComponent c) { 234 long width = 0; 235 final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); 236 while (enumeration.hasMoreElements()) { 237 final TableColumn aColumn = enumeration.nextElement(); 238 width = width + aColumn.getMinWidth(); 239 } 240 return createHeaderSizeAqua(width); 241 } 242 243 /** 244 * Return the preferred size of the header. The preferred height is the maximum of the preferred heights of all of 245 * the components provided by the header renderers. The preferred width is the sum of the preferred widths of each 246 * column (plus inter-cell spacing). 247 */ 248 public Dimension getPreferredSize(final JComponent c) { 249 long width = 0; 250 final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); 251 while (enumeration.hasMoreElements()) { 252 final TableColumn aColumn = enumeration.nextElement(); 253 width = width + aColumn.getPreferredWidth(); 254 } 255 return createHeaderSizeAqua(width); 256 } 257 }