1 /* 2 * Copyright (c) 2011, 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 class AquaTableCellRenderer extends DefaultTableCellRenderer implements UIResource { 115 public Component getTableCellRendererComponent(final JTable localTable, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { 116 if (localTable != null) { 117 if (header != null) { 118 setForeground(header.getForeground()); 119 setBackground(header.getBackground()); 120 setFont(UIManager.getFont("TableHeader.font")); 121 } 122 } 123 124 setText((value == null) ? "" : value.toString()); 125 126 // Modify the table "border" to draw smaller, and with the titles in the right position 127 // and sort indicators, just like an NSSave/Open panel. 128 final AquaTableHeaderBorder cellBorder = AquaTableHeaderBorder.getListHeaderBorder(); 129 final boolean thisColumnSelected = localTable.getColumnModel().getColumn(column).getModelIndex() == sortColumn; 130 131 cellBorder.setSelected(thisColumnSelected); 132 if (thisColumnSelected) { 133 cellBorder.setSortOrder(sortOrder); 134 } else { 135 cellBorder.setSortOrder(AquaTableHeaderBorder.SORT_NONE); 136 } 137 setBorder(cellBorder); 138 return this; 139 } 140 } 141 142 protected static TableColumn getTableColumn(final JTableHeader target, final Object value) { 143 if (value == null || !(value instanceof Integer)) return null; 144 final int columnIndex = ((Integer)value).intValue(); 145 146 final TableColumnModel columnModel = target.getColumnModel(); 147 if (columnIndex < 0 || columnIndex >= columnModel.getColumnCount()) return null; 148 149 return columnModel.getColumn(columnIndex); 150 } 151 152 protected static AquaTableHeaderBorder getAquaBorderFrom(final JTableHeader header, final TableColumn column) { 153 final TableCellRenderer renderer = column.getHeaderRenderer(); 154 if (renderer == null) return null; 155 156 final Component c = renderer.getTableCellRendererComponent(header.getTable(), column.getHeaderValue(), false, false, -1, column.getModelIndex()); 157 if (!(c instanceof JComponent)) return null; 158 159 final Border border = ((JComponent)c).getBorder(); 160 if (!(border instanceof AquaTableHeaderBorder)) return null; 161 162 return (AquaTableHeaderBorder)border; 163 } 164 165 protected void installListeners() { 166 super.installListeners(); 167 getTableHeaderApplicators().attachAndApplyClientProperties(header); 168 } 169 170 protected void uninstallListeners() { 171 getTableHeaderApplicators().removeFrom(header); 172 super.uninstallListeners(); 173 } 174 175 private int getHeaderHeightAqua() { 176 int height = 0; 177 boolean accomodatedDefault = false; 178 179 final TableColumnModel columnModel = header.getColumnModel(); 180 for (int column = 0; column < columnModel.getColumnCount(); column++) { 181 final TableColumn aColumn = columnModel.getColumn(column); 182 // Configuring the header renderer to calculate its preferred size is expensive. 183 // Optimise this by assuming the default renderer always has the same height. 184 if (aColumn.getHeaderRenderer() != null || !accomodatedDefault) { 185 final Component comp = getHeaderRendererAqua(column); 186 final int rendererHeight = comp.getPreferredSize().height; 187 height = Math.max(height, rendererHeight); 188 // If the header value is empty (== "") in the 189 // first column (and this column is set up 190 // to use the default renderer) we will 191 // return zero from this routine and the header 192 // will disappear altogether. Avoiding the calculation 193 // of the preferred size is such a performance win for 194 // most applications that we will continue to 195 // use this cheaper calculation, handling these 196 // issues as `edge cases'. 197 198 // Mac OS X Change - since we have a border on our renderers 199 // it is possible the height of an empty header could be > 0, 200 // so we chose the relatively safe number of 4 to handle this case. 201 // Now if we get a size of 4 or less we assume it is empty and measure 202 // a different header. 203 if (rendererHeight > 4) { 204 accomodatedDefault = true; 205 } 206 } 207 } 208 return height; 209 } 210 211 private Component getHeaderRendererAqua(final int columnIndex) { 212 final TableColumn aColumn = header.getColumnModel().getColumn(columnIndex); 213 TableCellRenderer renderer = aColumn.getHeaderRenderer(); 214 if (renderer == null) { 215 renderer = header.getDefaultRenderer(); 216 } 217 return renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, columnIndex); 218 } 219 220 private Dimension createHeaderSizeAqua(long width) { 221 // None of the callers include the intercell spacing, do it here. 222 if (width > Integer.MAX_VALUE) { 223 width = Integer.MAX_VALUE; 224 } 225 return new Dimension((int)width, getHeaderHeightAqua()); 226 } 227 228 /** 229 * Return the minimum size of the header. The minimum width is the sum of the minimum widths of each column (plus 230 * inter-cell spacing). 231 */ 232 public Dimension getMinimumSize(final JComponent c) { 233 long width = 0; 234 final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); 235 while (enumeration.hasMoreElements()) { 236 final TableColumn aColumn = enumeration.nextElement(); 237 width = width + aColumn.getMinWidth(); 238 } 239 return createHeaderSizeAqua(width); 240 } 241 242 /** 243 * Return the preferred size of the header. The preferred height is the maximum of the preferred heights of all of 244 * the components provided by the header renderers. The preferred width is the sum of the preferred widths of each 245 * column (plus inter-cell spacing). 246 */ 247 public Dimension getPreferredSize(final JComponent c) { 248 long width = 0; 249 final Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); 250 while (enumeration.hasMoreElements()) { 251 final TableColumn aColumn = enumeration.nextElement(); 252 width = width + aColumn.getPreferredWidth(); 253 } 254 return createHeaderSizeAqua(width); 255 } 256 }