001 /*
002 * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.plaf.synth;
027
028 import javax.swing.*;
029 import javax.swing.event.*;
030 import javax.swing.plaf.*;
031 import javax.swing.plaf.basic.*;
032 import javax.swing.text.View;
033
034 import java.awt.*;
035 import java.awt.event.*;
036 import java.beans.PropertyChangeListener;
037 import java.beans.PropertyChangeEvent;
038 import java.util.Vector;
039 import java.util.Hashtable;
040 import sun.swing.plaf.synth.SynthUI;
041 import sun.swing.SwingUtilities2;
042
043 /**
044 * A Synth L&F implementation of TabbedPaneUI.
045 *
046 * @version 1.44, 05/05/07
047 * @author Scott Violet
048 */
049 /**
050 * Looks up 'selectedTabPadInsets' from the Style, which will be additional
051 * insets for the selected tab.
052 */
053 class SynthTabbedPaneUI extends BasicTabbedPaneUI implements SynthUI,
054 PropertyChangeListener {
055 private SynthContext tabAreaContext;
056 private SynthContext tabContext;
057 private SynthContext tabContentContext;
058
059 private SynthStyle style;
060 private SynthStyle tabStyle;
061 private SynthStyle tabAreaStyle;
062 private SynthStyle tabContentStyle;
063
064 private Rectangle textRect;
065 private Rectangle iconRect;
066
067 private Rectangle tabAreaBounds = new Rectangle();
068
069 public static ComponentUI createUI(JComponent c) {
070 return new SynthTabbedPaneUI();
071 }
072
073 SynthTabbedPaneUI() {
074 textRect = new Rectangle();
075 iconRect = new Rectangle();
076 }
077
078 private boolean scrollableTabLayoutEnabled() {
079 return (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT);
080 }
081
082 protected void installDefaults() {
083 updateStyle(tabPane);
084 }
085
086 private void updateStyle(JTabbedPane c) {
087 SynthContext context = getContext(c, ENABLED);
088 SynthStyle oldStyle = style;
089 style = SynthLookAndFeel.updateStyle(context, this );
090 // Add properties other than JComponent colors, Borders and
091 // opacity settings here:
092 if (style != oldStyle) {
093 tabRunOverlay = style.getInt(context,
094 "TabbedPane.tabRunOverlay", 0);
095 textIconGap = style.getInt(context,
096 "TabbedPane.textIconGap", 0);
097 selectedTabPadInsets = (Insets) style.get(context,
098 "TabbedPane.selectedTabPadInsets");
099 if (selectedTabPadInsets == null) {
100 selectedTabPadInsets = new Insets(0, 0, 0, 0);
101 }
102 if (oldStyle != null) {
103 uninstallKeyboardActions();
104 installKeyboardActions();
105 }
106 }
107 context.dispose();
108
109 if (tabContext != null) {
110 tabContext.dispose();
111 }
112 tabContext = getContext(c, Region.TABBED_PANE_TAB, ENABLED);
113 this .tabStyle = SynthLookAndFeel.updateStyle(tabContext, this );
114 tabInsets = tabStyle.getInsets(tabContext, null);
115
116 if (tabAreaContext != null) {
117 tabAreaContext.dispose();
118 }
119 tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA,
120 ENABLED);
121 this .tabAreaStyle = SynthLookAndFeel.updateStyle(
122 tabAreaContext, this );
123 tabAreaInsets = tabAreaStyle.getInsets(tabAreaContext, null);
124
125 if (tabContentContext != null) {
126 tabContentContext.dispose();
127 }
128 tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT,
129 ENABLED);
130 this .tabContentStyle = SynthLookAndFeel.updateStyle(
131 tabContentContext, this );
132 contentBorderInsets = tabContentStyle.getInsets(
133 tabContentContext, null);
134 }
135
136 protected void installListeners() {
137 super .installListeners();
138 tabPane.addPropertyChangeListener(this );
139 }
140
141 protected void uninstallListeners() {
142 super .uninstallListeners();
143 tabPane.removePropertyChangeListener(this );
144 }
145
146 protected void uninstallDefaults() {
147 SynthContext context = getContext(tabPane, ENABLED);
148 style.uninstallDefaults(context);
149 context.dispose();
150 style = null;
151
152 tabStyle.uninstallDefaults(tabContext);
153 tabContext.dispose();
154 tabContext = null;
155 tabStyle = null;
156
157 tabAreaStyle.uninstallDefaults(tabAreaContext);
158 tabAreaContext.dispose();
159 tabAreaContext = null;
160 tabAreaStyle = null;
161
162 tabContentStyle.uninstallDefaults(tabContentContext);
163 tabContentContext.dispose();
164 tabContentContext = null;
165 tabContentStyle = null;
166 }
167
168 public SynthContext getContext(JComponent c) {
169 return getContext(c, getComponentState(c));
170 }
171
172 public SynthContext getContext(JComponent c, int state) {
173 return SynthContext.getContext(SynthContext.class, c,
174 SynthLookAndFeel.getRegion(c), style, state);
175 }
176
177 public SynthContext getContext(JComponent c, Region subregion) {
178 return getContext(c, subregion, getComponentState(c));
179 }
180
181 private SynthContext getContext(JComponent c, Region subregion,
182 int state) {
183 SynthStyle style = null;
184 Class klass = SynthContext.class;
185
186 if (subregion == Region.TABBED_PANE_TAB) {
187 style = tabStyle;
188 } else if (subregion == Region.TABBED_PANE_TAB_AREA) {
189 style = tabAreaStyle;
190 } else if (subregion == Region.TABBED_PANE_CONTENT) {
191 style = tabContentStyle;
192 }
193 return SynthContext.getContext(klass, c, subregion, style,
194 state);
195 }
196
197 private Region getRegion(JComponent c) {
198 return SynthLookAndFeel.getRegion(c);
199 }
200
201 private int getComponentState(JComponent c) {
202 return SynthLookAndFeel.getComponentState(c);
203 }
204
205 protected JButton createScrollButton(int direction) {
206 return new SynthScrollableTabButton(direction);
207 }
208
209 public void propertyChange(PropertyChangeEvent e) {
210 if (SynthLookAndFeel.shouldUpdateStyle(e)) {
211 updateStyle(tabPane);
212 }
213 }
214
215 public void update(Graphics g, JComponent c) {
216 SynthContext context = getContext(c);
217
218 SynthLookAndFeel.update(context, g);
219 context.getPainter().paintTabbedPaneBackground(context, g, 0,
220 0, c.getWidth(), c.getHeight());
221 paint(context, g);
222 context.dispose();
223 }
224
225 protected int getBaseline(int tab) {
226 if (tabPane.getTabComponentAt(tab) != null
227 || getTextViewForTab(tab) != null) {
228 return super .getBaseline(tab);
229 }
230 String title = tabPane.getTitleAt(tab);
231 Font font = tabContext.getStyle().getFont(tabContext);
232 FontMetrics metrics = getFontMetrics(font);
233 Icon icon = getIconForTab(tab);
234 textRect.setBounds(0, 0, 0, 0);
235 iconRect.setBounds(0, 0, 0, 0);
236 calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight);
237 tabContext.getStyle().getGraphicsUtils(tabContext).layoutText(
238 tabContext, metrics, title, icon,
239 SwingUtilities.CENTER, SwingUtilities.CENTER,
240 SwingUtilities.LEADING, SwingUtilities.TRAILING,
241 calcRect, iconRect, textRect, textIconGap);
242 return textRect.y + metrics.getAscent() + getBaselineOffset();
243 }
244
245 public void paintBorder(SynthContext context, Graphics g, int x,
246 int y, int w, int h) {
247 context.getPainter().paintTabbedPaneBorder(context, g, x, y, w,
248 h);
249 }
250
251 public void paint(Graphics g, JComponent c) {
252 SynthContext context = getContext(c);
253
254 paint(context, g);
255 context.dispose();
256 }
257
258 protected void paint(SynthContext context, Graphics g) {
259 int selectedIndex = tabPane.getSelectedIndex();
260 int tabPlacement = tabPane.getTabPlacement();
261
262 ensureCurrentLayout();
263
264 // Paint tab area
265 // If scrollable tabs are enabled, the tab area will be
266 // painted by the scrollable tab panel instead.
267 //
268 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
269 Insets insets = tabPane.getInsets();
270 int x = insets.left;
271 int y = insets.top;
272 int width = tabPane.getWidth() - insets.left - insets.right;
273 int height = tabPane.getHeight() - insets.top
274 - insets.bottom;
275 int size;
276 switch (tabPlacement) {
277 case LEFT:
278 width = calculateTabAreaWidth(tabPlacement, runCount,
279 maxTabWidth);
280 break;
281 case RIGHT:
282 size = calculateTabAreaWidth(tabPlacement, runCount,
283 maxTabWidth);
284 x = x + width - size;
285 width = size;
286 break;
287 case BOTTOM:
288 size = calculateTabAreaHeight(tabPlacement, runCount,
289 maxTabHeight);
290 y = y + height - size;
291 height = size;
292 break;
293 case TOP:
294 default:
295 height = calculateTabAreaHeight(tabPlacement, runCount,
296 maxTabHeight);
297 }
298
299 tabAreaBounds.setBounds(x, y, width, height);
300
301 if (g.getClipBounds().intersects(tabAreaBounds)) {
302 paintTabArea(tabAreaContext, g, tabPlacement,
303 selectedIndex, tabAreaBounds);
304 }
305 }
306
307 // Paint content border
308 paintContentBorder(tabContentContext, g, tabPlacement,
309 selectedIndex);
310 }
311
312 protected void paintTabArea(Graphics g, int tabPlacement,
313 int selectedIndex) {
314 // This can be invoked from ScrollabeTabPanel
315 Insets insets = tabPane.getInsets();
316 int x = insets.left;
317 int y = insets.top;
318 int width = tabPane.getWidth() - insets.left - insets.right;
319 int height = tabPane.getHeight() - insets.top - insets.bottom;
320
321 paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex,
322 new Rectangle(x, y, width, height));
323 }
324
325 protected void paintTabArea(SynthContext ss, Graphics g,
326 int tabPlacement, int selectedIndex, Rectangle tabAreaBounds) {
327 Rectangle clipRect = g.getClipBounds();
328
329 // Paint the tab area.
330 SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
331 ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g,
332 tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
333 tabAreaBounds.height, tabPlacement);
334 ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g,
335 tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
336 tabAreaBounds.height, tabPlacement);
337
338 int tabCount = tabPane.getTabCount();
339
340 iconRect.setBounds(0, 0, 0, 0);
341 textRect.setBounds(0, 0, 0, 0);
342
343 // Paint tabRuns of tabs from back to front
344 for (int i = runCount - 1; i >= 0; i--) {
345 int start = tabRuns[i];
346 int next = tabRuns[(i == runCount - 1) ? 0 : i + 1];
347 int end = (next != 0 ? next - 1 : tabCount - 1);
348 for (int j = start; j <= end; j++) {
349 if (rects[j].intersects(clipRect) && selectedIndex != j) {
350 paintTab(tabContext, g, tabPlacement, rects, j,
351 iconRect, textRect);
352 }
353 }
354 }
355
356 if (selectedIndex >= 0) {
357 if (rects[selectedIndex].intersects(clipRect)) {
358 paintTab(tabContext, g, tabPlacement, rects,
359 selectedIndex, iconRect, textRect);
360 }
361 }
362 }
363
364 protected void setRolloverTab(int index) {
365 int oldRolloverTab = getRolloverTab();
366 super .setRolloverTab(index);
367
368 Rectangle r = null;
369
370 if ((oldRolloverTab >= 0)
371 && (oldRolloverTab < tabPane.getTabCount())) {
372 r = getTabBounds(tabPane, oldRolloverTab);
373 if (r != null) {
374 tabPane.repaint(r);
375 }
376 }
377
378 if (index >= 0) {
379 r = getTabBounds(tabPane, index);
380 if (r != null) {
381 tabPane.repaint(r);
382 }
383 }
384 }
385
386 protected void paintTab(SynthContext ss, Graphics g,
387 int tabPlacement, Rectangle[] rects, int tabIndex,
388 Rectangle iconRect, Rectangle textRect) {
389 Rectangle tabRect = rects[tabIndex];
390 int selectedIndex = tabPane.getSelectedIndex();
391 boolean isSelected = selectedIndex == tabIndex;
392 updateTabContext(tabIndex, isSelected,
393 (getRolloverTab() == tabIndex),
394 (getFocusIndex() == tabIndex));
395
396 SynthLookAndFeel.updateSubregion(ss, g, tabRect);
397 tabContext.getPainter().paintTabbedPaneTabBackground(
398 tabContext, g, tabRect.x, tabRect.y, tabRect.width,
399 tabRect.height, tabIndex, tabPane.getTabPlacement());
400 tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g,
401 tabRect.x, tabRect.y, tabRect.width, tabRect.height,
402 tabIndex, tabPane.getTabPlacement());
403
404 if (tabPane.getTabComponentAt(tabIndex) == null) {
405 String title = tabPane.getTitleAt(tabIndex);
406 Font font = ss.getStyle().getFont(ss);
407 FontMetrics metrics = SwingUtilities2.getFontMetrics(
408 tabPane, g, font);
409 Icon icon = getIconForTab(tabIndex);
410
411 layoutLabel(ss, tabPlacement, metrics, tabIndex, title,
412 icon, tabRect, iconRect, textRect, isSelected);
413
414 paintText(ss, g, tabPlacement, font, metrics, tabIndex,
415 title, textRect, isSelected);
416
417 paintIcon(g, tabPlacement, tabIndex, icon, iconRect,
418 isSelected);
419 }
420 }
421
422 protected void layoutLabel(SynthContext ss, int tabPlacement,
423 FontMetrics metrics, int tabIndex, String title, Icon icon,
424 Rectangle tabRect, Rectangle iconRect, Rectangle textRect,
425 boolean isSelected) {
426 View v = getTextViewForTab(tabIndex);
427 if (v != null) {
428 tabPane.putClientProperty("html", v);
429 }
430
431 textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
432
433 ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics,
434 title, icon, SwingUtilities.CENTER,
435 SwingUtilities.CENTER, SwingUtilities.LEADING,
436 SwingUtilities.TRAILING, tabRect, iconRect, textRect,
437 textIconGap);
438
439 tabPane.putClientProperty("html", null);
440
441 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex,
442 isSelected);
443 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex,
444 isSelected);
445 iconRect.x += xNudge;
446 iconRect.y += yNudge;
447 textRect.x += xNudge;
448 textRect.y += yNudge;
449 }
450
451 protected void paintText(SynthContext ss, Graphics g,
452 int tabPlacement, Font font, FontMetrics metrics,
453 int tabIndex, String title, Rectangle textRect,
454 boolean isSelected) {
455 g.setFont(font);
456
457 View v = getTextViewForTab(tabIndex);
458 if (v != null) {
459 // html
460 v.paint(g, textRect);
461 } else {
462 // plain text
463 int mnemIndex = tabPane
464 .getDisplayedMnemonicIndexAt(tabIndex);
465
466 g.setColor(ss.getStyle().getColor(ss,
467 ColorType.TEXT_FOREGROUND));
468 ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title,
469 textRect, mnemIndex);
470 }
471 }
472
473 protected void paintContentBorder(SynthContext ss, Graphics g,
474 int tabPlacement, int selectedIndex) {
475 int width = tabPane.getWidth();
476 int height = tabPane.getHeight();
477 Insets insets = tabPane.getInsets();
478
479 int x = insets.left;
480 int y = insets.top;
481 int w = width - insets.right - insets.left;
482 int h = height - insets.top - insets.bottom;
483
484 switch (tabPlacement) {
485 case LEFT:
486 x += calculateTabAreaWidth(tabPlacement, runCount,
487 maxTabWidth);
488 w -= (x - insets.left);
489 break;
490 case RIGHT:
491 w -= calculateTabAreaWidth(tabPlacement, runCount,
492 maxTabWidth);
493 break;
494 case BOTTOM:
495 h -= calculateTabAreaHeight(tabPlacement, runCount,
496 maxTabHeight);
497 break;
498 case TOP:
499 default:
500 y += calculateTabAreaHeight(tabPlacement, runCount,
501 maxTabHeight);
502 h -= (y - insets.top);
503 }
504 SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w,
505 h));
506 ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y,
507 w, h);
508 ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h);
509 }
510
511 private void ensureCurrentLayout() {
512 if (!tabPane.isValid()) {
513 tabPane.validate();
514 }
515 /* If tabPane doesn't have a peer yet, the validate() call will
516 * silently fail. We handle that by forcing a layout if tabPane
517 * is still invalid. See bug 4237677.
518 */
519 if (!tabPane.isValid()) {
520 TabbedPaneLayout layout = (TabbedPaneLayout) tabPane
521 .getLayout();
522 layout.calculateLayoutInfo();
523 }
524 }
525
526 protected int calculateMaxTabHeight(int tabPlacement) {
527 FontMetrics metrics = getFontMetrics(tabContext.getStyle()
528 .getFont(tabContext));
529 int tabCount = tabPane.getTabCount();
530 int result = 0;
531 int fontHeight = metrics.getHeight();
532 for (int i = 0; i < tabCount; i++) {
533 result = Math.max(calculateTabHeight(tabPlacement, i,
534 fontHeight), result);
535 }
536 return result;
537 }
538
539 protected int calculateTabWidth(int tabPlacement, int tabIndex,
540 FontMetrics metrics) {
541 Icon icon = getIconForTab(tabIndex);
542 Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
543 int width = tabInsets.left + tabInsets.right;
544 Component tabComponent = tabPane.getTabComponentAt(tabIndex);
545 if (tabComponent != null) {
546 width += tabComponent.getPreferredSize().width;
547 } else {
548 if (icon != null) {
549 width += icon.getIconWidth() + textIconGap;
550 }
551 View v = getTextViewForTab(tabIndex);
552 if (v != null) {
553 // html
554 width += (int) v.getPreferredSpan(View.X_AXIS);
555 } else {
556 // plain text
557 String title = tabPane.getTitleAt(tabIndex);
558 width += tabContext.getStyle().getGraphicsUtils(
559 tabContext).computeStringWidth(tabContext,
560 metrics.getFont(), metrics, title);
561 }
562 }
563 return width;
564 }
565
566 protected int calculateMaxTabWidth(int tabPlacement) {
567 FontMetrics metrics = getFontMetrics(tabContext.getStyle()
568 .getFont(tabContext));
569 int tabCount = tabPane.getTabCount();
570 int result = 0;
571 for (int i = 0; i < tabCount; i++) {
572 result = Math.max(calculateTabWidth(tabPlacement, i,
573 metrics), result);
574 }
575 return result;
576 }
577
578 protected Insets getTabInsets(int tabPlacement, int tabIndex) {
579 updateTabContext(tabIndex, false, false,
580 (getFocusIndex() == tabIndex));
581 return tabInsets;
582 }
583
584 protected FontMetrics getFontMetrics() {
585 return getFontMetrics(tabContext.getStyle().getFont(tabContext));
586 }
587
588 protected FontMetrics getFontMetrics(Font font) {
589 return tabPane.getFontMetrics(font);
590 }
591
592 private void updateTabContext(int index, boolean selected,
593 boolean isMouseOver, boolean hasFocus) {
594 int state = 0;
595 if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) {
596 state |= SynthConstants.DISABLED;
597 if (selected) {
598 state |= SynthConstants.SELECTED;
599 }
600 } else if (selected) {
601 state |= (SynthConstants.ENABLED | SynthConstants.SELECTED);
602 if (isMouseOver
603 && UIManager.getBoolean("TabbedPane.isTabRollover")) {
604 state |= SynthConstants.MOUSE_OVER;
605 }
606 } else if (isMouseOver) {
607 state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER);
608 } else {
609 state = SynthLookAndFeel.getComponentState(tabPane);
610 state &= ~SynthConstants.FOCUSED; // don't use tabbedpane focus state
611 }
612 if (hasFocus && tabPane.hasFocus()) {
613 state |= SynthConstants.FOCUSED; // individual tab has focus
614 }
615 tabContext.setComponentState(state);
616 }
617
618 private class SynthScrollableTabButton extends SynthArrowButton
619 implements UIResource {
620 public SynthScrollableTabButton(int direction) {
621 super(direction);
622 }
623 }
624 }
|