001: /*
002: * Copyright (c) 2001-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.looks.windows;
032:
033: import java.awt.FontMetrics;
034: import java.awt.Graphics;
035: import java.awt.Insets;
036: import java.awt.Rectangle;
037: import java.beans.PropertyChangeEvent;
038: import java.beans.PropertyChangeListener;
039:
040: import javax.swing.Icon;
041: import javax.swing.JComponent;
042: import javax.swing.SwingConstants;
043: import javax.swing.SwingUtilities;
044: import javax.swing.plaf.ComponentUI;
045: import javax.swing.plaf.basic.BasicGraphicsUtils;
046: import javax.swing.plaf.basic.BasicTabbedPaneUI;
047: import javax.swing.text.View;
048:
049: import com.jgoodies.looks.LookUtils;
050: import com.jgoodies.looks.Options;
051:
052: /**
053: * The JGoodies Windows L&F implementation of <code>TabbedPaneUI</code>.<p>
054: *
055: * The flat appearance is work in progress; currently it works only
056: * for a single line of tabs and paints distored tabs for multiple lines.
057: *
058: * @author Karsten Lentzsch
059: * @version $Revision: 1.5 $
060: */
061: public final class WindowsTabbedPaneUI extends
062: com.sun.java.swing.plaf.windows.WindowsTabbedPaneUI {
063:
064: private static final boolean IS_XP_LAF_5_OR_LATER = LookUtils.IS_JAVA_5_OR_LATER
065: && LookUtils.IS_LAF_WINDOWS_XP_ENABLED;
066:
067: /** Insets used for the embedded style content border. */
068: private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
069:
070: /** Insets used if we paint no content border. */
071: private static final int INSET = IS_XP_LAF_5_OR_LATER ? -1 : 1;
072: private static final Insets NO_CONTENT_BORDER_NORTH_INSETS = new Insets(
073: INSET, 0, 0, 0);
074: private static final Insets NO_CONTENT_BORDER_WEST_INSETS = new Insets(
075: 0, INSET, 0, 0);
076: private static final Insets NO_CONTENT_BORDER_SOUTH_INSETS = new Insets(
077: 0, 0, INSET, 0);
078: private static final Insets NO_CONTENT_BORDER_EAST_INSETS = new Insets(
079: 0, 0, 0, INSET);
080:
081: /** Insets used if we paint content border. */
082: private static final Insets CONTENT_BORDER_NORTH_INSETS = new Insets(
083: 0, 2, 4, 4);
084: private static final Insets CONTENT_BORDER_WEST_INSETS = new Insets(
085: 2, 0, 4, 4);
086: private static final Insets CONTENT_BORDER_SOUTH_INSETS = new Insets(
087: 4, 2, 0, 4);
088: private static final Insets CONTENT_BORDER_EAST_INSETS = new Insets(
089: 2, 4, 4, 0);
090:
091: /**
092: * Describes if tabs are painted with or without icons.
093: */
094: private static boolean isTabIconsEnabled = Options
095: .isTabIconsEnabled();
096:
097: /**
098: * Describes if we paint no content border or not; this is false by default.
099: * You can disable the content border by setting the client property
100: * Options.NO_CONTENT_BORDER_KEY to Boolean.TRUE;
101: */
102: private Boolean noContentBorder;
103:
104: /**
105: * Describes if we paint tabs in an embedded style that is with
106: * less decoration; this is false by default.
107: * You can enable the embedded tabs style by setting the client property
108: * Options.EMBEDDED_TABS_KEY to Boolean.TRUE.
109: */
110: private Boolean embeddedTabs;
111:
112: /**
113: * Creates and answers the <code>WindowsTabbedPaneUI</code>.
114: *
115: * @see javax.swing.plaf.ComponentUI#createUI(JComponent)
116: */
117: public static ComponentUI createUI(JComponent x) {
118: return new WindowsTabbedPaneUI();
119: }
120:
121: /**
122: * Installs the UI.
123: *
124: * @see javax.swing.plaf.ComponentUI#installUI(JComponent)
125: */
126: public void installUI(JComponent c) {
127: super .installUI(c);
128: embeddedTabs = (Boolean) c
129: .getClientProperty(Options.EMBEDDED_TABS_KEY);
130: noContentBorder = (Boolean) c
131: .getClientProperty(Options.NO_CONTENT_BORDER_KEY);
132: }
133:
134: /**
135: * Checks and answers if content border will be painted.
136: * This is controlled by the component's client property
137: * Options.NO_CONTENT_BORDER or Options.EMBEDDED.
138: */
139: private boolean hasNoContentBorder() {
140: return hasEmbeddedTabs()
141: || Boolean.TRUE.equals(noContentBorder);
142: }
143:
144: /**
145: * Checks and answers if tabs are painted with minimal decoration.
146: */
147: private boolean hasEmbeddedTabs() {
148: return embeddedTabs == null ? false : embeddedTabs
149: .booleanValue();
150: }
151:
152: /**
153: * Creates and answer a handler that listens to property changes.
154: * Unlike the superclass BasicTabbedPane, the PlasticTabbedPaneUI
155: * uses an extended Handler.
156: */
157: protected PropertyChangeListener createPropertyChangeListener() {
158: return new MyPropertyChangeHandler();
159: }
160:
161: private void doLayout() {
162: tabPane.revalidate();
163: tabPane.repaint();
164: }
165:
166: /**
167: * Updates the embedded tabs property. This message is sent by
168: * my PropertyChangeHandler whenever the embedded tabs property changes.
169: */
170: private void embeddedTabsPropertyChanged(Boolean newValue) {
171: embeddedTabs = newValue;
172: doLayout();
173: }
174:
175: /**
176: * Updates the no content border property. This message is sent
177: * by my PropertyChangeHandler whenever the noContentBorder
178: * property changes.
179: */
180: private void noContentBorderPropertyChanged(Boolean newValue) {
181: noContentBorder = newValue;
182: doLayout();
183: }
184:
185: /**
186: * Answers the icon for the tab with the specified index.
187: * In case, we have globally switched of the use tab icons,
188: * we answer <code>null</code> if and only if we have a title.
189: */
190: protected Icon getIconForTab(int tabIndex) {
191: String title = tabPane.getTitleAt(tabIndex);
192: boolean hasTitle = (title != null) && (title.length() > 0);
193: return !isTabIconsEnabled && hasTitle ? null : super
194: .getIconForTab(tabIndex);
195: }
196:
197: protected Insets getContentBorderInsets(int tabPlacement) {
198: if (!hasNoContentBorder()) {
199: if (IS_XP_LAF_5_OR_LATER) {
200: switch (tabPlacement) {
201: case RIGHT:
202: return CONTENT_BORDER_EAST_INSETS;
203: case LEFT:
204: return CONTENT_BORDER_WEST_INSETS;
205: case TOP:
206: return CONTENT_BORDER_NORTH_INSETS;
207: case BOTTOM:
208: default:
209: return CONTENT_BORDER_SOUTH_INSETS;
210: }
211: }
212: return contentBorderInsets;
213: } else if (hasEmbeddedTabs()) {
214: return EMPTY_INSETS;
215: } else {
216: switch (tabPlacement) {
217: case RIGHT:
218: return NO_CONTENT_BORDER_EAST_INSETS;
219: case LEFT:
220: return NO_CONTENT_BORDER_WEST_INSETS;
221: case TOP:
222: return NO_CONTENT_BORDER_NORTH_INSETS;
223: case BOTTOM:
224: default:
225: return NO_CONTENT_BORDER_SOUTH_INSETS;
226: }
227: }
228: }
229:
230: protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
231: boolean isSelected) {
232: switch (tabPlacement) {
233: case RIGHT:
234: return isSelected ? 2 : 0;
235: case LEFT:
236: return isSelected ? -2 : 0;
237: case TOP:
238: case BOTTOM:
239: default:
240: return 0;
241: }
242: }
243:
244: protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
245: boolean isSelected) {
246: return 0;
247: }
248:
249: protected Insets getSelectedTabPadInsets(int tabPlacement) {
250: if (hasEmbeddedTabs()) {
251: return EMPTY_INSETS;
252: } else if (hasNoContentBorder()) {
253: int inset = IS_XP_LAF_5_OR_LATER ? 1 : 0;
254: switch (tabPlacement) {
255: case LEFT:
256: return new Insets(1, 2, 1, inset);
257: case RIGHT:
258: return new Insets(1, inset, 1, 2);
259: case TOP:
260: return new Insets(2, 2, inset, 2);
261: case BOTTOM:
262: return new Insets(inset, 2, 2, 2);
263: default:
264: return EMPTY_INSETS;
265: }
266: } else {
267: Insets super Insets = super
268: .getSelectedTabPadInsets(tabPlacement);
269: int equalized = super Insets.left + super Insets.right / 2;
270: super Insets.left = super Insets.right = equalized;
271: return super Insets;
272: }
273: }
274:
275: protected Insets getTabAreaInsets(int tabPlacement) {
276: return hasEmbeddedTabs() ? /*new Insets(1,1,1,1)*/EMPTY_INSETS
277: : super .getTabAreaInsets(tabPlacement);
278: }
279:
280: /**
281: * Paints the top edge of the pane's content border.
282: */
283: protected void paintContentBorderTopEdge(Graphics g,
284: int tabPlacement, int selectedIndex, int x, int y, int w,
285: int h) {
286: if (hasNoContentBorder() && tabPlacement != TOP) {
287: return;
288: }
289: Rectangle selRect = selectedIndex < 0 ? null : getTabBounds(
290: selectedIndex, calcRect);
291: if (tabPlacement != TOP || selectedIndex < 0
292: || (selRect.y + selRect.height + 1 < y)
293: || (selRect.x < x || selRect.x > x + w)) {
294: // no special case, do the super thing
295: super .paintContentBorderTopEdge(g, tabPlacement,
296: selectedIndex, x, y, w, h);
297: } else {
298: g.setColor(lightHighlight);
299: g.fillRect(x, y, selRect.x + 1 - x, 1);
300: g.fillRect(selRect.x + selRect.width, y, x + w - 2
301: - selRect.x - selRect.width, 1);
302: }
303: }
304:
305: /**
306: * Paints the bottom edge of the pane's content border.
307: */
308: protected void paintContentBorderBottomEdge(Graphics g,
309: int tabPlacement, int selectedIndex, int x, int y, int w,
310: int h) {
311: if (!hasNoContentBorder()) {
312: Rectangle selRect = selectedIndex < 0 ? null
313: : getTabBounds(selectedIndex, calcRect);
314: if (tabPlacement != BOTTOM || selectedIndex < 0
315: || (selRect.y - 1 > h + y)
316: || (selRect.x < x || selRect.x > x + w)) {
317: // no special case, do the super thing
318: super .paintContentBorderBottomEdge(g, tabPlacement,
319: selectedIndex, x, y, w, h);
320: } else {
321: g.setColor(lightHighlight);
322: g.fillRect(x, y + h - 1, 1, 1);
323: g.setColor(shadow);
324: g.fillRect(x + 1, y + h - 2, selRect.x - 1 - x, 1);
325: g.fillRect(selRect.x + selRect.width, y + h - 2, x + w
326: - 2 - selRect.x - selRect.width, 1);
327: g.setColor(darkShadow);
328: g.fillRect(x, y + h - 1, selRect.x - x, 1);
329: g.fillRect(selRect.x + selRect.width - 1, y + h - 1, x
330: + w - selRect.x - selRect.width, 1);
331: }
332: } else if (!(tabPlacement == BOTTOM)) {
333: // no content border really means only one content border:
334: // the one edge that touches the tabs
335: } else {
336: g.setColor(shadow);
337: g.fillRect(x, y + h, w, 1);
338: }
339: }
340:
341: /**
342: * Paints the left Edge of the pane's content border.
343: */
344: protected void paintContentBorderLeftEdge(Graphics g,
345: int tabPlacement, int selectedIndex, int x, int y, int w,
346: int h) {
347: if (!hasNoContentBorder()) {
348: Rectangle selRect = selectedIndex < 0 ? null
349: : getTabBounds(selectedIndex, calcRect);
350: if (tabPlacement != LEFT || selectedIndex < 0
351: || (selRect.x + selRect.width + 1 < x)
352: || (selRect.y < y || selRect.y > y + h)) {
353: // no special case, do the super thing
354: super .paintContentBorderLeftEdge(g, tabPlacement,
355: selectedIndex, x, y, w, h);
356: } else {
357: g.setColor(lightHighlight);
358: g.fillRect(x, y, 1, selRect.y + 1 - y);
359: g.fillRect(x, selRect.y + selRect.height, 1, y + h - 1
360: - selRect.y - selRect.height);
361:
362: }
363: } else if (!(tabPlacement == LEFT)) {
364: // no content border really means only one content border:
365: // the one edge that touches the tabs
366: } else {
367: g.setColor(shadow);
368: g.fillRect(x, y, 1, h);
369: }
370: }
371:
372: /**
373: * Paints the right Edge of the pane's content border.
374: */
375: protected void paintContentBorderRightEdge(Graphics g,
376: int tabPlacement, int selectedIndex, int x, int y, int w,
377: int h) {
378: if (!hasNoContentBorder()) {
379: Rectangle selRect = selectedIndex < 0 ? null
380: : getTabBounds(selectedIndex, calcRect);
381: if (tabPlacement != RIGHT || selectedIndex < 0
382: || (selRect.x - 1 > x + w)
383: || (selRect.y < y || selRect.y > y + h)) {
384: // no special case, do the super thing
385: super .paintContentBorderRightEdge(g, tabPlacement,
386: selectedIndex, x, y, w, h);
387: } else {
388: g.setColor(lightHighlight);
389: g.fillRect(x + w - 1, y, 1, 1);
390: g.setColor(shadow);
391: g.fillRect(x + w - 2, y + 1, 1, selRect.y - 1 - y);
392: g.fillRect(x + w - 2, selRect.y + selRect.height, 1, y
393: + h - 1 - selRect.y - selRect.height);
394: g.setColor(darkShadow);
395: g.fillRect(x + w - 1, y, 1, selRect.y - y);
396: g.fillRect(x + w - 1, selRect.y + selRect.height - 1,
397: 1, y + h - selRect.y - selRect.height);
398:
399: }
400: } else if (!(tabPlacement == RIGHT)) {
401: // no content border really means only one content border:
402: // the one edge that touches the tabs
403: } else {
404: g.setColor(shadow);
405: g.fillRect(x + w, y, 1, h);
406: }
407: }
408:
409: /**
410: * Paints the border for a single tab; it does not paint the tab's background.
411: */
412: protected void paintTabBorder(Graphics g, int tabPlacement,
413: int tabIndex, int x, int y, int w, int h, boolean isSelected) {
414: if (!hasEmbeddedTabs()) {
415: super .paintTabBorder(g, tabPlacement, tabIndex, x, y, w, h,
416: isSelected);
417: return;
418: }
419: g.translate(x - 1, y - 1);
420: int w1, w2, w3;
421: int h1, h2, h3;
422: switch (tabPlacement) {
423: case TOP:
424: w1 = 1;
425: w2 = w - 2;
426: w3 = 1;
427: h1 = 1;
428: h2 = h - 1;
429: h3 = 0;
430: break;
431: case BOTTOM:
432: w1 = 1;
433: w2 = w - 2;
434: w3 = 1;
435: h1 = 0;
436: h2 = h - 1;
437: h3 = 1;
438: break;
439: case LEFT:
440: w1 = 1;
441: w2 = w - 1;
442: w3 = 0;
443: h1 = 1;
444: h2 = h - 3;
445: h3 = 1;
446: break;
447: case RIGHT:
448: default:
449: w1 = 0;
450: w2 = w - 1;
451: w3 = 1;
452: h1 = 1;
453: h2 = h - 3;
454: h3 = 1;
455: }
456: if (isSelected) {
457: g.setColor(lightHighlight);
458: g.drawRect(w1, h1, w1 + w2 + w3, h1 + h2 + h3);
459: g.setColor(shadow);
460: g.fillRect(1 + w1, 0, w2, h1);
461: g.fillRect(0, 1 + h1, w1, h2);
462: g.fillRect(2 * w1 + w2 + 2 * w3, 1 + h1, w3, h2);
463: g.fillRect(1 + w1, 2 * h1 + h2 + 2 * h3, w2, h3);
464: g.fillRect(1, 1, w1, h1);
465: g.fillRect(2 * w1 + w2 + w3, 1, w3, h1);
466: g.fillRect(1, 2 * h1 + h2 + h3, w1, h3);
467: g.fillRect(2 * w1 + w2 + w3, 2 * h1 + h2 + h3, w3, h3);
468: } else {
469: g.setColor(shadow);
470: g.fillRect(w1 + w2 + 2 * w3, h3 * h2 / 2, w3, h2 * 2 / 3);
471: g.fillRect(w3 * w2 / 2, h1 + h2 + 2 * h3, w2 / 2 + 2, h3);
472: }
473: g.translate(-x + 1, -y + 1);
474: }
475:
476: protected void paintFocusIndicator(Graphics g, int tabPlacement,
477: Rectangle[] rectangles, int tabIndex, Rectangle iconRect,
478: Rectangle textRect, boolean isSelected) {
479: if (!hasEmbeddedTabs()) {
480: super .paintFocusIndicator(g, tabPlacement, rectangles,
481: tabIndex, iconRect, textRect, isSelected);
482: return;
483: }
484: if (tabPane.hasFocus() && isSelected) {
485: g.setColor(focus);
486: BasicGraphicsUtils.drawDashedRect(g, textRect.x - 2,
487: textRect.y, textRect.width + 3, textRect.height);
488: }
489: }
490:
491: protected boolean shouldRotateTabRuns(int tabPlacement) {
492: return !hasEmbeddedTabs();
493: }
494:
495: /**
496: * Copied here from super(super)class to avoid labels being centered on
497: * vertical tab runs if they consist of icon and text.
498: */
499: protected void layoutLabel(int tabPlacement, FontMetrics metrics,
500: int tabIndex, String title, Icon icon, Rectangle tabRect,
501: Rectangle iconRect, Rectangle textRect, boolean isSelected) {
502: textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
503:
504: //fix of issue #4
505: View v = getTextViewForTab(tabIndex);
506: if (v != null) {
507: tabPane.putClientProperty("html", v);
508: }
509:
510: int xNudge = getTabLabelShiftX(tabPlacement, tabIndex,
511: isSelected);
512: int yNudge = getTabLabelShiftY(tabPlacement, tabIndex,
513: isSelected);
514: if ((tabPlacement == RIGHT || tabPlacement == LEFT)
515: && icon != null && title != null && !title.equals("")) {
516: /* vertical tab runs look ugly if icons and text are centered */
517: SwingUtilities.layoutCompoundLabel(tabPane, metrics, title,
518: icon, SwingConstants.CENTER, SwingConstants.LEFT,
519: SwingConstants.CENTER, SwingConstants.TRAILING,
520: tabRect, iconRect, textRect, textIconGap);
521: xNudge += 4;
522: } else { /* original superclass behavior */
523: SwingUtilities.layoutCompoundLabel(tabPane, metrics, title,
524: icon, SwingConstants.CENTER, SwingConstants.CENTER,
525: SwingConstants.CENTER, SwingConstants.TRAILING,
526: tabRect, iconRect, textRect, textIconGap);
527: }
528:
529: //fix of issue #4
530: tabPane.putClientProperty("html", null);
531:
532: iconRect.x += xNudge;
533: iconRect.y += yNudge;
534: textRect.x += xNudge;
535: textRect.y += yNudge;
536: }
537:
538: /**
539: * Catches and handles property change events. In addition to the super
540: * class behavior we listen to changes of the ancestor, tab placement,
541: * and JGoodies options for content border, and embedded tabs.
542: */
543: private final class MyPropertyChangeHandler extends
544: BasicTabbedPaneUI.PropertyChangeHandler {
545:
546: public void propertyChange(PropertyChangeEvent e) {
547: super .propertyChange(e);
548:
549: String pName = e.getPropertyName();
550: if (null == pName) {
551: return;
552: }
553: if (pName.equals(Options.EMBEDDED_TABS_KEY)) {
554: embeddedTabsPropertyChanged((Boolean) e.getNewValue());
555: return;
556: }
557: if (pName.equals(Options.NO_CONTENT_BORDER_KEY)) {
558: noContentBorderPropertyChanged((Boolean) e
559: .getNewValue());
560: return;
561: }
562:
563: }
564:
565: }
566:
567: }
|