001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.swing.popupswitcher;
043:
044: import java.awt.Color;
045: import java.awt.Component;
046: import java.awt.Dimension;
047: import java.awt.Font;
048: import java.awt.FontMetrics;
049: import java.awt.Graphics;
050: import java.awt.Graphics2D;
051: import java.awt.image.BufferedImage;
052: import java.lang.ref.SoftReference;
053: import javax.swing.BorderFactory;
054: import javax.swing.Icon;
055: import javax.swing.JTable;
056: import javax.swing.UIManager;
057: import javax.swing.border.Border;
058: import javax.swing.table.DefaultTableCellRenderer;
059: import javax.swing.table.TableCellRenderer;
060: import org.openide.util.Utilities;
061:
062: /**
063: * This class is used as a content for PopupSwitcher classes (see below). It
064: * appropriately displays its contents (<code>SwitcherTableItem</code>s)
065: * according to screen size, given position, used font, number of items, etc.
066: * and inteligently consider number of rows and columns to be used.
067: *
068: * @see SwitcherTableItem
069: *
070: * @author mkrauskopf
071: */
072: public class SwitcherTable extends JTable {
073:
074: private static final Border rendererBorder = BorderFactory
075: .createEmptyBorder(2, 5, 0, 5);
076:
077: private Icon nullIcon = new NullIcon();
078: private Color foreground;
079: private Color background;
080: private Color selForeground;
081: private Color selBackground;
082:
083: /** Cached preferred size value */
084: private Dimension prefSize;
085:
086: /** Current NetBeans LookAndFreel id */
087: /**
088: * Flag indicating that the fixed row height has not yet been calculated -
089: * this is for fontsize support
090: */
091: private boolean needCalcRowHeight = true;
092:
093: /**
094: * Creates a new instance of SwitcherTable. Created table will be as high
095: * as possible. Height will be used during the number of row computing.
096: */
097: public SwitcherTable(SwitcherTableItem[] items) {
098: this (items, 0);
099: }
100:
101: /**
102: * Creates a new instance of SwitcherTable. Height of created table will be
103: * computed according to given y coordinate. Height will be used during the
104: * number of row computing.
105: */
106: public SwitcherTable(SwitcherTableItem[] items, int y) {
107: super ();
108: init();
109: // get rid of the effect when popup seems to be higher that screen height
110: int gap = (y == 0 ? 10 : 5);
111: int height = Utilities.getUsableScreenBounds().height - y - gap;
112: setModel(new SwitcherTableModel(items, getRowHeight(), height));
113: getSelectionModel().clearSelection();
114: getSelectionModel().setAnchorSelectionIndex(-1);
115: getSelectionModel().setLeadSelectionIndex(-1);
116: setAutoscrolls(false);
117: }
118:
119: private void init() {
120: setBorder(BorderFactory.createLineBorder(getForeground()));
121: setShowHorizontalLines(false);
122: // Calc row height here so that TableModel can adjust number of columns.
123: calcRowHeight(getOffscreenGraphics());
124: }
125:
126: public void updateUI() {
127: needCalcRowHeight = true;
128: super .updateUI();
129: }
130:
131: public void setFont(Font f) {
132: needCalcRowHeight = true;
133: super .setFont(f);
134: }
135:
136: public Component prepareRenderer(TableCellRenderer renderer,
137: int row, int column) {
138:
139: SwitcherTableItem item = (SwitcherTableItem) getSwitcherTableModel()
140: .getValueAt(row, column);
141:
142: boolean selected = row == getSelectedRow()
143: && column == getSelectedColumn() && item != null;
144:
145: DefaultTableCellRenderer ren = (DefaultTableCellRenderer) renderer
146: .getTableCellRendererComponent(this , item, selected,
147: selected, row, column);
148:
149: if (item == null) {
150: // it's a filler space, we're done
151: ren.setOpaque(false);
152: ren.setIcon(null);
153: return ren;
154: }
155:
156: Icon icon = item.getIcon();
157: if (icon == null) {
158: icon = nullIcon;
159: }
160: ren.setText(selected || item.isActive() ? stripHtml(item
161: .getHtmlName()) : item.getHtmlName());
162: ren.setIcon(icon);
163: ren.setBorder(rendererBorder);
164: ren.setIconTextGap(26 - icon.getIconWidth());
165:
166: if (item.isActive()) {
167: // don't use deriveFont() - see #49973 for details
168: ren.setFont(new Font(getFont().getName(), Font.BOLD,
169: getFont().getSize()));
170: }
171:
172: ren.setOpaque(true);
173:
174: return ren;
175: }
176:
177: private String stripHtml(String htmlText) {
178: if (null == htmlText)
179: return null;
180: String res = htmlText.replaceAll("<[^>]*>", ""); // NOI18N // NOI18N
181: res = res.replaceAll(" ", " "); // NOI18N // NOI18N
182: res = res.trim();
183: return res;
184: }
185:
186: private static class NullIcon implements Icon {
187: public int getIconWidth() {
188: return 0;
189: }
190:
191: public int getIconHeight() {
192: return 0;
193: }
194:
195: public void paintIcon(Component c, Graphics g, int x, int y) {
196: }
197: }
198:
199: public Color getForeground() {
200: if (foreground == null) {
201: foreground = UIManager.getColor("ComboBox.foreground");
202: }
203: return foreground != null ? foreground : super .getForeground();
204: }
205:
206: public Color getBackground() {
207: if (background == null) {
208: background = UIManager.getColor("ComboBox.background");
209: }
210: return background != null ? background : super .getBackground();
211: }
212:
213: public Color getSelectionForeground() {
214: if (selForeground == null) {
215: selForeground = UIManager
216: .getColor("ComboBox.selectionForeground");
217: }
218: return selForeground != null ? selForeground : super
219: .getSelectionForeground();
220: }
221:
222: public Color getSelectionBackground() {
223: if (selBackground == null) {
224: selBackground = UIManager
225: .getColor("ComboBox.selectionBackground");
226: }
227: return selBackground != null ? selBackground : super
228: .getSelectionBackground();
229: }
230:
231: /**
232: * Calculate the height of rows based on the current font. This is done
233: * when the first paint occurs, to ensure that a valid Graphics object is
234: * available.
235: *
236: * @since 1.25
237: */
238: private void calcRowHeight(Graphics g) {
239: Font f = getFont();
240: FontMetrics fm = g.getFontMetrics(f);
241: // As icons are displayed use maximum from font and icon height
242: int rowHeight = Math.max(fm.getHeight(), 16) + 4;
243: needCalcRowHeight = false;
244: setRowHeight(rowHeight);
245: }
246:
247: private static SoftReference<BufferedImage> ctx = null;
248:
249: /**
250: * Provides an offscreen graphics context so that widths based on character
251: * size can be calculated correctly before the component is shown
252: */
253: private static Graphics2D getOffscreenGraphics() {
254: BufferedImage result = null;
255: // XXX multi-monitors w/ different resolution may have problems; Better
256: // to call Toolkit to create a screen graphics
257: if (ctx != null) {
258: result = ctx.get();
259: }
260: if (result == null) {
261: result = new BufferedImage(10, 10,
262: BufferedImage.TYPE_INT_RGB);
263: ctx = new SoftReference<BufferedImage>(result);
264: }
265: return (Graphics2D) result.getGraphics();
266: }
267:
268: /**
269: * Overridden to calculate a preferred size based on the current optimal
270: * number of columns, and set up the preferred width for each column based
271: * on the maximum width item & icon displayed in it
272: */
273: public Dimension getPreferredSize() {
274: if (prefSize == null) {
275: int cols = getColumnCount();
276: int rows = getRowCount();
277:
278: // Iterate all rows and find the widest cell of a whole table
279: int columnWidth = 0;
280: for (int i = 0; i < cols; i++) {
281: for (int j = 0; j < rows; j++) {
282: TableCellRenderer ren = getCellRenderer(j, i);
283: Component c = prepareRenderer(ren, j, i);
284: // sometime adding of one pixel is needed to prevent "..." truncating
285: columnWidth = Math
286: .max(c.getPreferredSize().width + 1,
287: columnWidth);
288: }
289: }
290: columnWidth = Math.min(columnWidth, 250);
291: // Set the same (maximum) widht to all columns
292: for (int i = 0; i < cols; i++) {
293: getColumnModel().getColumn(i).setPreferredWidth(
294: columnWidth);
295: }
296: // Rows will be fixed height, so just multiply it out
297: prefSize = new Dimension(columnWidth * cols, rows
298: * getRowHeight());
299: }
300: return prefSize;
301: }
302:
303: private SwitcherTableModel getSwitcherTableModel() {
304: return (SwitcherTableModel) getModel();
305: }
306:
307: public SwitcherTableItem getSelectedItem() {
308: return (SwitcherTableItem) getValueAt(getSelectedRow(),
309: getSelectedColumn());
310: }
311:
312: public void paint(Graphics g) {
313: if (needCalcRowHeight) {
314: calcRowHeight(g);
315: }
316: super .paint(g);
317: }
318:
319: /**
320: * Returns the last valid row in the last collumn.
321: *
322: * @return index of last non-null value in the last collumn or -1 when all
323: * values are null.
324: */
325: public int getLastValidRow() {
326: int lastColIdx = getColumnCount() - 1;
327: for (int i = getRowCount() - 1; i >= 0; i--) {
328: if (getValueAt(i, lastColIdx) != null) {
329: return i;
330: }
331: }
332: return -1;
333: }
334: }
|