blob: 3afdc74d16e0b14098b0e63dc0995adee23336ca [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package javax.swing.plaf.basic;
27
28import sun.swing.SwingUtilities2;
29
30import javax.swing.*;
31import javax.swing.event.*;
32import javax.swing.plaf.*;
33import javax.swing.text.View;
34
35import java.awt.*;
36import java.awt.event.*;
37import java.beans.PropertyChangeListener;
38import java.beans.PropertyChangeEvent;
39import java.util.Vector;
40import java.util.Hashtable;
41
42import sun.swing.DefaultLookup;
43import 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 */
54public 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(new Integer(mnemonic), new Integer(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 &lt; 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(new Integer(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 &quot;protected&quot; 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.left;
2454 cy = insets.top + contentInsets.top;
2455 break;
2456 case TOP:
2457 default:
2458 totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2459 cx = insets.left + contentInsets.left;
2460 cy = insets.top + totalTabHeight + contentInsets.top;
2461 }
2462
2463 cw = bounds.width - totalTabWidth -
2464 insets.left - insets.right -
2465 contentInsets.left - contentInsets.right;
2466 ch = bounds.height - totalTabHeight -
2467 insets.top - insets.bottom -
2468 contentInsets.top - contentInsets.bottom;
2469
2470 for(int i = 0; i < numChildren; i++) {
2471 Component child = tabPane.getComponent(i);
2472 if(child == tabContainer) {
2473
2474 int tabContainerWidth = totalTabWidth == 0 ? bounds.width :
2475 totalTabWidth + insets.left + insets.right +
2476 contentInsets.left + contentInsets.right;
2477 int tabContainerHeight = totalTabHeight == 0 ? bounds.height :
2478 totalTabHeight + insets.top + insets.bottom +
2479 contentInsets.top + contentInsets.bottom;
2480
2481 int tabContainerX = 0;
2482 int tabContainerY = 0;
2483 if(tabPlacement == BOTTOM) {
2484 tabContainerY = bounds.height - tabContainerHeight;
2485 } else if(tabPlacement == RIGHT) {
2486 tabContainerX = bounds.width - tabContainerWidth;
2487 }
2488 child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
2489 } else {
2490 child.setBounds(cx, cy, cw, ch);
2491 }
2492 }
2493 }
2494 layoutTabComponents();
2495 if(shouldChangeFocus) {
2496 if(!requestFocusForVisibleComponent()) {
2497 tabPane.requestFocus();
2498 }
2499 }
2500 }
2501
2502 public void calculateLayoutInfo() {
2503 int tabCount = tabPane.getTabCount();
2504 assureRectsCreated(tabCount);
2505 calculateTabRects(tabPane.getTabPlacement(), tabCount);
2506 isRunsDirty = false;
2507 }
2508
2509 private void layoutTabComponents() {
2510 if (tabContainer == null) {
2511 return;
2512 }
2513 Rectangle rect = new Rectangle();
2514 Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
2515 if (scrollableTabLayoutEnabled()) {
2516 translatePointToTabPanel(0, 0, delta);
2517 }
2518 for (int i = 0; i < tabPane.getTabCount(); i++) {
2519 Component c = tabPane.getTabComponentAt(i);
2520 if (c == null) {
2521 continue;
2522 }
2523 getTabBounds(i, rect);
2524 Dimension preferredSize = c.getPreferredSize();
2525 Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
2526 int outerX = rect.x + insets.left + delta.x;
2527 int outerY = rect.y + insets.top + delta.y;
2528 int outerWidth = rect.width - insets.left - insets.right;
2529 int outerHeight = rect.height - insets.top - insets.bottom;
2530 //centralize component
2531 int x = outerX + (outerWidth - preferredSize.width) / 2;
2532 int y = outerY + (outerHeight - preferredSize.height) / 2;
2533 int tabPlacement = tabPane.getTabPlacement();
2534 boolean isSeleceted = i == tabPane.getSelectedIndex();
2535 c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
2536 y + getTabLabelShiftY(tabPlacement, i, isSeleceted),
2537 preferredSize.width, preferredSize.height);
2538 }
2539 }
2540
2541 protected void calculateTabRects(int tabPlacement, int tabCount) {
2542 FontMetrics metrics = getFontMetrics();
2543 Dimension size = tabPane.getSize();
2544 Insets insets = tabPane.getInsets();
2545 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2546 int fontHeight = metrics.getHeight();
2547 int selectedIndex = tabPane.getSelectedIndex();
2548 int tabRunOverlay;
2549 int i, j;
2550 int x, y;
2551 int returnAt;
2552 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2553 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
2554
2555 //
2556 // Calculate bounds within which a tab run must fit
2557 //
2558 switch(tabPlacement) {
2559 case LEFT:
2560 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2561 x = insets.left + tabAreaInsets.left;
2562 y = insets.top + tabAreaInsets.top;
2563 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2564 break;
2565 case RIGHT:
2566 maxTabWidth = calculateMaxTabWidth(tabPlacement);
2567 x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
2568 y = insets.top + tabAreaInsets.top;
2569 returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
2570 break;
2571 case BOTTOM:
2572 maxTabHeight = calculateMaxTabHeight(tabPlacement);
2573 x = insets.left + tabAreaInsets.left;
2574 y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
2575 returnAt = size.width - (insets.right + tabAreaInsets.right);
2576 break;
2577 case TOP:
2578 default:
2579 maxTabHeight = calculateMaxTabHeight(tabPlacement);
2580 x = insets.left + tabAreaInsets.left;
2581 y = insets.top + tabAreaInsets.top;
2582 returnAt = size.width - (insets.right + tabAreaInsets.right);
2583 break;
2584 }
2585
2586 tabRunOverlay = getTabRunOverlay(tabPlacement);
2587
2588 runCount = 0;
2589 selectedRun = -1;
2590
2591 if (tabCount == 0) {
2592 return;
2593 }
2594
2595 // Run through tabs and partition them into runs
2596 Rectangle rect;
2597 for (i = 0; i < tabCount; i++) {
2598 rect = rects[i];
2599
2600 if (!verticalTabRuns) {
2601 // Tabs on TOP or BOTTOM....
2602 if (i > 0) {
2603 rect.x = rects[i-1].x + rects[i-1].width;
2604 } else {
2605 tabRuns[0] = 0;
2606 runCount = 1;
2607 maxTabWidth = 0;
2608 rect.x = x;
2609 }
2610 rect.width = calculateTabWidth(tabPlacement, i, metrics);
2611 maxTabWidth = Math.max(maxTabWidth, rect.width);
2612
2613 // Never move a TAB down a run if it is in the first column.
2614 // Even if there isn't enough room, moving it to a fresh
2615 // line won't help.
2616 if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
2617 if (runCount > tabRuns.length - 1) {
2618 expandTabRunsArray();
2619 }
2620 tabRuns[runCount] = i;
2621 runCount++;
2622 rect.x = x;
2623 }
2624 // Initialize y position in case there's just one run
2625 rect.y = y;
2626 rect.height = maxTabHeight/* - 2*/;
2627
2628 } else {
2629 // Tabs on LEFT or RIGHT...
2630 if (i > 0) {
2631 rect.y = rects[i-1].y + rects[i-1].height;
2632 } else {
2633 tabRuns[0] = 0;
2634 runCount = 1;
2635 maxTabHeight = 0;
2636 rect.y = y;
2637 }
2638 rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
2639 maxTabHeight = Math.max(maxTabHeight, rect.height);
2640
2641 // Never move a TAB over a run if it is in the first run.
2642 // Even if there isn't enough room, moving it to a fresh
2643 // column won't help.
2644 if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
2645 if (runCount > tabRuns.length - 1) {
2646 expandTabRunsArray();
2647 }
2648 tabRuns[runCount] = i;
2649 runCount++;
2650 rect.y = y;
2651 }
2652 // Initialize x position in case there's just one column
2653 rect.x = x;
2654 rect.width = maxTabWidth/* - 2*/;
2655
2656 }
2657 if (i == selectedIndex) {
2658 selectedRun = runCount - 1;
2659 }
2660 }
2661
2662 if (runCount > 1) {
2663 // Re-distribute tabs in case last run has leftover space
2664 normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
2665
2666 selectedRun = getRunForTab(tabCount, selectedIndex);
2667
2668 // Rotate run array so that selected run is first
2669 if (shouldRotateTabRuns(tabPlacement)) {
2670 rotateTabRuns(tabPlacement, selectedRun);
2671 }
2672 }
2673
2674 // Step through runs from back to front to calculate
2675 // tab y locations and to pad runs appropriately
2676 for (i = runCount - 1; i >= 0; i--) {
2677 int start = tabRuns[i];
2678 int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
2679 int end = (next != 0? next - 1 : tabCount - 1);
2680 if (!verticalTabRuns) {
2681 for (j = start; j <= end; j++) {
2682 rect = rects[j];
2683 rect.y = y;
2684 rect.x += getTabRunIndent(tabPlacement, i);
2685 }
2686 if (shouldPadTabRun(tabPlacement, i)) {
2687 padTabRun(tabPlacement, start, end, returnAt);
2688 }
2689 if (tabPlacement == BOTTOM) {
2690 y -= (maxTabHeight - tabRunOverlay);
2691 } else {
2692 y += (maxTabHeight - tabRunOverlay);
2693 }
2694 } else {
2695 for (j = start; j <= end; j++) {
2696 rect = rects[j];
2697 rect.x = x;
2698 rect.y += getTabRunIndent(tabPlacement, i);
2699 }
2700 if (shouldPadTabRun(tabPlacement, i)) {
2701 padTabRun(tabPlacement, start, end, returnAt);
2702 }
2703 if (tabPlacement == RIGHT) {
2704 x -= (maxTabWidth - tabRunOverlay);
2705 } else {
2706 x += (maxTabWidth - tabRunOverlay);
2707 }
2708 }
2709 }
2710
2711 // Pad the selected tab so that it appears raised in front
2712 padSelectedTab(tabPlacement, selectedIndex);
2713
2714 // if right to left and tab placement on the top or
2715 // the bottom, flip x positions and adjust by widths
2716 if (!leftToRight && !verticalTabRuns) {
2717 int rightMargin = size.width
2718 - (insets.right + tabAreaInsets.right);
2719 for (i = 0; i < tabCount; i++) {
2720 rects[i].x = rightMargin - rects[i].x - rects[i].width;
2721 }
2722 }
2723 }
2724
2725
2726 /*
2727 * Rotates the run-index array so that the selected run is run[0]
2728 */
2729 protected void rotateTabRuns(int tabPlacement, int selectedRun) {
2730 for (int i = 0; i < selectedRun; i++) {
2731 int save = tabRuns[0];
2732 for (int j = 1; j < runCount; j++) {
2733 tabRuns[j - 1] = tabRuns[j];
2734 }
2735 tabRuns[runCount-1] = save;
2736 }
2737 }
2738
2739 protected void normalizeTabRuns(int tabPlacement, int tabCount,
2740 int start, int max) {
2741 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2742 int run = runCount - 1;
2743 boolean keepAdjusting = true;
2744 double weight = 1.25;
2745
2746 // At this point the tab runs are packed to fit as many
2747 // tabs as possible, which can leave the last run with a lot
2748 // of extra space (resulting in very fat tabs on the last run).
2749 // So we'll attempt to distribute this extra space more evenly
2750 // across the runs in order to make the runs look more consistent.
2751 //
2752 // Starting with the last run, determine whether the last tab in
2753 // the previous run would fit (generously) in this run; if so,
2754 // move tab to current run and shift tabs accordingly. Cycle
2755 // through remaining runs using the same algorithm.
2756 //
2757 while (keepAdjusting) {
2758 int last = lastTabInRun(tabCount, run);
2759 int prevLast = lastTabInRun(tabCount, run-1);
2760 int end;
2761 int prevLastLen;
2762
2763 if (!verticalTabRuns) {
2764 end = rects[last].x + rects[last].width;
2765 prevLastLen = (int)(maxTabWidth*weight);
2766 } else {
2767 end = rects[last].y + rects[last].height;
2768 prevLastLen = (int)(maxTabHeight*weight*2);
2769 }
2770
2771 // Check if the run has enough extra space to fit the last tab
2772 // from the previous row...
2773 if (max - end > prevLastLen) {
2774
2775 // Insert tab from previous row and shift rest over
2776 tabRuns[run] = prevLast;
2777 if (!verticalTabRuns) {
2778 rects[prevLast].x = start;
2779 } else {
2780 rects[prevLast].y = start;
2781 }
2782 for (int i = prevLast+1; i <= last; i++) {
2783 if (!verticalTabRuns) {
2784 rects[i].x = rects[i-1].x + rects[i-1].width;
2785 } else {
2786 rects[i].y = rects[i-1].y + rects[i-1].height;
2787 }
2788 }
2789
2790 } else if (run == runCount - 1) {
2791 // no more room left in last run, so we're done!
2792 keepAdjusting = false;
2793 }
2794 if (run - 1 > 0) {
2795 // check previous run next...
2796 run -= 1;
2797 } else {
2798 // check last run again...but require a higher ratio
2799 // of extraspace-to-tabsize because we don't want to
2800 // end up with too many tabs on the last run!
2801 run = runCount - 1;
2802 weight += .25;
2803 }
2804 }
2805 }
2806
2807 protected void padTabRun(int tabPlacement, int start, int end, int max) {
2808 Rectangle lastRect = rects[end];
2809 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2810 int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
2811 int deltaWidth = max - (lastRect.x + lastRect.width);
2812 float factor = (float)deltaWidth / (float)runWidth;
2813
2814 for (int j = start; j <= end; j++) {
2815 Rectangle pastRect = rects[j];
2816 if (j > start) {
2817 pastRect.x = rects[j-1].x + rects[j-1].width;
2818 }
2819 pastRect.width += Math.round((float)pastRect.width * factor);
2820 }
2821 lastRect.width = max - lastRect.x;
2822 } else {
2823 int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
2824 int deltaHeight = max - (lastRect.y + lastRect.height);
2825 float factor = (float)deltaHeight / (float)runHeight;
2826
2827 for (int j = start; j <= end; j++) {
2828 Rectangle pastRect = rects[j];
2829 if (j > start) {
2830 pastRect.y = rects[j-1].y + rects[j-1].height;
2831 }
2832 pastRect.height += Math.round((float)pastRect.height * factor);
2833 }
2834 lastRect.height = max - lastRect.y;
2835 }
2836 }
2837
2838 protected void padSelectedTab(int tabPlacement, int selectedIndex) {
2839
2840 if (selectedIndex >= 0) {
2841 Rectangle selRect = rects[selectedIndex];
2842 Insets padInsets = getSelectedTabPadInsets(tabPlacement);
2843 selRect.x -= padInsets.left;
2844 selRect.width += (padInsets.left + padInsets.right);
2845 selRect.y -= padInsets.top;
2846 selRect.height += (padInsets.top + padInsets.bottom);
2847
2848 if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
2849 // do not expand selected tab more then necessary
2850 Dimension size = tabPane.getSize();
2851 Insets insets = tabPane.getInsets();
2852
2853 if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
2854 int top = insets.top - selRect.y;
2855 if (top > 0) {
2856 selRect.y += top;
2857 selRect.height -= top;
2858 }
2859 int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
2860 if (bottom > 0) {
2861 selRect.height -= bottom;
2862 }
2863 } else {
2864 int left = insets.left - selRect.x;
2865 if (left > 0) {
2866 selRect.x += left;
2867 selRect.width -= left;
2868 }
2869 int right = (selRect.x + selRect.width) + insets.right - size.width;
2870 if (right > 0) {
2871 selRect.width -= right;
2872 }
2873 }
2874 }
2875 }
2876 }
2877 }
2878
2879 private class TabbedPaneScrollLayout extends TabbedPaneLayout {
2880
2881 protected int preferredTabAreaHeight(int tabPlacement, int width) {
2882 return calculateMaxTabHeight(tabPlacement);
2883 }
2884
2885 protected int preferredTabAreaWidth(int tabPlacement, int height) {
2886 return calculateMaxTabWidth(tabPlacement);
2887 }
2888
2889 public void layoutContainer(Container parent) {
2890 /* Some of the code in this method deals with changing the
2891 * visibility of components to hide and show the contents for the
2892 * selected tab. This is older code that has since been duplicated
2893 * in JTabbedPane.fireStateChanged(), so as to allow visibility
2894 * changes to happen sooner (see the note there). This code remains
2895 * for backward compatibility as there are some cases, such as
2896 * subclasses that don't fireStateChanged() where it may be used.
2897 * Any changes here need to be kept in synch with
2898 * JTabbedPane.fireStateChanged().
2899 */
2900
2901 setRolloverTab(-1);
2902
2903 int tabPlacement = tabPane.getTabPlacement();
2904 int tabCount = tabPane.getTabCount();
2905 Insets insets = tabPane.getInsets();
2906 int selectedIndex = tabPane.getSelectedIndex();
2907 Component visibleComponent = getVisibleComponent();
2908
2909 calculateLayoutInfo();
2910
2911 Component selectedComponent = null;
2912 if (selectedIndex < 0) {
2913 if (visibleComponent != null) {
2914 // The last tab was removed, so remove the component
2915 setVisibleComponent(null);
2916 }
2917 } else {
2918 selectedComponent = tabPane.getComponentAt(selectedIndex);
2919 }
2920
2921 if (tabPane.getTabCount() == 0) {
2922 tabScroller.croppedEdge.resetParams();
2923 tabScroller.scrollForwardButton.setVisible(false);
2924 tabScroller.scrollBackwardButton.setVisible(false);
2925 return;
2926 }
2927
2928 boolean shouldChangeFocus = false;
2929
2930 // In order to allow programs to use a single component
2931 // as the display for multiple tabs, we will not change
2932 // the visible compnent if the currently selected tab
2933 // has a null component. This is a bit dicey, as we don't
2934 // explicitly state we support this in the spec, but since
2935 // programs are now depending on this, we're making it work.
2936 //
2937 if(selectedComponent != null) {
2938 if(selectedComponent != visibleComponent &&
2939 visibleComponent != null) {
2940 if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
2941 shouldChangeFocus = true;
2942 }
2943 }
2944 setVisibleComponent(selectedComponent);
2945 }
2946 int tx, ty, tw, th; // tab area bounds
2947 int cx, cy, cw, ch; // content area bounds
2948 Insets contentInsets = getContentBorderInsets(tabPlacement);
2949 Rectangle bounds = tabPane.getBounds();
2950 int numChildren = tabPane.getComponentCount();
2951
2952 if(numChildren > 0) {
2953 switch(tabPlacement) {
2954 case LEFT:
2955 // calculate tab area bounds
2956 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2957 th = bounds.height - insets.top - insets.bottom;
2958 tx = insets.left;
2959 ty = insets.top;
2960
2961 // calculate content area bounds
2962 cx = tx + tw + contentInsets.left;
2963 cy = ty + contentInsets.top;
2964 cw = bounds.width - insets.left - insets.right - tw -
2965 contentInsets.left - contentInsets.right;
2966 ch = bounds.height - insets.top - insets.bottom -
2967 contentInsets.top - contentInsets.bottom;
2968 break;
2969 case RIGHT:
2970 // calculate tab area bounds
2971 tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
2972 th = bounds.height - insets.top - insets.bottom;
2973 tx = bounds.width - insets.right - tw;
2974 ty = insets.top;
2975
2976 // calculate content area bounds
2977 cx = insets.left + contentInsets.left;
2978 cy = insets.top + contentInsets.top;
2979 cw = bounds.width - insets.left - insets.right - tw -
2980 contentInsets.left - contentInsets.right;
2981 ch = bounds.height - insets.top - insets.bottom -
2982 contentInsets.top - contentInsets.bottom;
2983 break;
2984 case BOTTOM:
2985 // calculate tab area bounds
2986 tw = bounds.width - insets.left - insets.right;
2987 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
2988 tx = insets.left;
2989 ty = bounds.height - insets.bottom - th;
2990
2991 // calculate content area bounds
2992 cx = insets.left + contentInsets.left;
2993 cy = insets.top + contentInsets.top;
2994 cw = bounds.width - insets.left - insets.right -
2995 contentInsets.left - contentInsets.right;
2996 ch = bounds.height - insets.top - insets.bottom - th -
2997 contentInsets.top - contentInsets.bottom;
2998 break;
2999 case TOP:
3000 default:
3001 // calculate tab area bounds
3002 tw = bounds.width - insets.left - insets.right;
3003 th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
3004 tx = insets.left;
3005 ty = insets.top;
3006
3007 // calculate content area bounds
3008 cx = tx + contentInsets.left;
3009 cy = ty + th + contentInsets.top;
3010 cw = bounds.width - insets.left - insets.right -
3011 contentInsets.left - contentInsets.right;
3012 ch = bounds.height - insets.top - insets.bottom - th -
3013 contentInsets.top - contentInsets.bottom;
3014 }
3015
3016 for(int i = 0; i < numChildren; i++) {
3017 Component child = tabPane.getComponent(i);
3018
3019 if(tabScroller != null && child == tabScroller.viewport) {
3020 JViewport viewport = (JViewport) child;
3021 Rectangle viewRect = viewport.getViewRect();
3022 int vw = tw;
3023 int vh = th;
3024 Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
3025 switch(tabPlacement) {
3026 case LEFT:
3027 case RIGHT:
3028 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3029 if(totalTabHeight > th) {
3030 // Allow space for scrollbuttons
3031 vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
3032 if(totalTabHeight - viewRect.y <= vh) {
3033 // Scrolled to the end, so ensure the viewport size is
3034 // such that the scroll offset aligns with a tab
3035 vh = totalTabHeight - viewRect.y;
3036 }
3037 }
3038 break;
3039 case BOTTOM:
3040 case TOP:
3041 default:
3042 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3043 if(totalTabWidth > tw) {
3044 // Need to allow space for scrollbuttons
3045 vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
3046 if(totalTabWidth - viewRect.x <= vw) {
3047 // Scrolled to the end, so ensure the viewport size is
3048 // such that the scroll offset aligns with a tab
3049 vw = totalTabWidth - viewRect.x;
3050 }
3051 }
3052 }
3053 child.setBounds(tx, ty, vw, vh);
3054
3055 } else if(tabScroller != null &&
3056 (child == tabScroller.scrollForwardButton ||
3057 child == tabScroller.scrollBackwardButton)) {
3058 Component scrollbutton = child;
3059 Dimension bsize = scrollbutton.getPreferredSize();
3060 int bx = 0;
3061 int by = 0;
3062 int bw = bsize.width;
3063 int bh = bsize.height;
3064 boolean visible = false;
3065
3066 switch(tabPlacement) {
3067 case LEFT:
3068 case RIGHT:
3069 int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
3070 if(totalTabHeight > th) {
3071 visible = true;
3072 bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
3073 by = (child == tabScroller.scrollForwardButton) ?
3074 bounds.height - insets.bottom - bsize.height :
3075 bounds.height - insets.bottom - 2 * bsize.height;
3076 }
3077 break;
3078
3079 case BOTTOM:
3080 case TOP:
3081 default:
3082 int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
3083
3084 if(totalTabWidth > tw) {
3085 visible = true;
3086 bx = (child == tabScroller.scrollForwardButton) ?
3087 bounds.width - insets.left - bsize.width :
3088 bounds.width - insets.left - 2 * bsize.width;
3089 by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
3090 }
3091 }
3092 child.setVisible(visible);
3093 if(visible) {
3094 child.setBounds(bx, by, bw, bh);
3095 }
3096
3097 } else {
3098 // All content children...
3099 child.setBounds(cx, cy, cw, ch);
3100 }
3101 }
3102 super.layoutTabComponents();
3103 layoutCroppedEdge();
3104 if(shouldChangeFocus) {
3105 if(!requestFocusForVisibleComponent()) {
3106 tabPane.requestFocus();
3107 }
3108 }
3109 }
3110 }
3111
3112 private void layoutCroppedEdge() {
3113 tabScroller.croppedEdge.resetParams();
3114 Rectangle viewRect = tabScroller.viewport.getViewRect();
3115 int cropline;
3116 for (int i = 0; i < rects.length; i++) {
3117 Rectangle tabRect = rects[i];
3118 switch (tabPane.getTabPlacement()) {
3119 case LEFT:
3120 case RIGHT:
3121 cropline = viewRect.y + viewRect.height;
3122 if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
3123 tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1,
3124 -currentTabAreaInsets.left, 0);
3125 }
3126 break;
3127 case TOP:
3128 case BOTTOM:
3129 default:
3130 cropline = viewRect.x + viewRect.width;
3131 if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
3132 tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1,
3133 0, -currentTabAreaInsets.top);
3134 }
3135 }
3136 }
3137 }
3138
3139 protected void calculateTabRects(int tabPlacement, int tabCount) {
3140 FontMetrics metrics = getFontMetrics();
3141 Dimension size = tabPane.getSize();
3142 Insets insets = tabPane.getInsets();
3143 Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
3144 int fontHeight = metrics.getHeight();
3145 int selectedIndex = tabPane.getSelectedIndex();
3146 int i, j;
3147 boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
3148 boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
3149 int x = tabAreaInsets.left;
3150 int y = tabAreaInsets.top;
3151 int totalWidth = 0;
3152 int totalHeight = 0;
3153
3154 //
3155 // Calculate bounds within which a tab run must fit
3156 //
3157 switch(tabPlacement) {
3158 case LEFT:
3159 case RIGHT:
3160 maxTabWidth = calculateMaxTabWidth(tabPlacement);
3161 break;
3162 case BOTTOM:
3163 case TOP:
3164 default:
3165 maxTabHeight = calculateMaxTabHeight(tabPlacement);
3166 }
3167
3168 runCount = 0;
3169 selectedRun = -1;
3170
3171 if (tabCount == 0) {
3172 return;
3173 }
3174
3175 selectedRun = 0;
3176 runCount = 1;
3177
3178 // Run through tabs and lay them out in a single run
3179 Rectangle rect;
3180 for (i = 0; i < tabCount; i++) {
3181 rect = rects[i];
3182
3183 if (!verticalTabRuns) {
3184 // Tabs on TOP or BOTTOM....
3185 if (i > 0) {
3186 rect.x = rects[i-1].x + rects[i-1].width;
3187 } else {
3188 tabRuns[0] = 0;
3189 maxTabWidth = 0;
3190 totalHeight += maxTabHeight;
3191 rect.x = x;
3192 }
3193 rect.width = calculateTabWidth(tabPlacement, i, metrics);
3194 totalWidth = rect.x + rect.width;
3195 maxTabWidth = Math.max(maxTabWidth, rect.width);
3196
3197 rect.y = y;
3198 rect.height = maxTabHeight/* - 2*/;
3199
3200 } else {
3201 // Tabs on LEFT or RIGHT...
3202 if (i > 0) {
3203 rect.y = rects[i-1].y + rects[i-1].height;
3204 } else {
3205 tabRuns[0] = 0;
3206 maxTabHeight = 0;
3207 totalWidth = maxTabWidth;
3208 rect.y = y;
3209 }
3210 rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
3211 totalHeight = rect.y + rect.height;
3212 maxTabHeight = Math.max(maxTabHeight, rect.height);
3213
3214 rect.x = x;
3215 rect.width = maxTabWidth/* - 2*/;
3216
3217 }
3218 }
3219
3220 if (tabsOverlapBorder) {
3221 // Pad the selected tab so that it appears raised in front
3222 padSelectedTab(tabPlacement, selectedIndex);
3223 }
3224
3225 // if right to left and tab placement on the top or
3226 // the bottom, flip x positions and adjust by widths
3227 if (!leftToRight && !verticalTabRuns) {
3228 int rightMargin = size.width
3229 - (insets.right + tabAreaInsets.right);
3230 for (i = 0; i < tabCount; i++) {
3231 rects[i].x = rightMargin - rects[i].x - rects[i].width;
3232 }
3233 }
3234 tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
3235 }
3236 }
3237
3238 private class ScrollableTabSupport implements ActionListener,
3239 ChangeListener {
3240 public ScrollableTabViewport viewport;
3241 public ScrollableTabPanel tabPanel;
3242 public JButton scrollForwardButton;
3243 public JButton scrollBackwardButton;
3244 public CroppedEdge croppedEdge;
3245 public int leadingTabIndex;
3246
3247 private Point tabViewPosition = new Point(0,0);
3248
3249 ScrollableTabSupport(int tabPlacement) {
3250 viewport = new ScrollableTabViewport();
3251 tabPanel = new ScrollableTabPanel();
3252 viewport.setView(tabPanel);
3253 viewport.addChangeListener(this);
3254 croppedEdge = new CroppedEdge();
3255 createButtons();
3256 }
3257
3258 /**
3259 * Recreates the scroll buttons and adds them to the TabbedPane.
3260 */
3261 void createButtons() {
3262 if (scrollForwardButton != null) {
3263 tabPane.remove(scrollForwardButton);
3264 scrollForwardButton.removeActionListener(this);
3265 tabPane.remove(scrollBackwardButton);
3266 scrollBackwardButton.removeActionListener(this);
3267 }
3268 int tabPlacement = tabPane.getTabPlacement();
3269 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3270 scrollForwardButton = createScrollButton(EAST);
3271 scrollBackwardButton = createScrollButton(WEST);
3272
3273 } else { // tabPlacement = LEFT || RIGHT
3274 scrollForwardButton = createScrollButton(SOUTH);
3275 scrollBackwardButton = createScrollButton(NORTH);
3276 }
3277 scrollForwardButton.addActionListener(this);
3278 scrollBackwardButton.addActionListener(this);
3279 tabPane.add(scrollForwardButton);
3280 tabPane.add(scrollBackwardButton);
3281 }
3282
3283 public void scrollForward(int tabPlacement) {
3284 Dimension viewSize = viewport.getViewSize();
3285 Rectangle viewRect = viewport.getViewRect();
3286
3287 if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3288 if (viewRect.width >= viewSize.width - viewRect.x) {
3289 return; // no room left to scroll
3290 }
3291 } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3292 if (viewRect.height >= viewSize.height - viewRect.y) {
3293 return;
3294 }
3295 }
3296 setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
3297 }
3298
3299 public void scrollBackward(int tabPlacement) {
3300 if (leadingTabIndex == 0) {
3301 return; // no room left to scroll
3302 }
3303 setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
3304 }
3305
3306 public void setLeadingTabIndex(int tabPlacement, int index) {
3307 leadingTabIndex = index;
3308 Dimension viewSize = viewport.getViewSize();
3309 Rectangle viewRect = viewport.getViewRect();
3310
3311 switch(tabPlacement) {
3312 case TOP:
3313 case BOTTOM:
3314 tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
3315
3316 if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3317 // We've scrolled to the end, so adjust the viewport size
3318 // to ensure the view position remains aligned on a tab boundary
3319 Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
3320 viewRect.height);
3321 viewport.setExtentSize(extentSize);
3322 }
3323 break;
3324 case LEFT:
3325 case RIGHT:
3326 tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
3327
3328 if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3329 // We've scrolled to the end, so adjust the viewport size
3330 // to ensure the view position remains aligned on a tab boundary
3331 Dimension extentSize = new Dimension(viewRect.width,
3332 viewSize.height - tabViewPosition.y);
3333 viewport.setExtentSize(extentSize);
3334 }
3335 }
3336 viewport.setViewPosition(tabViewPosition);
3337 }
3338
3339 public void stateChanged(ChangeEvent e) {
3340 updateView();
3341 }
3342
3343 private void updateView() {
3344 int tabPlacement = tabPane.getTabPlacement();
3345 int tabCount = tabPane.getTabCount();
3346 Rectangle vpRect = viewport.getBounds();
3347 Dimension viewSize = viewport.getViewSize();
3348 Rectangle viewRect = viewport.getViewRect();
3349
3350 leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3351
3352 // If the tab isn't right aligned, adjust it.
3353 if (leadingTabIndex + 1 < tabCount) {
3354 switch (tabPlacement) {
3355 case TOP:
3356 case BOTTOM:
3357 if (rects[leadingTabIndex].x < viewRect.x) {
3358 leadingTabIndex++;
3359 }
3360 break;
3361 case LEFT:
3362 case RIGHT:
3363 if (rects[leadingTabIndex].y < viewRect.y) {
3364 leadingTabIndex++;
3365 }
3366 break;
3367 }
3368 }
3369 Insets contentInsets = getContentBorderInsets(tabPlacement);
3370 switch(tabPlacement) {
3371 case LEFT:
3372 tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
3373 contentInsets.left, vpRect.height);
3374 scrollBackwardButton.setEnabled(
3375 viewRect.y > 0 && leadingTabIndex > 0);
3376 scrollForwardButton.setEnabled(
3377 leadingTabIndex < tabCount-1 &&
3378 viewSize.height-viewRect.y > viewRect.height);
3379 break;
3380 case RIGHT:
3381 tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
3382 contentInsets.right, vpRect.height);
3383 scrollBackwardButton.setEnabled(
3384 viewRect.y > 0 && leadingTabIndex > 0);
3385 scrollForwardButton.setEnabled(
3386 leadingTabIndex < tabCount-1 &&
3387 viewSize.height-viewRect.y > viewRect.height);
3388 break;
3389 case BOTTOM:
3390 tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
3391 vpRect.width, contentInsets.bottom);
3392 scrollBackwardButton.setEnabled(
3393 viewRect.x > 0 && leadingTabIndex > 0);
3394 scrollForwardButton.setEnabled(
3395 leadingTabIndex < tabCount-1 &&
3396 viewSize.width-viewRect.x > viewRect.width);
3397 break;
3398 case TOP:
3399 default:
3400 tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
3401 vpRect.width, contentInsets.top);
3402 scrollBackwardButton.setEnabled(
3403 viewRect.x > 0 && leadingTabIndex > 0);
3404 scrollForwardButton.setEnabled(
3405 leadingTabIndex < tabCount-1 &&
3406 viewSize.width-viewRect.x > viewRect.width);
3407 }
3408 }
3409
3410 /**
3411 * ActionListener for the scroll buttons.
3412 */
3413 public void actionPerformed(ActionEvent e) {
3414 ActionMap map = tabPane.getActionMap();
3415
3416 if (map != null) {
3417 String actionKey;
3418
3419 if (e.getSource() == scrollForwardButton) {
3420 actionKey = "scrollTabsForwardAction";
3421 }
3422 else {
3423 actionKey = "scrollTabsBackwardAction";
3424 }
3425 Action action = map.get(actionKey);
3426
3427 if (action != null && action.isEnabled()) {
3428 action.actionPerformed(new ActionEvent(tabPane,
3429 ActionEvent.ACTION_PERFORMED, null, e.getWhen(),
3430 e.getModifiers()));
3431 }
3432 }
3433 }
3434
3435 public String toString() {
3436 return new String("viewport.viewSize="+viewport.getViewSize()+"\n"+
3437 "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
3438 "leadingTabIndex="+leadingTabIndex+"\n"+
3439 "tabViewPosition="+tabViewPosition);
3440 }
3441
3442 }
3443
3444 private class ScrollableTabViewport extends JViewport implements UIResource {
3445 public ScrollableTabViewport() {
3446 super();
3447 setName("TabbedPane.scrollableViewport");
3448 setScrollMode(SIMPLE_SCROLL_MODE);
3449 setOpaque(tabPane.isOpaque());
3450 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3451 if (bgColor == null) {
3452 bgColor = tabPane.getBackground();
3453 }
3454 setBackground(bgColor);
3455 }
3456 }
3457
3458 private class ScrollableTabPanel extends JPanel implements UIResource {
3459 public ScrollableTabPanel() {
3460 super(null);
3461 setOpaque(tabPane.isOpaque());
3462 Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
3463 if (bgColor == null) {
3464 bgColor = tabPane.getBackground();
3465 }
3466 setBackground(bgColor);
3467 }
3468 public void paintComponent(Graphics g) {
3469 super.paintComponent(g);
3470 BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
3471 tabPane.getSelectedIndex());
3472 if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
3473 Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
3474 g.translate(croppedRect.x, croppedRect.y);
3475 tabScroller.croppedEdge.paintComponent(g);
3476 g.translate(-croppedRect.x, -croppedRect.y);
3477 }
3478 }
3479
3480 public void doLayout() {
3481 if (getComponentCount() > 0) {
3482 Component child = getComponent(0);
3483 child.setBounds(0, 0, getWidth(), getHeight());
3484 }
3485 }
3486 }
3487
3488 private class ScrollableTabButton extends BasicArrowButton implements UIResource,
3489 SwingConstants {
3490 public ScrollableTabButton(int direction) {
3491 super(direction,
3492 UIManager.getColor("TabbedPane.selected"),
3493 UIManager.getColor("TabbedPane.shadow"),
3494 UIManager.getColor("TabbedPane.darkShadow"),
3495 UIManager.getColor("TabbedPane.highlight"));
3496 }
3497 }
3498
3499
3500// Controller: event listeners
3501
3502 private class Handler implements ChangeListener, ContainerListener,
3503 FocusListener, MouseListener, MouseMotionListener,
3504 PropertyChangeListener {
3505 //
3506 // PropertyChangeListener
3507 //
3508 public void propertyChange(PropertyChangeEvent e) {
3509 JTabbedPane pane = (JTabbedPane)e.getSource();
3510 String name = e.getPropertyName();
3511 boolean isScrollLayout = scrollableTabLayoutEnabled();
3512 if (name == "mnemonicAt") {
3513 updateMnemonics();
3514 pane.repaint();
3515 }
3516 else if (name == "displayedMnemonicIndexAt") {
3517 pane.repaint();
3518 }
3519 else if (name =="indexForTitle") {
3520 calculatedBaseline = false;
3521 updateHtmlViews((Integer)e.getNewValue());
3522 } else if (name == "tabLayoutPolicy") {
3523 BasicTabbedPaneUI.this.uninstallUI(pane);
3524 BasicTabbedPaneUI.this.installUI(pane);
3525 calculatedBaseline = false;
3526 } else if (name == "tabPlacement") {
3527 if (scrollableTabLayoutEnabled()) {
3528 tabScroller.createButtons();
3529 }
3530 calculatedBaseline = false;
3531 } else if (name == "opaque" && isScrollLayout) {
3532 boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
3533 tabScroller.tabPanel.setOpaque(newVal);
3534 tabScroller.viewport.setOpaque(newVal);
3535 } else if (name == "background" && isScrollLayout) {
3536 Color newVal = (Color)e.getNewValue();
3537 tabScroller.tabPanel.setBackground(newVal);
3538 tabScroller.viewport.setBackground(newVal);
3539 Color newColor = selectedColor == null ? newVal : selectedColor;
3540 tabScroller.scrollForwardButton.setBackground(newColor);
3541 tabScroller.scrollBackwardButton.setBackground(newColor);
3542 } else if (name == "indexForTabComponent") {
3543 if (tabContainer != null) {
3544 tabContainer.removeUnusedTabComponents();
3545 }
3546 Component c = tabPane.getTabComponentAt(
3547 (Integer)e.getNewValue());
3548 if (c != null) {
3549 if (tabContainer == null) {
3550 installTabContainer();
3551 } else {
3552 tabContainer.add(c);
3553 }
3554 }
3555 tabPane.revalidate();
3556 tabPane.repaint();
3557 calculatedBaseline = false;
3558 } else if (name == "indexForNullComponent") {
3559 isRunsDirty = true;
3560 updateHtmlViews((Integer)e.getNewValue());
3561 } else if (name == "font") {
3562 calculatedBaseline = false;
3563 }
3564 }
3565
3566 private void updateHtmlViews(int index) {
3567 String title = tabPane.getTitleAt(index);
3568 boolean isHTML = BasicHTML.isHTMLString(title);
3569 if (isHTML) {
3570 if (htmlViews==null) { // Initialize vector
3571 htmlViews = createHTMLVector();
3572 } else { // Vector already exists
3573 View v = BasicHTML.createHTMLView(tabPane, title);
3574 htmlViews.insertElementAt(v, index);
3575 }
3576 } else { // Not HTML
3577 if (htmlViews!=null) { // Add placeholder
3578 htmlViews.insertElementAt(null, index);
3579 } // else nada!
3580 }
3581 updateMnemonics();
3582 }
3583
3584 //
3585 // ChangeListener
3586 //
3587 public void stateChanged(ChangeEvent e) {
3588 JTabbedPane tabPane = (JTabbedPane)e.getSource();
3589 tabPane.revalidate();
3590 tabPane.repaint();
3591
3592 setFocusIndex(tabPane.getSelectedIndex(), false);
3593
3594 if (scrollableTabLayoutEnabled()) {
3595 int index = tabPane.getSelectedIndex();
3596 if (index < rects.length && index != -1) {
3597 tabScroller.tabPanel.scrollRectToVisible(
3598 (Rectangle)rects[index].clone());
3599 }
3600 }
3601 }
3602
3603 //
3604 // MouseListener
3605 //
3606 public void mouseClicked(MouseEvent e) {
3607 }
3608
3609 public void mouseReleased(MouseEvent e) {
3610 }
3611
3612 public void mouseEntered(MouseEvent e) {
3613 setRolloverTab(e.getX(), e.getY());
3614 }
3615
3616 public void mouseExited(MouseEvent e) {
3617 setRolloverTab(-1);
3618 }
3619
3620 public void mousePressed(MouseEvent e) {
3621 if (!tabPane.isEnabled()) {
3622 return;
3623 }
3624 int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
3625 if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
3626 if (tabIndex != tabPane.getSelectedIndex()) {
3627 // Clicking on unselected tab, change selection, do NOT
3628 // request focus.
3629 // This will trigger the focusIndex to change by way
3630 // of stateChanged.
3631 tabPane.setSelectedIndex(tabIndex);
3632 }
3633 else if (tabPane.isRequestFocusEnabled()) {
3634 // Clicking on selected tab, try and give the tabbedpane
3635 // focus. Repaint will occur in focusGained.
3636 tabPane.requestFocus();
3637 }
3638 }
3639 }
3640
3641 //
3642 // MouseMotionListener
3643 //
3644 public void mouseDragged(MouseEvent e) {
3645 }
3646
3647 public void mouseMoved(MouseEvent e) {
3648 setRolloverTab(e.getX(), e.getY());
3649 }
3650
3651 //
3652 // FocusListener
3653 //
3654 public void focusGained(FocusEvent e) {
3655 setFocusIndex(tabPane.getSelectedIndex(), true);
3656 }
3657 public void focusLost(FocusEvent e) {
3658 repaintTab(focusIndex);
3659 }
3660
3661
3662 //
3663 // ContainerListener
3664 //
3665 /* GES 2/3/99:
3666 The container listener code was added to support HTML
3667 rendering of tab titles.
3668
3669 Ideally, we would be able to listen for property changes
3670 when a tab is added or its text modified. At the moment
3671 there are no such events because the Beans spec doesn't
3672 allow 'indexed' property changes (i.e. tab 2's text changed
3673 from A to B).
3674
3675 In order to get around this, we listen for tabs to be added
3676 or removed by listening for the container events. we then
3677 queue up a runnable (so the component has a chance to complete
3678 the add) which checks the tab title of the new component to see
3679 if it requires HTML rendering.
3680
3681 The Views (one per tab title requiring HTML rendering) are
3682 stored in the htmlViews Vector, which is only allocated after
3683 the first time we run into an HTML tab. Note that this vector
3684 is kept in step with the number of pages, and nulls are added
3685 for those pages whose tab title do not require HTML rendering.
3686
3687 This makes it easy for the paint and layout code to tell
3688 whether to invoke the HTML engine without having to check
3689 the string during time-sensitive operations.
3690
3691 When we have added a way to listen for tab additions and
3692 changes to tab text, this code should be removed and
3693 replaced by something which uses that. */
3694
3695 public void componentAdded(ContainerEvent e) {
3696 JTabbedPane tp = (JTabbedPane)e.getContainer();
3697 Component child = e.getChild();
3698 if (child instanceof UIResource) {
3699 return;
3700 }
3701 isRunsDirty = true;
3702 updateHtmlViews(tp.indexOfComponent(child));
3703 }
3704 public void componentRemoved(ContainerEvent e) {
3705 JTabbedPane tp = (JTabbedPane)e.getContainer();
3706 Component child = e.getChild();
3707 if (child instanceof UIResource) {
3708 return;
3709 }
3710
3711 // NOTE 4/15/2002 (joutwate):
3712 // This fix is implemented using client properties since there is
3713 // currently no IndexPropertyChangeEvent. Once
3714 // IndexPropertyChangeEvents have been added this code should be
3715 // modified to use it.
3716 Integer indexObj =
3717 (Integer)tp.getClientProperty("__index_to_remove__");
3718 if (indexObj != null) {
3719 int index = indexObj.intValue();
3720 if (htmlViews != null && htmlViews.size() > index) {
3721 htmlViews.removeElementAt(index);
3722 }
3723 tp.putClientProperty("__index_to_remove__", null);
3724 }
3725 isRunsDirty = true;
3726 updateMnemonics();
3727
3728 validateFocusIndex();
3729 }
3730 }
3731
3732 /**
3733 * This class should be treated as a &quot;protected&quot; inner class.
3734 * Instantiate it only within subclasses of BasicTabbedPaneUI.
3735 */
3736 public class PropertyChangeHandler implements PropertyChangeListener {
3737 // NOTE: This class exists only for backward compatability. All
3738 // its functionality has been moved into Handler. If you need to add
3739 // new functionality add it to the Handler, but make sure this
3740 // class calls into the Handler.
3741 public void propertyChange(PropertyChangeEvent e) {
3742 getHandler().propertyChange(e);
3743 }
3744 }
3745
3746 /**
3747 * This class should be treated as a &quot;protected&quot; inner class.
3748 * Instantiate it only within subclasses of BasicTabbedPaneUI.
3749 */
3750 public class TabSelectionHandler implements ChangeListener {
3751 // NOTE: This class exists only for backward compatability. All
3752 // its functionality has been moved into Handler. If you need to add
3753 // new functionality add it to the Handler, but make sure this
3754 // class calls into the Handler.
3755 public void stateChanged(ChangeEvent e) {
3756 getHandler().stateChanged(e);
3757 }
3758 }
3759
3760 /**
3761 * This class should be treated as a &quot;protected&quot; inner class.
3762 * Instantiate it only within subclasses of BasicTabbedPaneUI.
3763 */
3764 public class MouseHandler extends MouseAdapter {
3765 // NOTE: This class exists only for backward compatability. All
3766 // its functionality has been moved into Handler. If you need to add
3767 // new functionality add it to the Handler, but make sure this
3768 // class calls into the Handler.
3769 public void mousePressed(MouseEvent e) {
3770 getHandler().mousePressed(e);
3771 }
3772 }
3773
3774 /**
3775 * This class should be treated as a &quot;protected&quot; inner class.
3776 * Instantiate it only within subclasses of BasicTabbedPaneUI.
3777 */
3778 public class FocusHandler extends FocusAdapter {
3779 // NOTE: This class exists only for backward compatability. All
3780 // its functionality has been moved into Handler. If you need to add
3781 // new functionality add it to the Handler, but make sure this
3782 // class calls into the Handler.
3783 public void focusGained(FocusEvent e) {
3784 getHandler().focusGained(e);
3785 }
3786 public void focusLost(FocusEvent e) {
3787 getHandler().focusLost(e);
3788 }
3789 }
3790
3791 private Vector createHTMLVector() {
3792 Vector htmlViews = new Vector();
3793 int count = tabPane.getTabCount();
3794 if (count>0) {
3795 for (int i=0 ; i<count; i++) {
3796 String title = tabPane.getTitleAt(i);
3797 if (BasicHTML.isHTMLString(title)) {
3798 htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
3799 } else {
3800 htmlViews.addElement(null);
3801 }
3802 }
3803 }
3804 return htmlViews;
3805 }
3806
3807 private class TabContainer extends JPanel implements UIResource {
3808 private boolean notifyTabbedPane = true;
3809
3810 public TabContainer() {
3811 super(null);
3812 setOpaque(false);
3813 }
3814
3815 public void remove(Component comp) {
3816 int index = tabPane.indexOfTabComponent(comp);
3817 super.remove(comp);
3818 if (notifyTabbedPane && index != -1) {
3819 tabPane.setTabComponentAt(index, null);
3820 }
3821 }
3822
3823 private void removeUnusedTabComponents() {
3824 for (Component c : getComponents()) {
3825 if (!(c instanceof UIResource)) {
3826 int index = tabPane.indexOfTabComponent(c);
3827 if (index == -1) {
3828 super.remove(c);
3829 }
3830 }
3831 }
3832 }
3833
3834 public boolean isOptimizedDrawingEnabled() {
3835 return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
3836 }
3837
3838 public void doLayout() {
3839 // We layout tabComponents in JTabbedPane's layout manager
3840 // and use this method as a hook for repainting tabs
3841 // to update tabs area e.g. when the size of tabComponent was changed
3842 if (scrollableTabLayoutEnabled()) {
3843 tabScroller.tabPanel.repaint();
3844 tabScroller.updateView();
3845 } else {
3846 tabPane.repaint(getBounds());
3847 }
3848 }
3849 }
3850
3851 private class CroppedEdge extends JPanel implements UIResource {
3852 private Shape shape;
3853 private int tabIndex;
3854 private int cropline;
3855 private int cropx, cropy;
3856
3857 public CroppedEdge() {
3858 setOpaque(false);
3859 }
3860
3861 public void setParams(int tabIndex, int cropline, int cropx, int cropy) {
3862 this.tabIndex = tabIndex;
3863 this.cropline = cropline;
3864 this.cropx = cropx;
3865 this.cropy = cropy;
3866 Rectangle tabRect = rects[tabIndex];
3867 setBounds(tabRect);
3868 shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
3869 if (getParent() == null && tabContainer != null) {
3870 tabContainer.add(this, 0);
3871 }
3872 }
3873
3874 public void resetParams() {
3875 shape = null;
3876 if (getParent() == tabContainer && tabContainer != null) {
3877 tabContainer.remove(this);
3878 }
3879 }
3880
3881 public boolean isParamsSet() {
3882 return shape != null;
3883 }
3884
3885 public int getTabIndex() {
3886 return tabIndex;
3887 }
3888
3889 public int getCropline() {
3890 return cropline;
3891 }
3892
3893 public int getCroppedSideWidth() {
3894 return 3;
3895 }
3896
3897 private Color getBgColor() {
3898 Component parent = tabPane.getParent();
3899 if (parent != null) {
3900 Color bg = parent.getBackground();
3901 if (bg != null) {
3902 return bg;
3903 }
3904 }
3905 return UIManager.getColor("control");
3906 }
3907
3908 protected void paintComponent(Graphics g) {
3909 super.paintComponent(g);
3910 if (isParamsSet() && g instanceof Graphics2D) {
3911 Graphics2D g2 = (Graphics2D) g;
3912 g2.clipRect(0, 0, getWidth(), getHeight());
3913 g2.setColor(getBgColor());
3914 g2.translate(cropx, cropy);
3915 g2.fill(shape);
3916 paintCroppedTabEdge(g);
3917 g2.translate(-cropx, -cropy);
3918 }
3919 }
3920 }
3921}