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: * BasicSlidingTabDisplayerUI.java
042: *
043: * Created on March 27, 2004, 7:14 AM
044: */
045:
046: package org.netbeans.swing.tabcontrol.plaf;
047:
048: import org.netbeans.swing.tabcontrol.TabDisplayer;
049:
050: import javax.swing.*;
051: import javax.swing.event.ChangeEvent;
052: import javax.swing.event.ChangeListener;
053: import javax.swing.plaf.ButtonUI;
054: import javax.swing.plaf.ComponentUI;
055: import java.awt.*;
056: import java.awt.event.*;
057: import java.awt.image.BufferedImage;
058: import java.beans.PropertyChangeEvent;
059: import java.util.Arrays;
060: import java.util.Comparator;
061: import org.netbeans.swing.tabcontrol.TabData;
062:
063: /** Common UI for sliding tabs. Simply uses JToggleButtons for displayers,
064: * since the contents of the data model are not expected to change often,
065: * and no scrolling behavior needs to be supported for the tabs area.
066: * <p>
067: * Note that the "sliding" is provided by an instance of <code>FxProvider</code>
068: * provided in the <code>DefaultTabbedContainerUI</code>, not here.
069: * <p>
070: * To change the appearance of the buttons, simply provide a subclass of <code>SlidingTabDisplayerButtonUI</code>
071: * via UIDefaults. This class is final.
072: *
073: * @author Tim Boudreau
074: */
075: public final class BasicSlidingTabDisplayerUI extends
076: AbstractTabDisplayerUI {
077: private Rectangle scratch = new Rectangle();
078:
079: /** Creates a new instance of BasicSlidingTabDisplayerUI */
080: public BasicSlidingTabDisplayerUI(TabDisplayer displayer) {
081: super (displayer);
082: }
083:
084: public static ComponentUI createUI(JComponent c) {
085: return new BasicSlidingTabDisplayerUI((TabDisplayer) c);
086: }
087:
088: protected void install() {
089: displayer.setLayout(new OrientedLayoutManager());
090: syncButtonsWithModel();
091: }
092:
093: protected Font createFont() {
094: //XXX Sideways text is more readable with a slightly larger bold font
095: Font f = super .createFont();
096: // don't use deriveFont() - see #49973 for details
097: f = new Font(f.getName(), Font.BOLD, f.getSize() + 1);
098: return f;
099: }
100:
101: protected void uninstall() {
102: displayer.removeAll();
103: }
104:
105: public Dimension getPreferredSize(JComponent c) {
106: return displayer.getLayout().preferredLayoutSize(c);
107: }
108:
109: public Dimension getMinimumSize(JComponent c) {
110: return displayer.getLayout().minimumLayoutSize(c);
111: }
112:
113: private int buttonCount = 0;
114:
115: private boolean syncButtonsWithModel() {
116: assert SwingUtilities.isEventDispatchThread();
117:
118: int count = displayer.getModel().size();
119: boolean changed = false;
120: buttonCount = displayer.getComponentCount();
121:
122: if (count != buttonCount) {
123: synchronized (displayer.getTreeLock()) {
124: while (count < buttonCount) {
125: if (buttonCount-- > 0) {
126: displayer.remove(buttonCount - 1);
127: changed = true;
128: }
129: }
130: while (count > buttonCount) {
131: IndexButton ib = new IndexButton(buttonCount++);
132: ib.setFont(displayer.getFont());
133: displayer.add(ib);
134: changed = true;
135: }
136: Component[] c = displayer.getComponents();
137: for (int i = 0; i < c.length; i++) {
138: if (c[i] instanceof IndexButton) {
139: changed |= ((IndexButton) c[i]).checkChanged();
140: }
141: }
142: }
143: }
144: return changed;
145: }
146:
147: /** Not used so much to determine layout as to calculate preferred sizes
148: * here
149: */
150: protected TabLayoutModel createLayoutModel() {
151: DefaultTabLayoutModel result = new DefaultTabLayoutModel(
152: displayer.getModel(), displayer);
153: result.setPadding(new Dimension(15, 2));
154: return result;
155: }
156:
157: protected MouseListener createMouseListener() {
158: return new MouseAdapter() {
159: };
160: }
161:
162: public void requestAttention(int tab) {
163: //not implemented
164: }
165:
166: public void cancelRequestAttention(int tab) {
167: //not implemented
168: }
169:
170: protected ChangeListener createSelectionListener() {
171: return new ChangeListener() {
172: private int lastKnownSelection = -1;
173:
174: public void stateChanged(ChangeEvent ce) {
175: int selection = selectionModel.getSelectedIndex();
176: if (selection != lastKnownSelection) {
177: if (lastKnownSelection != -1) {
178: IndexButton last = findButtonFor(lastKnownSelection);
179: if (last != null) {
180: last.getModel().setSelected(false);
181: }
182: }
183: if (selection != -1) {
184: IndexButton current = findButtonFor(selection);
185: if (displayer.getComponentCount() == 0) {
186: syncButtonsWithModel();
187: }
188: if (current != null) {
189: current.getModel().setSelected(true);
190: }
191: }
192: }
193: lastKnownSelection = selection;
194: }
195: };
196: }
197:
198: public Polygon getExactTabIndication(int index) {
199: return new EqualPolygon(findButtonFor(index).getBounds());
200: }
201:
202: public Polygon getInsertTabIndication(int index) {
203: Rectangle r = findButtonFor(index).getBounds();
204: Polygon result = new EqualPolygon(findButtonFor(index)
205: .getBounds());
206: return result;
207: }
208:
209: private IndexButton findButtonFor(int index) {
210: Component[] c = displayer.getComponents();
211: for (int i = 0; i < c.length; i++) {
212: if (c[i] instanceof IndexButton
213: && ((IndexButton) c[i]).getIndex() == index) {
214: return (IndexButton) c[i];
215: }
216: }
217: return null;
218: }
219:
220: public Rectangle getTabRect(int index, Rectangle destination) {
221: if (destination == null) {
222: destination = new Rectangle();
223: }
224: IndexButton ib = findButtonFor(index);
225: if (ib != null) {
226: destination.setBounds(ib.getBounds());
227: } else {
228: destination.setBounds(-20, -20, 0, 0);
229: }
230: return destination;
231: }
232:
233: public int tabForCoordinate(Point p) {
234: Component[] c = displayer.getComponents();
235: for (int i = 0; i < c.length; i++) {
236: if (c[i] instanceof IndexButton) {
237: if (c[i].contains(p)) {
238: return ((IndexButton) c[i]).getIndex();
239: }
240: }
241: }
242: return -1;
243: }
244:
245: protected final class SlidingPropertyChangeListener extends
246: AbstractTabDisplayerUI.DisplayerPropertyChangeListener {
247: public void propertyChange(PropertyChangeEvent e) {
248: super .propertyChange(e);
249: if (TabDisplayer.PROP_ORIENTATION.equals(e
250: .getPropertyName())) {
251: displayer.revalidate();
252: }
253: }
254: }
255:
256: private Object getDisplayerOrientation() {
257: return displayer
258: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
259: }
260:
261: /** Paints the rectangle occupied by a tab into an image and returns the result */
262: public Image createImageOfTab(int index) {
263: TabData td = displayer.getModel().getTab(index);
264:
265: JLabel lbl = new JLabel(td.getText());
266: int width = lbl.getFontMetrics(lbl.getFont()).stringWidth(
267: td.getText());
268: int height = lbl.getFontMetrics(lbl.getFont()).getHeight();
269: width = width + td.getIcon().getIconWidth() + 6;
270: height = Math.max(height, td.getIcon().getIconHeight()) + 5;
271:
272: GraphicsConfiguration config = GraphicsEnvironment
273: .getLocalGraphicsEnvironment().getDefaultScreenDevice()
274: .getDefaultConfiguration();
275:
276: BufferedImage image = config.createCompatibleImage(width,
277: height);
278: Graphics2D g = image.createGraphics();
279: g.setColor(lbl.getForeground());
280: g.setFont(lbl.getFont());
281: td.getIcon().paintIcon(lbl, g, 0, 0);
282: g.drawString(td.getText(), 18, height / 2);
283:
284: return image;
285:
286: }
287:
288: /**
289: * JToggleButton subclass which maps to an index in the data model, and displays
290: * whatever the content of the data model at that index is. Buttons are added or removed
291: * from the tab displayer as the model changes. This class is public to allow
292: * alternate UIs for the buttons to be provided via subclasses of <code>SlidingTabDisplayerButtonUI</code>.
293: */
294: public final class IndexButton extends JToggleButton implements
295: ActionListener {
296: private int index;
297: private String lastKnownText = null;
298: private Icon lastKnownIcon = null;
299:
300: /** UI Class ID for IndexButtons, to be used by providers of UI delegates */
301: public static final String UI_KEY = "IndexButtonUI"; //NOI18N
302:
303: /** Create a new button representing an index in the model. The index is immutable for the life of the
304: * button.
305: *
306: * @param index The index
307: */
308: public IndexButton(int index) {
309: this .index = index;
310: addActionListener(this );
311: setFont(displayer.getFont());
312: setFocusable(false);
313: }
314:
315: public void addNotify() {
316: super .addNotify();
317: ToolTipManager.sharedInstance().registerComponent(this );
318: }
319:
320: public void removeNotify() {
321: super .removeNotify();
322: ToolTipManager.sharedInstance().unregisterComponent(this );
323: }
324:
325: /** Accessor for the UI delegate to determine if the tab displayer is currently active */
326: public boolean isActive() {
327: return displayer.isActive();
328: }
329:
330: public void updateUI() {
331: SlidingTabDisplayerButtonUI ui = null;
332: try {
333: ui = (SlidingTabDisplayerButtonUI) UIManager
334: .getUI(this );
335: setUI(ui);
336: return;
337: } catch (Error e) {
338: System.err.println("Error getting sliding button UI: "
339: + e.getMessage());
340: } catch (Exception ex) {
341: System.err.println("Exception getting button UI: "
342: + ex.getMessage());
343: }
344: setUI((ButtonUI) SlidingTabDisplayerButtonUI.createUI(this ));
345: }
346:
347: public String getUIClassID() {
348: return UI_KEY;
349: }
350:
351: /** Accessor for the UI delegate - orientation will be one of the constants defined on
352: * TabDisplayer */
353: public Object getOrientation() {
354: return getDisplayerOrientation();
355: }
356:
357: public String getText() {
358: if (index == -1) {
359: //We're being called in the superclass constructor when the UI is
360: //assigned
361: return "";
362: }
363: if (index < displayer.getModel().size()) {
364: lastKnownText = displayer.getModel().getTab(index)
365: .getText();
366: } else {
367: return "This tab doesn't exist."; //NOI18N
368: }
369: return lastKnownText;
370: }
371:
372: public String getToolTipText() {
373: return displayer.getModel().getTab(index).getTooltip();
374: }
375:
376: /** Implementation of ActionListener - sets the selected index in the selection model */
377: public final void actionPerformed(ActionEvent e) {
378: if (!isSelected()) {
379: selectionModel.setSelectedIndex(-1);
380: } else {
381: selectionModel.setSelectedIndex(index);
382: }
383: }
384:
385: /** Get the index into the data model that this button represents */
386: public int getIndex() {
387: return index;
388: }
389:
390: public Icon getIcon() {
391: if (index == -1) {
392: //We're being called in the superclass constructor when the UI is
393: //assigned
394: return null;
395: }
396: if (index < displayer.getModel().size()) {
397: lastKnownIcon = displayer.getModel().getTab(index)
398: .getIcon();
399: }
400: return lastKnownIcon;
401: }
402:
403: /**
404: * Test if the text or icon in the model has changed since the last time <code>getText()</code> or
405: * <code>getIcon()</code> was called. If a change has occured, the button will fire the appropriate
406: * property changes, including preferred size, to ensure the tab displayer is re-laid out correctly.
407: * This method is called when a change happens in the model over the index this button represents.
408: *
409: * @return true if something has changed
410: */
411: final boolean checkChanged() {
412: boolean result = false;
413: Icon ic = lastKnownIcon;
414: Icon nue = getIcon();
415: if (nue != ic) {
416: firePropertyChange("icon", lastKnownIcon, nue); //NOI18N
417: result = true;
418: }
419: String txt = lastKnownText;
420: String nu = getText();
421: if (nu != txt) { //Equality compare probably not needed
422: firePropertyChange("text", lastKnownText, getText()); //NOI18N
423: result = true;
424: }
425: if (result) {
426: firePropertyChange("preferredSize", null, null); //NOI18N
427: }
428: return result;
429: }
430: }
431:
432: protected void modelChanged() {
433: if (syncButtonsWithModel()) {
434: displayer.validate();
435: }
436: }
437:
438: public Icon getButtonIcon(int buttonId, int buttonState) {
439: return null;
440: }
441:
442: private static final Comparator<Component> BUTTON_COMPARATOR = new IndexButtonComparator();
443:
444: private static class IndexButtonComparator implements
445: Comparator<Component> {
446: public int compare(Component o1, Component o2) {
447: if (o2 instanceof IndexButton && o1 instanceof IndexButton) {
448: return ((IndexButton) o1).getIndex()
449: - ((IndexButton) o2).getIndex();
450: }
451: return 0;
452: }
453: }
454:
455: private final class OrientedLayoutManager implements LayoutManager {
456: public void addLayoutComponent(String name, Component comp) {
457: //do nothing
458: }
459:
460: public void layoutContainer(Container parent) {
461: synchronized (parent.getTreeLock()) {
462: syncButtonsWithModel();
463: Component[] c = parent.getComponents();
464: Arrays.sort(c, BUTTON_COMPARATOR);
465: for (int i = 0; i < c.length; i++) {
466: if (c[i] instanceof IndexButton) {
467: boundsFor((IndexButton) c[i], scratch);
468: c[i].setBounds(scratch);
469: }
470: }
471: }
472: }
473:
474: private void boundsFor(IndexButton b, Rectangle r) {
475: Object orientation = getDisplayerOrientation();
476: boolean flip = orientation == TabDisplayer.ORIENTATION_EAST
477: || orientation == TabDisplayer.ORIENTATION_WEST;
478: int index = b.getIndex();
479:
480: if (index >= displayer.getModel().size() || index < 0) {
481: r.setBounds(-20, -20, 0, 0);
482: return;
483: }
484:
485: r.x = layoutModel.getX(index);
486: r.y = layoutModel.getY(index);
487: r.width = layoutModel.getW(index);
488: r.height = layoutModel.getH(index);
489: if (flip) {
490: int tmp = r.x;
491: r.x = r.y;
492: r.y = tmp;
493:
494: tmp = r.width;
495: r.width = r.height;
496: r.height = tmp;
497: }
498: }
499:
500: public Dimension minimumLayoutSize(Container parent) {
501: return preferredLayoutSize(parent);
502: }
503:
504: public Dimension preferredLayoutSize(Container parent) {
505: Object orientation = getDisplayerOrientation();
506: boolean flip = orientation == TabDisplayer.ORIENTATION_EAST
507: || orientation == TabDisplayer.ORIENTATION_WEST;
508:
509: int max = displayer.getModel().size();
510: Dimension result = new Dimension();
511: for (int i = 0; i < max; i++) {
512: result.height = Math.max(result.height, layoutModel
513: .getH(i));
514: result.width += layoutModel.getW(i);
515: }
516: if (flip) {
517: int tmp = result.height;
518: result.height = result.width;
519: result.width = tmp;
520: }
521: return result;
522: }
523:
524: public void removeLayoutComponent(Component comp) {
525: //do nothing
526: }
527: }
528: }
|