1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.swing.plaf.basic;
27
28 import sun.swing.SwingUtilities2;
29
30 import javax.swing;
31 import javax.swing.event;
32 import javax.swing.plaf;
33 import javax.swing.text.View;
34
35 import java.awt;
36 import java.awt.event;
37 import java.beans.PropertyChangeListener;
38 import java.beans.PropertyChangeEvent;
39 import java.util.Vector;
40 import java.util.Hashtable;
41
42 import sun.swing.DefaultLookup;
43 import sun.swing.UIAction;
44
45 /**
46 * A Basic L&F implementation of TabbedPaneUI.
47 *
48 * @author Amy Fowler
49 * @author Philip Milne
50 * @author Steve Wilson
51 * @author Tom Santos
52 * @author Dave Moore
53 */
54 public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
55
56
57 // Instance variables initialized at installation
58
59 protected JTabbedPane tabPane;
60
61 protected Color highlight;
62 protected Color lightHighlight;
63 protected Color shadow;
64 protected Color darkShadow;
65 protected Color focus;
66 private Color selectedColor;
67
68 protected int textIconGap;
69
70 protected int tabRunOverlay;
71
72 protected Insets tabInsets;
73 protected Insets selectedTabPadInsets;
74 protected Insets tabAreaInsets;
75 protected Insets contentBorderInsets;
76 private boolean tabsOverlapBorder;
77 private boolean tabsOpaque = true;
78 private boolean contentOpaque = true;
79
80 /**
81 * As of Java 2 platform v1.3 this previously undocumented field is no
82 * longer used.
83 * Key bindings are now defined by the LookAndFeel, please refer to
84 * the key bindings specification for further details.
85 *
86 * @deprecated As of Java 2 platform v1.3.
87 */
88 @Deprecated
89 protected KeyStroke upKey;
90 /**
91 * As of Java 2 platform v1.3 this previously undocumented field is no
92 * longer used.
93 * Key bindings are now defined by the LookAndFeel, please refer to
94 * the key bindings specification for further details.
95 *
96 * @deprecated As of Java 2 platform v1.3.
97 */
98 @Deprecated
99 protected KeyStroke downKey;
100 /**
101 * As of Java 2 platform v1.3 this previously undocumented field is no
102 * longer used.
103 * Key bindings are now defined by the LookAndFeel, please refer to
104 * the key bindings specification for further details.
105 *
106 * @deprecated As of Java 2 platform v1.3.
107 */
108 @Deprecated
109 protected KeyStroke leftKey;
110 /**
111 * As of Java 2 platform v1.3 this previously undocumented field is no
112 * longer used.
113 * Key bindings are now defined by the LookAndFeel, please refer to
114 * the key bindings specification for further details.
115 *
116 * @deprecated As of Java 2 platform v1.3.
117 */
118 @Deprecated
119 protected KeyStroke rightKey;
120
121
122 // Transient variables (recalculated each time TabbedPane is layed out)
123
124 protected int tabRuns[] = new int[10];
125 protected int runCount = 0;
126 protected int selectedRun = -1;
127 protected Rectangle rects[] = new Rectangle[0];
128 protected int maxTabHeight;
129 protected int maxTabWidth;
130
131 // Listeners
132
133 protected ChangeListener tabChangeListener;
134 protected PropertyChangeListener propertyChangeListener;
135 protected MouseListener mouseListener;
136 protected FocusListener focusListener;
137
138 // Private instance data
139
140 private Insets currentPadInsets = new Insets(0,0,0,0);
141 private Insets currentTabAreaInsets = new Insets(0,0,0,0);
142
143 private Component visibleComponent;
144 // PENDING(api): See comment for ContainerHandler
145 private Vector htmlViews;
146
147 private Hashtable mnemonicToIndexMap;
148
149 /**
150 * InputMap used for mnemonics. Only non-null if the JTabbedPane has
151 * mnemonics associated with it. Lazily created in initMnemonics.
152 */
153 private InputMap mnemonicInputMap;
154
155 // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
156 private ScrollableTabSupport tabScroller;
157
158 private TabContainer tabContainer;
159
160 /**
161 * A rectangle used for general layout calculations in order
162 * to avoid constructing many new Rectangles on the fly.
163 */
164 protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
165
166 /**
167 * Tab that has focus.
168 */
169 private int focusIndex;
170
171 /**
172 * Combined listeners.
173 */
174 private Handler handler;
175
176 /**
177 * Index of the tab the mouse is over.
178 */
179 private int rolloverTabIndex;
180
181 /**
182 * This is set to true when a component is added/removed from the tab
183 * pane and set to false when layout happens. If true it indicates that
184 * tabRuns is not valid and shouldn't be used.
185 */
186 private boolean isRunsDirty;
187
188 private boolean calculatedBaseline;
189 private int baseline;
190
191 // UI creation
192
193 public static ComponentUI createUI(JComponent c) {
194 return new BasicTabbedPaneUI();
195 }
196
197 static void loadActionMap(LazyActionMap map) {
198 map.put(new Actions(Actions.NEXT));
199 map.put(new Actions(Actions.PREVIOUS));
200 map.put(new Actions(Actions.RIGHT));
201 map.put(new Actions(Actions.LEFT));
202 map.put(new Actions(Actions.UP));
203 map.put(new Actions(Actions.DOWN));
204 map.put(new Actions(Actions.PAGE_UP));
205 map.put(new Actions(Actions.PAGE_DOWN));
206 map.put(new Actions(Actions.REQUEST_FOCUS));
207 map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
208 map.put(new Actions(Actions.SET_SELECTED));
209 map.put(new Actions(Actions.SELECT_FOCUSED));
210 map.put(new Actions(Actions.SCROLL_FORWARD));
211 map.put(new Actions(Actions.SCROLL_BACKWARD));
212 }
213
214 // UI Installation/De-installation
215
216 public void installUI(JComponent c) {
217 this.tabPane = (JTabbedPane)c;
218
219 calculatedBaseline = false;
220 rolloverTabIndex = -1;
221 focusIndex = -1;
222 c.setLayout(createLayoutManager());
223 installComponents();
224 installDefaults();
225 installListeners();
226 installKeyboardActions();
227 }
228
229 public void uninstallUI(JComponent c) {
230 uninstallKeyboardActions();
231 uninstallListeners();
232 uninstallDefaults();
233 uninstallComponents();
234 c.setLayout(null);
235
236 this.tabPane = null;
237 }
238
239 /**
240 * Invoked by <code>installUI</code> to create
241 * a layout manager object to manage
242 * the <code>JTabbedPane</code>.
243 *
244 * @return a layout manager object
245 *
246 * @see TabbedPaneLayout
247 * @see javax.swing.JTabbedPane#getTabLayoutPolicy
248 */
249 protected LayoutManager createLayoutManager() {
250 if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
251 return new TabbedPaneScrollLayout();
252 } else { /* WRAP_TAB_LAYOUT */
253 return new TabbedPaneLayout();
254 }
255 }
256
257 /* In an attempt to preserve backward compatibility for programs
258 * which have extended BasicTabbedPaneUI to do their own layout, the
259 * UI uses the installed layoutManager (and not tabLayoutPolicy) to
260 * determine if scrollTabLayout is enabled.
261 */
262 private boolean scrollableTabLayoutEnabled() {
263 return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
264 }
265
266 /**
267 * Creates and installs any required subcomponents for the JTabbedPane.
268 * Invoked by installUI.
269 *
270 * @since 1.4
271 */
272 protected void installComponents() {
273 if (scrollableTabLayoutEnabled()) {
274 if (tabScroller == null) {
275 tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
276 tabPane.add(tabScroller.viewport);
277 }
278 }
279 installTabContainer();
280 }
281
282 private void installTabContainer() {
283 for (int i = 0; i < tabPane.getTabCount(); i++) {
284 Component tabComponent = tabPane.getTabComponentAt(i);
285 if (tabComponent != null) {
286 if(tabContainer == null) {
287 tabContainer = new TabContainer();
288 }
289 tabContainer.add(tabComponent);
290 }
291 }
292 if(tabContainer == null) {
293 return;
294 }
295 if (scrollableTabLayoutEnabled()) {
296 tabScroller.tabPanel.add(tabContainer);
297 } else {
298 tabPane.add(tabContainer);
299 }
300 }
301
302 /**
303 * Creates and returns a JButton that will provide the user
304 * with a way to scroll the tabs in a particular direction. The
305 * returned JButton must be instance of UIResource.
306 *
307 * @param direction One of the SwingConstants constants:
308 * SOUTH, NORTH, EAST or WEST
309 * @return Widget for user to
310 * @see javax.swing.JTabbedPane#setTabPlacement
311 * @see javax.swing.SwingConstants
312 * @throws IllegalArgumentException if direction is not one of
313 * NORTH, SOUTH, EAST or WEST
314 * @since 1.5
315 */
316 protected JButton createScrollButton(int direction) {
317 if (direction != SOUTH && direction != NORTH && direction != EAST &&
318 direction != WEST) {
319 throw new IllegalArgumentException("Direction must be one of: " +
320 "SOUTH, NORTH, EAST or WEST");
321 }
322 return new ScrollableTabButton(direction);
323 }
324
325 /**
326 * Removes any installed subcomponents from the JTabbedPane.
327 * Invoked by uninstallUI.
328 *
329 * @since 1.4
330 */
331 protected void uninstallComponents() {
332 uninstallTabContainer();
333 if (scrollableTabLayoutEnabled()) {
334 tabPane.remove(tabScroller.viewport);
335 tabPane.remove(tabScroller.scrollForwardButton);
336 tabPane.remove(tabScroller.scrollBackwardButton);
337 tabScroller = null;
338 }
339 }
340
341 private void uninstallTabContainer() {
342 if(tabContainer == null) {
343 return;
344 }
345 // Remove all the tabComponents, making sure not to notify
346 // the tabbedpane.
347 tabContainer.notifyTabbedPane = false;
348 tabContainer.removeAll();
349 if(scrollableTabLayoutEnabled()) {
350 tabContainer.remove(tabScroller.croppedEdge);
351 tabScroller.tabPanel.remove(tabContainer);
352 } else {
353 tabPane.remove(tabContainer);
354 }
355 tabContainer = null;
356 }
357
358 protected void installDefaults() {
359 LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
360 "TabbedPane.foreground", "TabbedPane.font");
361 highlight = UIManager.getColor("TabbedPane.light");
362 lightHighlight = UIManager.getColor("TabbedPane.highlight");
363 shadow = UIManager.getColor("TabbedPane.shadow");
364 darkShadow = UIManager.getColor("TabbedPane.darkShadow");
365 focus = UIManager.getColor("TabbedPane.focus");
366 selectedColor = UIManager.getColor("TabbedPane.selected");
367
368 textIconGap = UIManager.getInt("TabbedPane.textIconGap");
369 tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
370 selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
371 tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
372 tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
373 contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
374 tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
375 tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
376 contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
377 Object opaque = UIManager.get("TabbedPane.opaque");
378 if (opaque == null) {
379 opaque = Boolean.FALSE;
380 }
381 LookAndFeel.installProperty(tabPane, "opaque", opaque);
382 }
383
384 protected void uninstallDefaults() {
385 highlight = null;
386 lightHighlight = null;
387 shadow = null;
388 darkShadow = null;
389 focus = null;
390 tabInsets = null;
391 selectedTabPadInsets = null;
392 tabAreaInsets = null;
393 contentBorderInsets = null;
394 }
395
396 protected void installListeners() {
397 if ((propertyChangeListener = createPropertyChangeListener()) != null) {
398 tabPane.addPropertyChangeListener(propertyChangeListener);
399 }
400 if ((tabChangeListener = createChangeListener()) != null) {
401 tabPane.addChangeListener(tabChangeListener);
402 }
403 if ((mouseListener = createMouseListener()) != null) {
404 tabPane.addMouseListener(mouseListener);
405 }
406 tabPane.addMouseMotionListener(getHandler());
407 if ((focusListener = createFocusListener()) != null) {
408 tabPane.addFocusListener(focusListener);
409 }
410 tabPane.addContainerListener(getHandler());
411 if (tabPane.getTabCount()>0) {
412 htmlViews = createHTMLVector();
413 }
414 }
415
416 protected void uninstallListeners() {
417 if (mouseListener != null) {
418 tabPane.removeMouseListener(mouseListener);
419 mouseListener = null;
420 }
421 tabPane.removeMouseMotionListener(getHandler());
422 if (focusListener != null) {
423 tabPane.removeFocusListener(focusListener);
424 focusListener = null;
425 }
426
427 tabPane.removeContainerListener(getHandler());
428 if (htmlViews!=null) {
429 htmlViews.removeAllElements();
430 htmlViews = null;
431 }
432 if (tabChangeListener != null) {
433 tabPane.removeChangeListener(tabChangeListener);
434 tabChangeListener = null;
435 }
436 if (propertyChangeListener != null) {
437 tabPane.removePropertyChangeListener(propertyChangeListener);
438 propertyChangeListener = null;
439 }
440 handler = null;
441 }
442
443 protected MouseListener createMouseListener() {
444 return getHandler();
445 }
446
447 protected FocusListener createFocusListener() {
448 return getHandler();
449 }
450
451 protected ChangeListener createChangeListener() {
452 return getHandler();
453 }
454
455 protected PropertyChangeListener createPropertyChangeListener() {
456 return getHandler();
457 }
458
459 private Handler getHandler() {
460 if (handler == null) {
461 handler = new Handler();
462 }
463 return handler;
464 }
465
466 protected void installKeyboardActions() {
467 InputMap km = getInputMap(JComponent.
468 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
469
470 SwingUtilities.replaceUIInputMap(tabPane, JComponent.
471 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
472 km);
473 km = getInputMap(JComponent.WHEN_FOCUSED);
474 SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
475
476 LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI.class,
477 "TabbedPane.actionMap");
478 updateMnemonics();
479 }
480
481 InputMap getInputMap(int condition) {
482 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
483 return (InputMap)DefaultLookup.get(tabPane, this,
484 "TabbedPane.ancestorInputMap");
485 }
486 else if (condition == JComponent.WHEN_FOCUSED) {
487 return (InputMap)DefaultLookup.get(tabPane, this,
488 "TabbedPane.focusInputMap");
489 }
490 return null;
491 }
492
493 protected void uninstallKeyboardActions() {
494 SwingUtilities.replaceUIActionMap(tabPane, null);
495 SwingUtilities.replaceUIInputMap(tabPane, JComponent.
496 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
497 null);
498 SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
499 null);
500 SwingUtilities.replaceUIInputMap(tabPane,
501 JComponent.WHEN_IN_FOCUSED_WINDOW,
502 null);
503 mnemonicToIndexMap = null;
504 mnemonicInputMap = null;
505 }
506
507 /**
508 * Reloads the mnemonics. This should be invoked when a memonic changes,
509 * when the title of a mnemonic changes, or when tabs are added/removed.
510 */
511 private void updateMnemonics() {
512 resetMnemonics();
513 for (int counter = tabPane.getTabCount() - 1; counter >= 0;
514 counter--) {
515 int mnemonic = tabPane.getMnemonicAt(counter);
516
517 if (mnemonic > 0) {
518 addMnemonic(counter, mnemonic);
519 }
520 }
521 }
522
523 /**
524 * Resets the mnemonics bindings to an empty state.
525 */
526 private void resetMnemonics() {
527 if (mnemonicToIndexMap != null) {
528 mnemonicToIndexMap.clear();
529 mnemonicInputMap.clear();
530 }
531 }
532
533 /**
534 * Adds the specified mnemonic at the specified index.
535 */
536 private void addMnemonic(int index, int mnemonic) {
537 if (mnemonicToIndexMap == null) {
538 initMnemonics();
539 }
540 mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, Event.ALT_MASK),
541 "setSelectedIndex");
542 mnemonicToIndexMap.put(Integer.valueOf(mnemonic), Integer.valueOf(index));
543 }
544
545 /**
546 * Installs the state needed for mnemonics.
547 */
548 private void initMnemonics() {
549 mnemonicToIndexMap = new Hashtable();
550 mnemonicInputMap = new ComponentInputMapUIResource(tabPane);
551 mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane,
552 JComponent.WHEN_IN_FOCUSED_WINDOW));
553 SwingUtilities.replaceUIInputMap(tabPane,
554 JComponent.WHEN_IN_FOCUSED_WINDOW,
555 mnemonicInputMap);
556 }
557
558 /**
559 * Sets the tab the mouse is over by location. This is a cover method
560 * for <code>setRolloverTab(tabForCoordinate(x, y, false))</code>.
561 */
562 private void setRolloverTab(int x, int y) {
563 // NOTE:
564 // This calls in with false otherwise it could trigger a validate,
565 // which should NOT happen if the user is only dragging the
566 // mouse around.
567 setRolloverTab(tabForCoordinate(tabPane, x, y, false));
568 }
569
570 /**
571 * Sets the tab the mouse is currently over to <code>index</code>.
572 * <code>index</code> will be -1 if the mouse is no longer over any
573 * tab. No checking is done to ensure the passed in index identifies a
574 * valid tab.
575 *
576 * @param index Index of the tab the mouse is over.
577 * @since 1.5
578 */
579 protected void setRolloverTab(int index) {
580 rolloverTabIndex = index;
581 }
582
583 /**
584 * Returns the tab the mouse is currently over, or {@code -1} if the mouse is no
585 * longer over any tab.
586 *
587 * @return the tab the mouse is currently over, or {@code -1} if the mouse is no
588 * longer over any tab
589 * @since 1.5
590 */
591 protected int getRolloverTab() {
592 return rolloverTabIndex;
593 }
594
595 public Dimension getMinimumSize(JComponent c) {
596 // Default to LayoutManager's minimumLayoutSize
597 return null;
598 }
599
600 public Dimension getMaximumSize(JComponent c) {
601 // Default to LayoutManager's maximumLayoutSize
602 return null;
603 }
604
605 /**
606 * Returns the baseline.
607 *
608 * @throws NullPointerException {@inheritDoc}
609 * @throws IllegalArgumentException {@inheritDoc}
610 * @see javax.swing.JComponent#getBaseline(int, int)
611 * @since 1.6
612 */
613 public int getBaseline(JComponent c, int width, int height) {
614 super.getBaseline(c, width, height);
615 int baseline = calculateBaselineIfNecessary();
616 if (baseline != -1) {
617 int placement = tabPane.getTabPlacement();
618 Insets insets = tabPane.getInsets();
619 Insets tabAreaInsets = getTabAreaInsets(placement);
620 switch(placement) {
621 case JTabbedPane.TOP:
622 baseline += insets.top + tabAreaInsets.top;
623 return baseline;
624 case JTabbedPane.BOTTOM:
625 baseline = height - insets.bottom -
626 tabAreaInsets.bottom - maxTabHeight + baseline;
627 return baseline;
628 case JTabbedPane.LEFT:
629 case JTabbedPane.RIGHT:
630 baseline += insets.top + tabAreaInsets.top;
631 return baseline;
632 }
633 }
634 return -1;
635 }
636
637 /**
638 * Returns an enum indicating how the baseline of the component
639 * changes as the size changes.
640 *
641 * @throws NullPointerException {@inheritDoc}
642 * @see javax.swing.JComponent#getBaseline(int, int)
643 * @since 1.6
644 */
645 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
646 JComponent c) {
647 super.getBaselineResizeBehavior(c);
648 switch(tabPane.getTabPlacement()) {
649 case JTabbedPane.LEFT:
650 case JTabbedPane.RIGHT:
651 case JTabbedPane.TOP:
652 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
653 case JTabbedPane.BOTTOM:
654 return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
655 }
656 return Component.BaselineResizeBehavior.OTHER;
657 }
658
659 /**
660 * Returns the baseline for the specified tab.
661 *
662 * @param tab index of tab to get baseline for
663 * @exception IndexOutOfBoundsException if index is out of range
664 * (index < 0 || index >= tab count)
665 * @return baseline or a value < 0 indicating there is no reasonable
666 * baseline
667 * @since 1.6
668 */
669 protected int getBaseline(int tab) {
670 if (tabPane.getTabComponentAt(tab) != null) {
671 int offset = getBaselineOffset();
672 if (offset != 0) {
673 // The offset is not applied to the tab component, and so
674 // in general we can't get good alignment like with components
675 // in the tab.
676 return -1;
677 }
678 Component c = tabPane.getTabComponentAt(tab);
679 Dimension pref = c.getPreferredSize();
680 Insets tabInsets = getTabInsets(tabPane.getTabPlacement(), tab);
681 int cellHeight = maxTabHeight - tabInsets.top - tabInsets.bottom;
682 return c.getBaseline(pref.width, pref.height) +
683 (cellHeight - pref.height) / 2 + tabInsets.top;
684 }
685 else {
686 View view = getTextViewForTab(tab);
687 if (view != null) {
688 int viewHeight = (int)view.getPreferredSpan(View.Y_AXIS);
689 int baseline = BasicHTML.getHTMLBaseline(
690 view, (int)view.getPreferredSpan(View.X_AXIS), viewHeight);
691 if (baseline >= 0) {
692 return maxTabHeight / 2 - viewHeight / 2 + baseline +
693 getBaselineOffset();
694 }
695 return -1;
696 }
697 }
698 FontMetrics metrics = getFontMetrics();
699 int fontHeight = metrics.getHeight();
700 int fontBaseline = metrics.getAscent();
701 return maxTabHeight / 2 - fontHeight / 2 + fontBaseline +
702 getBaselineOffset();
703 }
704
705 /**
706 * Returns the amount the baseline is offset by. This is typically
707 * the same as <code>getTabLabelShiftY</code>.
708 *
709 * @return amount to offset the baseline by
710 * @since 1.6
711 */
712 protected int getBaselineOffset() {
713 switch(tabPane.getTabPlacement()) {
714 case JTabbedPane.TOP:
715 if (tabPane.getTabCount() > 1) {
716 return 1;
717 }
718 else {
719 return -1;
720 }
721 case JTabbedPane.BOTTOM:
722 if (tabPane.getTabCount() > 1) {
723 return -1;
724 }
725 else {
726 return 1;
727 }
728 default: // RIGHT|LEFT
729 return (maxTabHeight % 2);
730 }
731 }
732
733 private int calculateBaselineIfNecessary() {
734 if (!calculatedBaseline) {
735 calculatedBaseline = true;
736 baseline = -1;
737 if (tabPane.getTabCount() > 0) {
738 calculateBaseline();
739 }
740 }
741 return baseline;
742 }
743
744 private void calculateBaseline() {
745 int tabCount = tabPane.getTabCount();
746 int tabPlacement = tabPane.getTabPlacement();
747 maxTabHeight = calculateMaxTabHeight(tabPlacement);
748 baseline = getBaseline(0);
749 if (isHorizontalTabPlacement()) {
750 for(int i = 1; i < tabCount; i++) {
751 if (getBaseline(i) != baseline) {
752 baseline = -1;
753 break;
754 }
755 }
756 }
757 else {
758 // left/right, tabs may be different sizes.
759 FontMetrics fontMetrics = getFontMetrics();
760 int fontHeight = fontMetrics.getHeight();
761 int height = calculateTabHeight(tabPlacement, 0, fontHeight);
762 for(int i = 1; i < tabCount; i++) {
763 int newHeight = calculateTabHeight(tabPlacement, i,fontHeight);
764 if (height != newHeight) {
765 // assume different baseline
766 baseline = -1;
767 break;
768 }
769 }
770 }
771 }
772
773 // UI Rendering
774
775 public void paint(Graphics g, JComponent c) {
776 int selectedIndex = tabPane.getSelectedIndex();
777 int tabPlacement = tabPane.getTabPlacement();
778
779 ensureCurrentLayout();
780
781 // Paint content border and tab area
782 if (tabsOverlapBorder) {
783 paintContentBorder(g, tabPlacement, selectedIndex);
784 }
785 // If scrollable tabs are enabled, the tab area will be
786 // painted by the scrollable tab panel instead.
787 //
788 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
789 paintTabArea(g, tabPlacement, selectedIndex);
790 }
791 if (!tabsOverlapBorder) {
792 paintContentBorder(g, tabPlacement, selectedIndex);
793 }
794 }
795
796 /**
797 * Paints the tabs in the tab area.
798 * Invoked by paint().
799 * The graphics parameter must be a valid <code>Graphics</code>
800 * object. Tab placement may be either:
801 * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
802 * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
803 * The selected index must be a valid tabbed pane tab index (0 to
804 * tab count - 1, inclusive) or -1 if no tab is currently selected.
805 * The handling of invalid parameters is unspecified.
806 *
807 * @param g the graphics object to use for rendering
808 * @param tabPlacement the placement for the tabs within the JTabbedPane
809 * @param selectedIndex the tab index of the selected component
810 *
811 * @since 1.4
812 */
813 protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
814 int tabCount = tabPane.getTabCount();
815
816 Rectangle iconRect = new Rectangle(),
817 textRect = new Rectangle();
818 Rectangle clipRect = g.getClipBounds();
819
820 // Paint tabRuns of tabs from back to front
821 for (int i = runCount - 1; i >= 0; i--) {
822 int start = tabRuns[i];
823 int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
824 int end = (next != 0? next - 1: tabCount - 1);
825 for (int j = start; j <= end; j++) {
826 if (j != selectedIndex && rects[j].intersects(clipRect)) {
827 paintTab(g, tabPlacement, rects, j, iconRect, textRect);
828 }
829 }
830 }
831
832 // Paint selected tab if its in the front run
833 // since it may overlap other tabs
834 if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
835 paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
836 }
837 }
838
839 protected void paintTab(Graphics g, int tabPlacement,
840 Rectangle[] rects, int tabIndex,
841 Rectangle iconRect, Rectangle textRect) {
842 Rectangle tabRect = rects[tabIndex];
843 int selectedIndex = tabPane.getSelectedIndex();
844 boolean isSelected = selectedIndex == tabIndex;
845
846 if (tabsOpaque || tabPane.isOpaque()) {
847 paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
848 tabRect.width, tabRect.height, isSelected);
849 }
850
851 paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
852 tabRect.width, tabRect.height, isSelected);
853
854 String title = tabPane.getTitleAt(tabIndex);
855 Font font = tabPane.getFont();
856 FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
857 Icon icon = getIconForTab(tabIndex);
858
859 layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
860 tabRect, iconRect, textRect, isSelected);
861
862 if (tabPane.getTabComponentAt(tabIndex) == null) {
863 String clippedTitle = title;
864
865 if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() &&
866 tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) {
867 int availTextWidth = tabScroller.croppedEdge.getCropline() -
868 (textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth();
869 clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
870 }
871
872 paintText(g, tabPlacement, font, metrics,
873 tabIndex, clippedTitle, textRect, isSelected);
874
875 paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
876 }
877 paintFocusIndicator(g, tabPlacement, rects, tabIndex,
878 iconRect, textRect, isSelected);
879 }
880
881 private boolean isHorizontalTabPlacement() {
882 return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM;
883 }
884
885 /* This method will create and return a polygon shape for the given tab rectangle
886 * which has been cropped at the specified cropline with a torn edge visual.
887 * e.g. A "File" tab which has cropped been cropped just after the "i":
888 * -------------
889 * | ..... |
890 * | . |
891 * | ... . |
892 * | . . |
893 * | . . |
894 * | . . |
895 * --------------
896 *
897 * The x, y arrays below define the pattern used to create a "torn" edge
898 * segment which is repeated to fill the edge of the tab.
899 * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
900 * line segments which are defined by coordinates obtained by
901 * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
902 * to (tab.y).
903 * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
904 * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
905 * to (tab.x).
906 */
907 private static int xCropLen[] = {1,1,0,0,1,1,2,2};
908 private static int yCropLen[] = {0,3,3,6,6,9,9,12};
909 private static final int CROP_SEGMENT = 12;
910
911 private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) {
912 int rlen = 0;
913 int start = 0;
914 int end = 0;
915 int ostart = 0;
916
917 switch(tabPlacement) {
918 case LEFT:
919 case RIGHT:
920 rlen = tabRect.width;
921 start = tabRect.x;
922 end = tabRect.x + tabRect.width;
923 ostart = tabRect.y + tabRect.height;
924 break;
925 case TOP:
926 case BOTTOM:
927 default:
928 rlen = tabRect.height;
929 start = tabRect.y;
930 end = tabRect.y + tabRect.height;
931 ostart = tabRect.x + tabRect.width;
932 }
933 int rcnt = rlen/CROP_SEGMENT;
934 if (rlen%CROP_SEGMENT > 0) {
935 rcnt++;
936 }
937 int npts = 2 + (rcnt*8);
938 int xp[] = new int[npts];
939 int yp[] = new int[npts];
940 int pcnt = 0;
941
942 xp[pcnt] = ostart;
943 yp[pcnt++] = end;
944 xp[pcnt] = ostart;
945 yp[pcnt++] = start;
946 for(int i = 0; i < rcnt; i++) {
947 for(int j = 0; j < xCropLen.length; j++) {
948 xp[pcnt] = cropline - xCropLen[j];
949 yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j];
950 if (yp[pcnt] >= end) {
951 yp[pcnt] = end;
952 pcnt++;
953 break;
954 }
955 pcnt++;
956 }
957 }
958 if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) {
959 return new Polygon(xp, yp, pcnt);
960
961 } else { // LEFT or RIGHT
962 return new Polygon(yp, xp, pcnt);
963 }
964 }
965
966 /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
967 * indicating the tab is cropped in the viewport display
968 */
969 private void paintCroppedTabEdge(Graphics g) {
970 int tabIndex = tabScroller.croppedEdge.getTabIndex();
971 int cropline = tabScroller.croppedEdge.getCropline();
972 int x,y;
973 switch(tabPane.getTabPlacement()) {
974 case LEFT:
975 case RIGHT:
976 x = rects[tabIndex].x;
977 y = cropline;
978 int xx = x;
979 g.setColor(shadow);
980 while(xx <= x+rects[tabIndex].width) {
981 for (int i=0; i < xCropLen.length; i+=2) {
982 g.drawLine(xx+yCropLen[i],y-xCropLen[i],
983 xx+yCropLen[i+1]-1,y-xCropLen[i+1]);
984 }
985 xx+=CROP_SEGMENT;
986 }
987 break;
988 case TOP:
989 case BOTTOM:
990 default:
991 x = cropline;
992 y = rects[tabIndex].y;
993 int yy = y;
994 g.setColor(shadow);
995 while(yy <= y+rects[tabIndex].height) {
996 for (int i=0; i < xCropLen.length; i+=2) {
997 g.drawLine(x-xCropLen[i],yy+yCropLen[i],
998 x-xCropLen[i+1],yy+yCropLen[i+1]-1);
999 }
1000 yy+=CROP_SEGMENT;
1001 }
1002 }
1003 }
1004
1005 protected void layoutLabel(int tabPlacement,
1006 FontMetrics metrics, int tabIndex,
1007 String title, Icon icon,
1008 Rectangle tabRect, Rectangle iconRect,
1009 Rectangle textRect, boolean isSelected ) {
1010 textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
1011
1012 View v = getTextViewForTab(tabIndex);
1013 if (v != null) {
1014 tabPane.putClientProperty("html", v);
1015 }
1016
1017 SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
1018 metrics, title, icon,
1019 SwingUtilities.CENTER,
1020 SwingUtilities.CENTER,
1021 SwingUtilities.CENTER,
1022 SwingUtilities.TRAILING,
1023 tabRect,
1024 iconRect,
1025 textRect,
1026 textIconGap);
1027
1028 tabPane.putClientProperty("html", null);
1029
1030 int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1031 int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
1032 iconRect.x += xNudge;
1033 iconRect.y += yNudge;
1034 textRect.x += xNudge;
1035 textRect.y += yNudge;
1036 }
1037
1038 protected void paintIcon(Graphics g, int tabPlacement,
1039 int tabIndex, Icon icon, Rectangle iconRect,
1040 boolean isSelected ) {
1041 if (icon != null) {
1042 icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
1043 }
1044 }
1045
1046 protected void paintText(Graphics g, int tabPlacement,
1047 Font font, FontMetrics metrics, int tabIndex,
1048 String title, Rectangle textRect,
1049 boolean isSelected) {
1050
1051 g.setFont(font);
1052
1053 View v = getTextViewForTab(tabIndex);
1054 if (v != null) {
1055 // html
1056 v.paint(g, textRect);
1057 } else {
1058 // plain text
1059 int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
1060
1061 if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
1062 Color fg = tabPane.getForegroundAt(tabIndex);
1063 if (isSelected && (fg instanceof UIResource)) {
1064 Color selectedFG = UIManager.getColor(
1065 "TabbedPane.selectedForeground");
1066 if (selectedFG != null) {
1067 fg = selectedFG;
1068 }
1069 }
1070 g.setColor(fg);
1071 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1072 title, mnemIndex,
1073 textRect.x, textRect.y + metrics.getAscent());
1074
1075 } else { // tab disabled
1076 g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
1077 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1078 title, mnemIndex,
1079 textRect.x, textRect.y + metrics.getAscent());
1080 g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
1081 SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
1082 title, mnemIndex,
1083 textRect.x - 1, textRect.y + metrics.getAscent() - 1);
1084
1085 }
1086 }
1087 }
1088
1089
1090 protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
1091 Rectangle tabRect = rects[tabIndex];
1092 String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
1093 int nudge = DefaultLookup.getInt(
1094 tabPane, this, "TabbedPane." + propKey, 1);
1095
1096 switch (tabPlacement) {
1097 case LEFT:
1098 return nudge;
1099 case RIGHT:
1100 return -nudge;
1101 case BOTTOM:
1102 case TOP:
1103 default:
1104 return tabRect.width % 2;
1105 }
1106 }
1107
1108 protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
1109 Rectangle tabRect = rects[tabIndex];
1110 String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
1111 int nudge = DefaultLookup.getInt(
1112 tabPane, this, "TabbedPane." + propKey, 1);
1113
1114 switch (tabPlacement) {
1115 case BOTTOM:
1116 return -nudge;
1117 case LEFT:
1118 case RIGHT:
1119 return tabRect.height % 2;
1120 case TOP:
1121 default:
1122 return nudge;
1123 }
1124 }
1125
1126 protected void paintFocusIndicator(Graphics g, int tabPlacement,
1127 Rectangle[] rects, int tabIndex,
1128 Rectangle iconRect, Rectangle textRect,
1129 boolean isSelected) {
1130 Rectangle tabRect = rects[tabIndex];
1131 if (tabPane.hasFocus() && isSelected) {
1132 int x, y, w, h;
1133 g.setColor(focus);
1134 switch(tabPlacement) {
1135 case LEFT:
1136 x = tabRect.x + 3;
1137 y = tabRect.y + 3;
1138 w = tabRect.width - 5;
1139 h = tabRect.height - 6;
1140 break;
1141 case RIGHT:
1142 x = tabRect.x + 2;
1143 y = tabRect.y + 3;
1144 w = tabRect.width - 5;
1145 h = tabRect.height - 6;
1146 break;
1147 case BOTTOM:
1148 x = tabRect.x + 3;
1149 y = tabRect.y + 2;
1150 w = tabRect.width - 6;
1151 h = tabRect.height - 5;
1152 break;
1153 case TOP:
1154 default:
1155 x = tabRect.x + 3;
1156 y = tabRect.y + 3;
1157 w = tabRect.width - 6;
1158 h = tabRect.height - 5;
1159 }
1160 BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
1161 }
1162 }
1163
1164 /**
1165 * this function draws the border around each tab
1166 * note that this function does now draw the background of the tab.
1167 * that is done elsewhere
1168 */
1169 protected void paintTabBorder(Graphics g, int tabPlacement,
1170 int tabIndex,
1171 int x, int y, int w, int h,
1172 boolean isSelected ) {
1173 g.setColor(lightHighlight);
1174
1175 switch (tabPlacement) {
1176 case LEFT:
1177 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1178 g.drawLine(x, y+2, x, y+h-3); // left highlight
1179 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1180 g.drawLine(x+2, y, x+w-1, y); // top highlight
1181
1182 g.setColor(shadow);
1183 g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
1184
1185 g.setColor(darkShadow);
1186 g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
1187 break;
1188 case RIGHT:
1189 g.drawLine(x, y, x+w-3, y); // top highlight
1190
1191 g.setColor(shadow);
1192 g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
1193 g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
1194
1195 g.setColor(darkShadow);
1196 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
1197 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1198 g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
1199 g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1200 break;
1201 case BOTTOM:
1202 g.drawLine(x, y, x, y+h-3); // left highlight
1203 g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
1204
1205 g.setColor(shadow);
1206 g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
1207 g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
1208
1209 g.setColor(darkShadow);
1210 g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
1211 g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
1212 g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
1213 break;
1214 case TOP:
1215 default:
1216 g.drawLine(x, y+2, x, y+h-1); // left highlight
1217 g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
1218 g.drawLine(x+2, y, x+w-3, y); // top highlight
1219
1220 g.setColor(shadow);
1221 g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
1222
1223 g.setColor(darkShadow);
1224 g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
1225 g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
1226 }
1227 }
1228
1229 protected void paintTabBackground(Graphics g, int tabPlacement,
1230 int tabIndex,
1231 int x, int y, int w, int h,
1232 boolean isSelected ) {
1233 g.setColor(!isSelected || selectedColor == null?
1234 tabPane.getBackgroundAt(tabIndex) : selectedColor);
1235 switch(tabPlacement) {
1236 case LEFT:
1237 g.fillRect(x+1, y+1, w-1, h-3);
1238 break;
1239 case RIGHT:
1240 g.fillRect(x, y+1, w-2, h-3);
1241 break;
1242 case BOTTOM:
1243 g.fillRect(x+1, y, w-3, h-1);
1244 break;
1245 case TOP:
1246 default:
1247 g.fillRect(x+1, y+1, w-3, h-1);
1248 }
1249 }
1250
1251 protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
1252 int width = tabPane.getWidth();
1253 int height = tabPane.getHeight();
1254 Insets insets = tabPane.getInsets();
1255 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1256
1257 int x = insets.left;
1258 int y = insets.top;
1259 int w = width - insets.right - insets.left;
1260 int h = height - insets.top - insets.bottom;
1261
1262 switch(tabPlacement) {
1263 case LEFT:
1264 x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1265 if (tabsOverlapBorder) {
1266 x -= tabAreaInsets.right;
1267 }
1268 w -= (x - insets.left);
1269 break;
1270 case RIGHT:
1271 w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1272 if (tabsOverlapBorder) {
1273 w += tabAreaInsets.left;
1274 }
1275 break;
1276 case BOTTOM:
1277 h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1278 if (tabsOverlapBorder) {
1279 h += tabAreaInsets.top;
1280 }
1281 break;
1282 case TOP:
1283 default:
1284 y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1285 if (tabsOverlapBorder) {
1286 y -= tabAreaInsets.bottom;
1287 }
1288 h -= (y - insets.top);
1289 }
1290
1291 if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) {
1292 // Fill region behind content area
1293 Color color = UIManager.getColor("TabbedPane.contentAreaColor");
1294 if (color != null) {
1295 g.setColor(color);
1296 }
1297 else if ( selectedColor == null || selectedIndex == -1 ) {
1298 g.setColor(tabPane.getBackground());
1299 }
1300 else {
1301 g.setColor(selectedColor);
1302 }
1303 g.fillRect(x,y,w,h);
1304 }
1305
1306 paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1307 paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1308 paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1309 paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
1310
1311 }
1312
1313 protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
1314 int selectedIndex,
1315 int x, int y, int w, int h) {
1316 Rectangle selRect = selectedIndex < 0? null :
1317 getTabBounds(selectedIndex, calcRect);
1318
1319 g.setColor(lightHighlight);
1320
1321 // Draw unbroken line if tabs are not on TOP, OR
1322 // selected tab is not in run adjacent to content, OR
1323 // selected tab is not visible (SCROLL_TAB_LAYOUT)
1324 //
1325 if (tabPlacement != TOP || selectedIndex < 0 ||
1326 (selRect.y + selRect.height + 1 < y) ||
1327 (selRect.x < x || selRect.x > x + w)) {
1328 g.drawLine(x, y, x+w-2, y);
1329 } else {
1330 // Break line to show visual connection to selected tab
1331 g.drawLine(x, y, selRect.x - 1, y);
1332 if (selRect.x + selRect.width < x + w - 2) {
1333 g.drawLine(selRect.x + selRect.width, y,
1334 x+w-2, y);
1335 } else {
1336 g.setColor(shadow);
1337 g.drawLine(x+w-2, y, x+w-2, y);
1338 }
1339 }
1340 }
1341
1342 protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
1343 int selectedIndex,
1344 int x, int y, int w, int h) {
1345 Rectangle selRect = selectedIndex < 0? null :
1346 getTabBounds(selectedIndex, calcRect);
1347
1348 g.setColor(lightHighlight);
1349
1350 // Draw unbroken line if tabs are not on LEFT, OR
1351 // selected tab is not in run adjacent to content, OR
1352 // selected tab is not visible (SCROLL_TAB_LAYOUT)
1353 //
1354 if (tabPlacement != LEFT || selectedIndex < 0 ||
1355 (selRect.x + selRect.width + 1 < x) ||
1356 (selRect.y < y || selRect.y > y + h)) {
1357 g.drawLine(x, y, x, y+h-2);
1358 } else {
1359 // Break line to show visual connection to selected tab
1360 g.drawLine(x, y, x, selRect.y - 1);
1361 if (selRect.y + selRect.height < y + h - 2) {
1362 g.drawLine(x, selRect.y + selRect.height,
1363 x, y+h-2);
1364 }
1365 }
1366 }
1367
1368 protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
1369 int selectedIndex,
1370 int x, int y, int w, int h) {
1371 Rectangle selRect = selectedIndex < 0? null :
1372 getTabBounds(selectedIndex, calcRect);
1373
1374 g.setColor(shadow);
1375
1376 // Draw unbroken line if tabs are not on BOTTOM, OR
1377 // selected tab is not in run adjacent to content, OR
1378 // selected tab is not visible (SCROLL_TAB_LAYOUT)
1379 //
1380 if (tabPlacement != BOTTOM || selectedIndex < 0 ||
1381 (selRect.y - 1 > h) ||
1382 (selRect.x < x || selRect.x > x + w)) {
1383 g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
1384 g.setColor(darkShadow);
1385 g.drawLine(x, y+h-1, x+w-1, y+h-1);
1386 } else {
1387 // Break line to show visual connection to selected tab
1388 g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
1389 g.setColor(darkShadow);
1390 g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
1391 if (selRect.x + selRect.width < x + w - 2) {
1392 g.setColor(shadow);
1393 g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
1394 g.setColor(darkShadow);
1395 g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
1396 }
1397 }
1398
1399 }
1400
1401 protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
1402 int selectedIndex,
1403 int x, int y, int w, int h) {
1404 Rectangle selRect = selectedIndex < 0? null :
1405 getTabBounds(selectedIndex, calcRect);
1406
1407 g.setColor(shadow);
1408
1409 // Draw unbroken line if tabs are not on RIGHT, OR
1410 // selected tab is not in run adjacent to content, OR
1411 // selected tab is not visible (SCROLL_TAB_LAYOUT)
1412 //
1413 if (tabPlacement != RIGHT || selectedIndex < 0 ||
1414 (selRect.x - 1 > w) ||
1415 (selRect.y < y || selRect.y > y + h)) {
1416 g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
1417 g.setColor(darkShadow);
1418 g.drawLine(x+w-1, y, x+w-1, y+h-1);
1419 } else {
1420 // Break line to show visual connection to selected tab
1421 g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
1422 g.setColor(darkShadow);
1423 g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
1424
1425 if (selRect.y + selRect.height < y + h - 2) {
1426 g.setColor(shadow);
1427 g.drawLine(x+w-2, selRect.y + selRect.height,
1428 x+w-2, y+h-2);
1429 g.setColor(darkShadow);
1430 g.drawLine(x+w-1, selRect.y + selRect.height,
1431 x+w-1, y+h-2);
1432 }
1433 }
1434 }
1435
1436 private void ensureCurrentLayout() {
1437 if (!tabPane.isValid()) {
1438 tabPane.validate();
1439 }
1440 /* If tabPane doesn't have a peer yet, the validate() call will
1441 * silently fail. We handle that by forcing a layout if tabPane
1442 * is still invalid. See bug 4237677.
1443 */
1444 if (!tabPane.isValid()) {
1445 TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
1446 layout.calculateLayoutInfo();
1447 }
1448 }
1449
1450
1451 // TabbedPaneUI methods
1452
1453 /**
1454 * Returns the bounds of the specified tab index. The bounds are
1455 * with respect to the JTabbedPane's coordinate space.
1456 */
1457 public Rectangle getTabBounds(JTabbedPane pane, int i) {
1458 ensureCurrentLayout();
1459 Rectangle tabRect = new Rectangle();
1460 return getTabBounds(i, tabRect);
1461 }
1462
1463 public int getTabRunCount(JTabbedPane pane) {
1464 ensureCurrentLayout();
1465 return runCount;
1466 }
1467
1468 /**
1469 * Returns the tab index which intersects the specified point
1470 * in the JTabbedPane's coordinate space.
1471 */
1472 public int tabForCoordinate(JTabbedPane pane, int x, int y) {
1473 return tabForCoordinate(pane, x, y, true);
1474 }
1475
1476 private int tabForCoordinate(JTabbedPane pane, int x, int y,
1477 boolean validateIfNecessary) {
1478 if (validateIfNecessary) {
1479 ensureCurrentLayout();
1480 }
1481 if (isRunsDirty) {
1482 // We didn't recalculate the layout, runs and tabCount may not
1483 // line up, bail.
1484 return -1;
1485 }
1486 Point p = new Point(x, y);
1487
1488 if (scrollableTabLayoutEnabled()) {
1489 translatePointToTabPanel(x, y, p);
1490 Rectangle viewRect = tabScroller.viewport.getViewRect();
1491 if (!viewRect.contains(p)) {
1492 return -1;
1493 }
1494 }
1495 int tabCount = tabPane.getTabCount();
1496 for (int i = 0; i < tabCount; i++) {
1497 if (rects[i].contains(p.x, p.y)) {
1498 return i;
1499 }
1500 }
1501 return -1;
1502 }
1503
1504 /**
1505 * Returns the bounds of the specified tab in the coordinate space
1506 * of the JTabbedPane component. This is required because the tab rects
1507 * are by default defined in the coordinate space of the component where
1508 * they are rendered, which could be the JTabbedPane
1509 * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1510 * This method should be used whenever the tab rectangle must be relative
1511 * to the JTabbedPane itself and the result should be placed in a
1512 * designated Rectangle object (rather than instantiating and returning
1513 * a new Rectangle each time). The tab index parameter must be a valid
1514 * tabbed pane tab index (0 to tab count - 1, inclusive). The destination
1515 * rectangle parameter must be a valid <code>Rectangle</code> instance.
1516 * The handling of invalid parameters is unspecified.
1517 *
1518 * @param tabIndex the index of the tab
1519 * @param dest the rectangle where the result should be placed
1520 * @return the resulting rectangle
1521 *
1522 * @since 1.4
1523 */
1524 protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
1525 dest.width = rects[tabIndex].width;
1526 dest.height = rects[tabIndex].height;
1527
1528 if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
1529 // Need to translate coordinates based on viewport location &
1530 // view position
1531 Point vpp = tabScroller.viewport.getLocation();
1532 Point viewp = tabScroller.viewport.getViewPosition();
1533 dest.x = rects[tabIndex].x + vpp.x - viewp.x;
1534 dest.y = rects[tabIndex].y + vpp.y - viewp.y;
1535
1536 } else { // WRAP_TAB_LAYOUT
1537 dest.x = rects[tabIndex].x;
1538 dest.y = rects[tabIndex].y;
1539 }
1540 return dest;
1541 }
1542
1543 /**
1544 * Returns the index of the tab closest to the passed in location, note
1545 * that the returned tab may not contain the location x,y.
1546 */
1547 private int getClosestTab(int x, int y) {
1548 int min = 0;
1549 int tabCount = Math.min(rects.length, tabPane.getTabCount());
1550 int max = tabCount;
1551 int tabPlacement = tabPane.getTabPlacement();
1552 boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
1553 int want = (useX) ? x : y;
1554
1555 while (min != max) {
1556 int current = (max + min) / 2;
1557 int minLoc;
1558 int maxLoc;
1559
1560 if (useX) {
1561 minLoc = rects[current].x;
1562 maxLoc = minLoc + rects[current].width;
1563 }
1564 else {
1565 minLoc = rects[current].y;
1566 maxLoc = minLoc + rects[current].height;
1567 }
1568 if (want < minLoc) {
1569 max = current;
1570 if (min == max) {
1571 return Math.max(0, current - 1);
1572 }
1573 }
1574 else if (want >= maxLoc) {
1575 min = current;
1576 if (max - min <= 1) {
1577 return Math.max(current + 1, tabCount - 1);
1578 }
1579 }
1580 else {
1581 return current;
1582 }
1583 }
1584 return min;
1585 }
1586
1587 /**
1588 * Returns a point which is translated from the specified point in the
1589 * JTabbedPane's coordinate space to the coordinate space of the
1590 * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
1591 */
1592 private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
1593 Point vpp = tabScroller.viewport.getLocation();
1594 Point viewp = tabScroller.viewport.getViewPosition();
1595 dest.x = srcx - vpp.x + viewp.x;
1596 dest.y = srcy - vpp.y + viewp.y;
1597 return dest;
1598 }
1599
1600 // BasicTabbedPaneUI methods
1601
1602 protected Component getVisibleComponent() {
1603 return visibleComponent;
1604 }
1605
1606 protected void setVisibleComponent(Component component) {
1607 if (visibleComponent != null
1608 && visibleComponent != component
1609 && visibleComponent.getParent() == tabPane
1610 && visibleComponent.isVisible()) {
1611
1612 visibleComponent.setVisible(false);
1613 }
1614 if (component != null && !component.isVisible()) {
1615 component.setVisible(true);
1616 }
1617 visibleComponent = component;
1618 }
1619
1620 protected void assureRectsCreated(int tabCount) {
1621 int rectArrayLen = rects.length;
1622 if (tabCount != rectArrayLen ) {
1623 Rectangle[] tempRectArray = new Rectangle[tabCount];
1624 System.arraycopy(rects, 0, tempRectArray, 0,
1625 Math.min(rectArrayLen, tabCount));
1626 rects = tempRectArray;
1627 for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
1628 rects[rectIndex] = new Rectangle();
1629 }
1630 }
1631
1632 }
1633
1634 protected void expandTabRunsArray() {
1635 int rectLen = tabRuns.length;
1636 int[] newArray = new int[rectLen+10];
1637 System.arraycopy(tabRuns, 0, newArray, 0, runCount);
1638 tabRuns = newArray;
1639 }
1640
1641 protected int getRunForTab(int tabCount, int tabIndex) {
1642 for (int i = 0; i < runCount; i++) {
1643 int first = tabRuns[i];
1644 int last = lastTabInRun(tabCount, i);
1645 if (tabIndex >= first && tabIndex <= last) {
1646 return i;
1647 }
1648 }
1649 return 0;
1650 }
1651
1652 protected int lastTabInRun(int tabCount, int run) {
1653 if (runCount == 1) {
1654 return tabCount - 1;
1655 }
1656 int nextRun = (run == runCount - 1? 0 : run + 1);
1657 if (tabRuns[nextRun] == 0) {
1658 return tabCount - 1;
1659 }
1660 return tabRuns[nextRun]-1;
1661 }
1662
1663 protected int getTabRunOverlay(int tabPlacement) {
1664 return tabRunOverlay;
1665 }
1666
1667 protected int getTabRunIndent(int tabPlacement, int run) {
1668 return 0;
1669 }
1670
1671 protected boolean shouldPadTabRun(int tabPlacement, int run) {
1672 return runCount > 1;
1673 }
1674
1675 protected boolean shouldRotateTabRuns(int tabPlacement) {
1676 return true;
1677 }
1678
1679 protected Icon getIconForTab(int tabIndex) {
1680 return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
1681 tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
1682 }
1683
1684 /**
1685 * Returns the text View object required to render stylized text (HTML) for
1686 * the specified tab or null if no specialized text rendering is needed
1687 * for this tab. This is provided to support html rendering inside tabs.
1688 *
1689 * @param tabIndex the index of the tab
1690 * @return the text view to render the tab's text or null if no
1691 * specialized rendering is required
1692 *
1693 * @since 1.4
1694 */
1695 protected View getTextViewForTab(int tabIndex) {
1696 if (htmlViews != null) {
1697 return (View)htmlViews.elementAt(tabIndex);
1698 }
1699 return null;
1700 }
1701
1702 protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
1703 int height = 0;
1704 Component c = tabPane.getTabComponentAt(tabIndex);
1705 if (c != null) {
1706 height = c.getPreferredSize().height;
1707 } else {
1708 View v = getTextViewForTab(tabIndex);
1709 if (v != null) {
1710 // html
1711 height += (int) v.getPreferredSpan(View.Y_AXIS);
1712 } else {
1713 // plain text
1714 height += fontHeight;
1715 }
1716 Icon icon = getIconForTab(tabIndex);
1717
1718 if (icon != null) {
1719 height = Math.max(height, icon.getIconHeight());
1720 }
1721 }
1722 Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1723 height += tabInsets.top + tabInsets.bottom + 2;
1724 return height;
1725 }
1726
1727 protected int calculateMaxTabHeight(int tabPlacement) {
1728 FontMetrics metrics = getFontMetrics();
1729 int tabCount = tabPane.getTabCount();
1730 int result = 0;
1731 int fontHeight = metrics.getHeight();
1732 for(int i = 0; i < tabCount; i++) {
1733 result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
1734 }
1735 return result;
1736 }
1737
1738 protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
1739 Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1740 int width = tabInsets.left + tabInsets.right + 3;
1741 Component tabComponent = tabPane.getTabComponentAt(tabIndex);
1742 if (tabComponent != null) {
1743 width += tabComponent.getPreferredSize().width;
1744 } else {
1745 Icon icon = getIconForTab(tabIndex);
1746 if (icon != null) {
1747 width += icon.getIconWidth() + textIconGap;
1748 }
1749 View v = getTextViewForTab(tabIndex);
1750 if (v != null) {
1751 // html
1752 width += (int) v.getPreferredSpan(View.X_AXIS);
1753 } else {
1754 // plain text
1755 String title = tabPane.getTitleAt(tabIndex);
1756 width += SwingUtilities2.stringWidth(tabPane, metrics, title);
1757 }
1758 }
1759 return width;
1760 }
1761
1762 protected int calculateMaxTabWidth(int tabPlacement) {
1763 FontMetrics metrics = getFontMetrics();
1764 int tabCount = tabPane.getTabCount();
1765 int result = 0;
1766 for(int i = 0; i < tabCount; i++) {
1767 result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
1768 }
1769 return result;
1770 }
1771
1772 protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
1773 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1774 int tabRunOverlay = getTabRunOverlay(tabPlacement);
1775 return (horizRunCount > 0?
1776 horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
1777 tabAreaInsets.top + tabAreaInsets.bottom :
1778 0);
1779 }
1780
1781 protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
1782 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1783 int tabRunOverlay = getTabRunOverlay(tabPlacement);
1784 return (vertRunCount > 0?
1785 vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
1786 tabAreaInsets.left + tabAreaInsets.right :
1787 0);
1788 }
1789
1790 protected Insets getTabInsets(int tabPlacement, int tabIndex) {
1791 return tabInsets;
1792 }
1793
1794 protected Insets getSelectedTabPadInsets(int tabPlacement) {
1795 rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
1796 return currentPadInsets;
1797 }
1798
1799 protected Insets getTabAreaInsets(int tabPlacement) {
1800 rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
1801 return currentTabAreaInsets;
1802 }
1803
1804 protected Insets getContentBorderInsets(int tabPlacement) {
1805 return contentBorderInsets;
1806 }
1807
1808 protected FontMetrics getFontMetrics() {
1809 Font font = tabPane.getFont();
1810 return tabPane.getFontMetrics(font);
1811 }
1812
1813
1814 // Tab Navigation methods
1815
1816 protected void navigateSelectedTab(int direction) {
1817 int tabPlacement = tabPane.getTabPlacement();
1818 int current = DefaultLookup.getBoolean(tabPane, this,
1819 "TabbedPane.selectionFollowsFocus", true) ?
1820 tabPane.getSelectedIndex() : getFocusIndex();
1821 int tabCount = tabPane.getTabCount();
1822 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
1823
1824 // If we have no tabs then don't navigate.
1825 if (tabCount <= 0) {
1826 return;
1827 }
1828
1829 int offset;
1830 switch(tabPlacement) {
1831 case LEFT:
1832 case RIGHT:
1833 switch(direction) {
1834 case NEXT:
1835 selectNextTab(current);
1836 break;
1837 case PREVIOUS:
1838 selectPreviousTab(current);
1839 break;
1840 case NORTH:
1841 selectPreviousTabInRun(current);
1842 break;
1843 case SOUTH:
1844 selectNextTabInRun(current);
1845 break;
1846 case WEST:
1847 offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1848 selectAdjacentRunTab(tabPlacement, current, offset);
1849 break;
1850 case EAST:
1851 offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1852 selectAdjacentRunTab(tabPlacement, current, offset);
1853 break;
1854 default:
1855 }
1856 break;
1857 case BOTTOM:
1858 case TOP:
1859 default:
1860 switch(direction) {
1861 case NEXT:
1862 selectNextTab(current);
1863 break;
1864 case PREVIOUS:
1865 selectPreviousTab(current);
1866 break;
1867 case NORTH:
1868 offset = getTabRunOffset(tabPlacement, tabCount, current, false);
1869 selectAdjacentRunTab(tabPlacement, current, offset);
1870 break;
1871 case SOUTH:
1872 offset = getTabRunOffset(tabPlacement, tabCount, current, true);
1873 selectAdjacentRunTab(tabPlacement, current, offset);
1874 break;
1875 case EAST:
1876 if (leftToRight) {
1877 selectNextTabInRun(current);
1878 } else {
1879 selectPreviousTabInRun(current);
1880 }
1881 break;
1882 case WEST:
1883 if (leftToRight) {
1884 selectPreviousTabInRun(current);
1885 } else {
1886 selectNextTabInRun(current);
1887 }
1888 break;
1889 default:
1890 }
1891 }
1892 }
1893
1894 protected void selectNextTabInRun(int current) {
1895 int tabCount = tabPane.getTabCount();
1896 int tabIndex = getNextTabIndexInRun(tabCount, current);
1897
1898 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1899 tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
1900 }
1901 navigateTo(tabIndex);
1902 }
1903
1904 protected void selectPreviousTabInRun(int current) {
1905 int tabCount = tabPane.getTabCount();
1906 int tabIndex = getPreviousTabIndexInRun(tabCount, current);
1907
1908 while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1909 tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
1910 }
1911 navigateTo(tabIndex);
1912 }
1913
1914 protected void selectNextTab(int current) {
1915 int tabIndex = getNextTabIndex(current);
1916
1917 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1918 tabIndex = getNextTabIndex(tabIndex);
1919 }
1920 navigateTo(tabIndex);
1921 }
1922
1923 protected void selectPreviousTab(int current) {
1924 int tabIndex = getPreviousTabIndex(current);
1925
1926 while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1927 tabIndex = getPreviousTabIndex(tabIndex);
1928 }
1929 navigateTo(tabIndex);
1930 }
1931
1932 protected void selectAdjacentRunTab(int tabPlacement,
1933 int tabIndex, int offset) {
1934 if ( runCount < 2 ) {
1935 return;
1936 }
1937 int newIndex;
1938 Rectangle r = rects[tabIndex];
1939 switch(tabPlacement) {
1940 case LEFT:
1941 case RIGHT:
1942 newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset,
1943 r.y + r.height/2);
1944 break;
1945 case BOTTOM:
1946 case TOP:
1947 default:
1948 newIndex = tabForCoordinate(tabPane, r.x + r.width/2,
1949 r.y + r.height/2 + offset);
1950 }
1951 if (newIndex != -1) {
1952 while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
1953 newIndex = getNextTabIndex(newIndex);
1954 }
1955 navigateTo(newIndex);
1956 }
1957 }
1958
1959 private void navigateTo(int index) {
1960 if (DefaultLookup.getBoolean(tabPane, this,
1961 "TabbedPane.selectionFollowsFocus", true)) {
1962 tabPane.setSelectedIndex(index);
1963 } else {
1964 // Just move focus (not selection)
1965 setFocusIndex(index, true);
1966 }
1967 }
1968
1969 void setFocusIndex(int index, boolean repaint) {
1970 if (repaint && !isRunsDirty) {
1971 repaintTab(focusIndex);
1972 focusIndex = index;
1973 repaintTab(focusIndex);
1974 }
1975 else {
1976 focusIndex = index;
1977 }
1978 }
1979
1980 /**
1981 * Repaints the specified tab.
1982 */
1983 private void repaintTab(int index) {
1984 // If we're not valid that means we will shortly be validated and
1985 // painted, which means we don't have to do anything here.
1986 if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
1987 tabPane.repaint(getTabBounds(tabPane, index));
1988 }
1989 }
1990
1991 /**
1992 * Makes sure the focusIndex is valid.
1993 */
1994 private void validateFocusIndex() {
1995 if (focusIndex >= tabPane.getTabCount()) {
1996 setFocusIndex(tabPane.getSelectedIndex(), false);
1997 }
1998 }
1999
2000 /**
2001 * Returns the index of the tab that has focus.
2002 *
2003 * @return index of tab that has focus
2004 * @since 1.5
2005 */
2006 protected int getFocusIndex() {
2007 return focusIndex;
2008 }
2009
2010 protected int getTabRunOffset(int tabPlacement, int tabCount,
2011 int tabIndex, boolean forward) {
2012 int run = getRunForTab(tabCount, tabIndex);
2013 int offset;
2014 switch(tabPlacement) {
2015 case LEFT: {
2016 if (run == 0) {
2017 offset = (forward?
2018 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2019 -maxTabWidth);
2020
2021 } else if (run == runCount - 1) {
2022 offset = (forward?
2023 maxTabWidth :
2024 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2025 } else {
2026 offset = (forward? maxTabWidth : -maxTabWidth);
2027 }
2028 break;
2029 }
2030 case RIGHT: {
2031 if (run == 0) {
2032 offset = (forward?
2033 maxTabWidth :
2034 calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
2035 } else if (run == runCount - 1) {
2036 offset = (forward?
2037 -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
2038 -maxTabWidth);
2039 } else {
2040 offset = (forward? maxTabWidth : -maxTabWidth);
2041 }
2042 break;
2043 }
2044 case BOTTOM: {
2045 if (run == 0) {
2046 offset = (forward?
2047 maxTabHeight :
2048 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2049 } else if (run == runCount - 1) {
2050 offset = (forward?
2051 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2052 -maxTabHeight);
2053 } else {
2054 offset = (forward? maxTabHeight : -maxTabHeight);
2055 }
2056 break;
2057 }
2058 case TOP:
2059 default: {
2060 if (run == 0) {
2061 offset = (forward?
2062 -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
2063 -maxTabHeight);
2064 } else if (run == runCount - 1) {
2065 offset = (forward?
2066 maxTabHeight :
2067 calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
2068 } else {
2069 offset = (forward? maxTabHeight : -maxTabHeight);
2070 }
2071 }
2072 }
2073 return offset;
2074 }
2075
2076 protected int getPreviousTabIndex(int base) {
2077 int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
2078 return (tabIndex >= 0? tabIndex : 0);
2079 }
2080
2081 protected int getNextTabIndex(int base) {
2082 return (base+1)%tabPane.getTabCount();
2083 }
2084
2085 protected int getNextTabIndexInRun(int tabCount, int base) {
2086 if (runCount < 2) {
2087 return getNextTabIndex(base);
2088 }
2089 int currentRun = getRunForTab(tabCount, base);
2090 int next = getNextTabIndex(base);
2091 if (next == tabRuns[getNextTabRun(currentRun)]) {
2092 return tabRuns[currentRun];
2093 }
2094 return next;
2095 }
2096
2097 protected int getPreviousTabIndexInRun(int tabCount, int base) {
2098 if (runCount < 2) {
2099 return getPreviousTabIndex(base);
2100 }
2101 int currentRun = getRunForTab(tabCount, base);
2102 if (base == tabRuns[currentRun]) {
2103 int previous = tabRuns[getNextTabRun(currentRun)]-1;
2104 return (previous != -1? previous : tabCount-1);
2105 }
2106 return getPreviousTabIndex(base);
2107 }
2108
2109 protected int getPreviousTabRun(int baseRun) {
2110 int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
2111 return (runIndex >= 0? runIndex : 0);
2112 }
2113
2114 protected int getNextTabRun(int baseRun) {
2115 return (baseRun+1)%runCount;
2116 }
2117
2118 protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
2119
2120 switch(targetPlacement) {
2121 case LEFT:
2122 targetInsets.top = topInsets.left;
2123 targetInsets.left = topInsets.top;
2124 targetInsets.bottom = topInsets.right;
2125 targetInsets.right = topInsets.bottom;
2126 break;
2127 case BOTTOM:
2128 targetInsets.top = topInsets.bottom;
2129 targetInsets.left = topInsets.left;
2130 targetInsets.bottom = topInsets.top;
2131 targetInsets.right = topInsets.right;
2132 break;
2133 case RIGHT:
2134 targetInsets.top = topInsets.left;
2135 targetInsets.left = topInsets.bottom;
2136 targetInsets.bottom = topInsets.right;
2137 targetInsets.right = topInsets.top;
2138 break;
2139 case TOP:
2140 default:
2141 targetInsets.top = topInsets.top;
2142 targetInsets.left = topInsets.left;
2143 targetInsets.bottom = topInsets.bottom;
2144 targetInsets.right = topInsets.right;
2145 }
2146 }
2147
2148 // REMIND(aim,7/29/98): This method should be made
2149 // protected in the next release where
2150 // API changes are allowed
2151 boolean requestFocusForVisibleComponent() {
2152 return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
2153 }
2154
2155 private static class Actions extends UIAction {
2156 final static String NEXT = "navigateNext";
2157 final static String PREVIOUS = "navigatePrevious";
2158 final static String RIGHT = "navigateRight";
2159 final static String LEFT = "navigateLeft";
2160 final static String UP = "navigateUp";
2161 final static String DOWN = "navigateDown";
2162 final static String PAGE_UP = "navigatePageUp";
2163 final static String PAGE_DOWN = "navigatePageDown";
2164 final static String REQUEST_FOCUS = "requestFocus";
2165 final static String REQUEST_FOCUS_FOR_VISIBLE =
2166 "requestFocusForVisibleComponent";
2167 final static String SET_SELECTED = "setSelectedIndex";
2168 final static String SELECT_FOCUSED = "selectTabWithFocus";
2169 final static String SCROLL_FORWARD = "scrollTabsForwardAction";
2170 final static String SCROLL_BACKWARD = "scrollTabsBackwardAction";
2171
2172 Actions(String key) {
2173 super(key);
2174 }
2175
2176 public void actionPerformed(ActionEvent e) {
2177 String key = getName();
2178 JTabbedPane pane = (JTabbedPane)e.getSource();
2179 BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel.
2180 getUIOfType(pane.getUI(), BasicTabbedPaneUI.class);
2181
2182 if (ui == null) {
2183 return;
2184 }
2185 if (key == NEXT) {
2186 ui.navigateSelectedTab(SwingConstants.NEXT);
2187 }
2188 else if (key == PREVIOUS) {
2189 ui.navigateSelectedTab(SwingConstants.PREVIOUS);
2190 }
2191 else if (key == RIGHT) {
2192 ui.navigateSelectedTab(SwingConstants.EAST);
2193 }
2194 else if (key == LEFT) {
2195 ui.navigateSelectedTab(SwingConstants.WEST);
2196 }
2197 else if (key == UP) {
2198 ui.navigateSelectedTab(SwingConstants.NORTH);
2199 }
2200 else if (key == DOWN) {
2201 ui.navigateSelectedTab(SwingConstants.SOUTH);
2202 }
2203 else if (key == PAGE_UP) {
2204 int tabPlacement = pane.getTabPlacement();
2205 if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
2206 ui.navigateSelectedTab(SwingConstants.WEST);
2207 } else {
2208 ui.navigateSelectedTab(SwingConstants.NORTH);
2209 }
2210 }
2211 else if (key == PAGE_DOWN) {
2212 int tabPlacement = pane.getTabPlacement();
2213 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2214 ui.navigateSelectedTab(SwingConstants.EAST);
2215 } else {
2216 ui.navigateSelectedTab(SwingConstants.SOUTH);
2217 }
2218 }
2219 else if (key == REQUEST_FOCUS) {
2220 pane.requestFocus();
2221 }
2222 else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
2223 ui.requestFocusForVisibleComponent();
2224 }
2225 else if (key == SET_SELECTED) {
2226 String command = e.getActionCommand();
2227
2228 if (command != null && command.length() > 0) {
2229 int mnemonic = (int)e.getActionCommand().charAt(0);
2230 if (mnemonic >= 'a' && mnemonic <='z') {
2231 mnemonic -= ('a' - 'A');
2232 }
2233 Integer index = (Integer)ui.mnemonicToIndexMap.
2234 get(Integer.valueOf(mnemonic));
2235 if (index != null && pane.isEnabledAt(index.intValue())) {
2236 pane.setSelectedIndex(index.intValue());
2237 }
2238 }
2239 }
2240 else if (key == SELECT_FOCUSED) {
2241 int focusIndex = ui.getFocusIndex();
2242 if (focusIndex != -1) {
2243 pane.setSelectedIndex(focusIndex);
2244 }
2245 }
2246 else if (key == SCROLL_FORWARD) {
2247 if (ui.scrollableTabLayoutEnabled()) {
2248 ui.tabScroller.scrollForward(pane.getTabPlacement());
2249 }
2250 }
2251 else if (key == SCROLL_BACKWARD) {
2252 if (ui.scrollableTabLayoutEnabled()) {
2253 ui.tabScroller.scrollBackward(pane.getTabPlacement());
2254 }
2255 }
2256 }
2257 }
2258
2259 /**
2260 * This class should be treated as a "protected" inner class.
2261 * Instantiate it only within subclasses of BasicTabbedPaneUI.
2262 */
2263 public class TabbedPaneLayout implements LayoutManager {
2264
2265 public void addLayoutComponent(String name, Component comp) {}
2266
2267 public void removeLayoutComponent(Component comp) {}
2268
2269 public Dimension preferredLayoutSize(Container parent) {
2270 return calculateSize(false);
2271 }
2272
2273 public Dimension minimumLayoutSize(Container parent) {
2274 return calculateSize(true);
2275 }
2276
2277 protected Dimension calculateSize(boolean minimum) {
2278 int tabPlacement = tabPane.getTabPlacement();
2279 Insets insets = tabPane.getInsets();
2280 Insets contentInsets = getContentBorderInsets(tabPlacement);
2281 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2282
2283 Dimension zeroSize = new Dimension(0,0);
2284 int height = 0;
2285 int width = 0;
2286 int cWidth = 0;
2287 int cHeight = 0;
2288
2289 // Determine minimum size required to display largest
2290 // child in each dimension
2291 //
2292 for (int i = 0; i < tabPane.getTabCount(); i++) {
2293 Component component = tabPane.getComponentAt(i);
2294 if (component != null) {
2295 Dimension size = zeroSize;
2296 size = minimum? component.getMinimumSize() :
2297 component.getPreferredSize();
2298
2299 if (size != null) {
2300 cHeight = Math.max(size.height, cHeight);
2301 cWidth = Math.max(size.width, cWidth);
2302 }
2303 }
2304 }
2305 // Add content border insets to minimum size
2306 width += cWidth;
2307 height += cHeight;
2308 int tabExtent = 0;
2309
2310 // Calculate how much space the tabs will need, based on the
2311 // minimum size required to display largest child + content border
2312 //
2313 switch(tabPlacement) {
2314 case LEFT:
2315 case RIGHT:
2316 height = Math.max(height, calculateMaxTabHeight(tabPlacement));
2317 tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
2318 width += tabExtent;
2319 break;
2320 case TOP:
2321 case BOTTOM:
2322 default:
2323 width = Math.max(width, calculateMaxTabWidth(tabPlacement));
2324 tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
2325 height += tabExtent;
2326 }
2327 return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right,
2328 height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom);
2329
2330 }
2331
2332 protected int preferredTabAreaHeight(int tabPlacement, int width) {
2333 FontMetrics metrics = getFontMetrics();
2334 int tabCount = tabPane.getTabCount();
2335 int total = 0;
2336 if (tabCount > 0) {
2337 int rows = 1;
2338 int x = 0;
2339
2340 int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2341
2342 for (int i = 0; i < tabCount; i++) {
2343 int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
2344
2345 if (x != 0 && x + tabWidth > width) {
2346 rows++;
2347 x = 0;
2348 }
2349 x += tabWidth;
2350 }
2351 total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
2352 }
2353 return total;
2354 }
2355
2356 protected int preferredTabAreaWidth(int tabPlacement, int height) {
2357 FontMetrics metrics = getFontMetrics();
2358 int tabCount = tabPane.getTabCount();
2359 int total = 0;
2360 if (tabCount > 0) {
2361 int columns = 1;
2362 int y = 0;
2363 int fontHeight = metrics.getHeight();
2364
2365 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2366
2367 for (int i = 0; i < tabCount; i++) {
2368 int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
2369
2370 if (y != 0 && y + tabHeight > height) {
2371 columns++;
2372 y = 0;
2373 }
2374 y += tabHeight;
2375 }
2376 total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
2377 }
2378 return total;
2379 }
2380
2381 public void layoutContainer(Container parent) {
2382 /* Some of the code in this method deals with changing the
2383 * visibility of components to hide and show the contents for the
2384 * selected tab. This is older code that has since been duplicated
2385 * in JTabbedPane.fireStateChanged(), so as to allow visibility
2386 * changes to happen sooner (see the note there). This code remains
2387 * for backward compatibility as there are some cases, such as
2388 * subclasses that don't fireStateChanged() where it may be used.
2389 * Any changes here need to be kept in synch with
2390 * JTabbedPane.fireStateChanged().
2391 */
2392
2393 setRolloverTab(-1);
2394
2395 int tabPlacement = tabPane.getTabPlacement();
2396 Insets insets = tabPane.getInsets();
2397 int selectedIndex = tabPane.getSelectedIndex();
2398 Component visibleComponent = getVisibleComponent();
2399
2400 calculateLayoutInfo();
2401
2402 Component selectedComponent = null;
2403 if (selectedIndex < 0) {
2404 if (visibleComponent != null) {
2405 // The last tab was removed, so remove the component
2406 setVisibleComponent(null);
2407 }
2408 } else {
2409 selectedComponent = tabPane.getComponentAt(selectedIndex);
2410 }
2411 int cx, cy, cw, ch;
2412 int totalTabWidth = 0;
2413 int totalTabHeight = 0;
2414 Insets contentInsets = getContentBorderInsets(tabPlacement);
2415
2416 boolean shouldChangeFocus = false;
2417
2418 // In order to allow programs to use a single component
2419 // as the display for multiple tabs, we will not change
2420 // the visible compnent if the currently selected tab
2421 // has a null component. This is a bit dicey, as we don't
2422 // explicitly state we support this in the spec, but since
2423 // programs are now depending on this, we're making it work.
2424 //
2425 if(selectedComponent != null) {
2426 if(selectedComponent != visibleComponent &&
2427 visibleComponent != null) {
2428 if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2429 shouldChangeFocus = true;
2430 }
2431 }
2432 setVisibleComponent(selectedComponent);
2433 }
2434
2435 Rectangle bounds = tabPane.getBounds();
2436 int numChildren = tabPane.getComponentCount();
2437
2438 if(numChildren > 0) {
2439
2440 switch(tabPlacement) {
2441 case LEFT:
2442 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2443 cx = insets.left + totalTabWidth + contentInsets.left;
2444 cy = insets.top + contentInsets.top;
2445 break;
2446 case RIGHT:
2447 totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2448 cx = insets.left + contentInsets.left;
2449 cy = insets.top + contentInsets.top;
2450 break;
2451 case BOTTOM:
2452 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2453 cx = insets.left + contentInsets