2
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Substance Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.substance.internal.ui;
33
import java.awt.event.*;
34
import java.awt.geom.*;
35
import java.awt.image.BufferedImage;
36
import java.beans.PropertyChangeEvent;
37
import java.beans.PropertyChangeListener;
39
import java.util.List;
42
import javax.swing.event.ChangeEvent;
43
import javax.swing.event.ChangeListener;
44
import javax.swing.plaf.ComponentUI;
45
import javax.swing.plaf.UIResource;
46
import javax.swing.plaf.basic.BasicTabbedPaneUI;
47
import javax.swing.text.View;
49
import org.pushingpixels.lafwidget.LafWidgetUtilities;
50
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
51
import org.pushingpixels.lafwidget.animation.AnimationFacet;
52
import org.pushingpixels.lafwidget.utils.RenderingUtils;
53
import org.pushingpixels.substance.api.*;
54
import org.pushingpixels.substance.api.SubstanceConstants.*;
55
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
56
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
57
import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
58
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
59
import org.pushingpixels.substance.api.tabbed.*;
60
import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
61
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
62
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
63
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
64
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
65
import org.pushingpixels.substance.internal.utils.*;
66
import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
67
import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
68
import org.pushingpixels.trident.Timeline;
69
import org.pushingpixels.trident.Timeline.RepeatBehavior;
70
import org.pushingpixels.trident.Timeline.TimelineState;
71
import org.pushingpixels.trident.callback.TimelineCallback;
72
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
75
* UI for tabbed panes in <b>Substance</b> look and feel.
77
* @author Kirill Grouchnikov
79
public class SubstanceTabbedPaneUI extends BasicTabbedPaneUI {
81
* Current mouse location.
83
protected Point substanceMouseLocation;
86
* Hash map for storing already computed backgrounds.
88
private static LazyResettableHashMap<BufferedImage> backgroundMap = new LazyResettableHashMap<BufferedImage>(
89
"SubstanceTabbedPaneUI.background");
92
* Hash map for storing already computed backgrounds.
94
private static LazyResettableHashMap<BufferedImage> closeButtonMap = new LazyResettableHashMap<BufferedImage>(
95
"SubstanceTabbedPaneUI.closeButton");
98
* Key - tab component. Value - the looping timeline that animates the tab
99
* component when it's marked as modified (with
100
* {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property).
102
private Map<Component, Timeline> modifiedTimelines;
105
* Currently selected index (for selection animations).
107
private int currSelectedIndex;
109
private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker;
114
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
116
public static ComponentUI createUI(JComponent comp) {
117
SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
118
return new SubstanceTabbedPaneUI();
122
* Mouse handler for rollover effects.
124
protected MouseRolloverHandler substanceRolloverHandler;
127
* Tracks changes to the tabbed pane contents. Each tab component is tracked
128
* for changes on the {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property.
130
protected TabbedContainerListener substanceContainerListener;
133
* Listener for animation effects on tab selection.
135
protected ChangeListener substanceSelectionListener;
137
private boolean substanceContentOpaque;
140
* Tracks changes to the tabbed pane contents. Each tab component is tracked
141
* for changes on the {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property.
143
* @author Kirill Grouchnikov
145
protected final class TabbedContainerListener extends ContainerAdapter {
147
* Property change listeners on the tab components.
149
* Fixes defect 135 - memory leaks on tabbed panes.
151
private Map<Component, List<PropertyChangeListener>> listeners = new HashMap<Component, List<PropertyChangeListener>>();
154
* Creates a new container listener.
156
public TabbedContainerListener() {
160
* Tracks all existing tab component.
162
protected void trackExistingTabs() {
163
// register listeners on all existing tabs
164
for (int i = 0; i < SubstanceTabbedPaneUI.this.tabPane
165
.getTabCount(); i++) {
166
this.trackTab(SubstanceTabbedPaneUI.this.tabPane
172
* Tracks changes in a single tab component.
174
* @param tabComponent
177
protected void trackTab(final Component tabComponent) {
178
if (tabComponent == null)
181
PropertyChangeListener tabModifiedListener = new PropertyChangeListener() {
183
public void propertyChange(PropertyChangeEvent evt) {
184
if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
185
.getPropertyName())) {
186
Object oldValue = evt.getOldValue();
187
Object newValue = evt.getNewValue();
188
boolean wasModified = Boolean.TRUE.equals(oldValue);
189
boolean isModified = Boolean.TRUE.equals(newValue);
193
Timeline modifiedTimeline = modifiedTimelines
195
modifiedTimeline.cancel();
196
modifiedTimelines.remove(tabComponent);
200
int tabIndex = SubstanceTabbedPaneUI.this.tabPane
201
.indexOfComponent(tabComponent);
203
trackTabModification(tabIndex, tabComponent);
210
tabComponent.addPropertyChangeListener(tabModifiedListener);
211
// fix for defect 135 - memory leaks on tabbed panes
212
List<PropertyChangeListener> currList = this.listeners
214
if (currList == null)
215
currList = new LinkedList<PropertyChangeListener>();
216
currList.add(tabModifiedListener);
217
// System.err.println(this.hashCode() + " adding for " +
218
// tabComponent.hashCode());
219
this.listeners.put(tabComponent, currList);
220
// Fix for defect 104 - a 'modified' component is added to
221
// the tabbed pane. In this case it should be animated from the
223
if (tabComponent instanceof JComponent) {
225
.equals(((JComponent) tabComponent)
226
.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED))) {
227
int tabIndex = SubstanceTabbedPaneUI.this.tabPane
228
.indexOfComponent(tabComponent);
230
trackTabModification(tabIndex, tabComponent);
237
* Stops tracking changes to a single tab component.
239
* @param tabComponent
242
protected void stopTrackTab(final Component tabComponent) {
243
if (tabComponent == null)
246
List<PropertyChangeListener> pclList = this.listeners
248
if (pclList != null) {
249
for (PropertyChangeListener pcl : pclList)
250
tabComponent.removePropertyChangeListener(pcl);
253
this.listeners.put(tabComponent, null);
257
* Stops tracking all tab components.
259
protected void stopTrackExistingTabs() {
260
// register listeners on all existing tabs
261
for (int i = 0; i < SubstanceTabbedPaneUI.this.tabPane
262
.getTabCount(); i++) {
263
this.stopTrackTab(SubstanceTabbedPaneUI.this.tabPane
271
* @seejava.awt.event.ContainerAdapter#componentAdded(java.awt.event.
275
public void componentAdded(final ContainerEvent e) {
276
final Component tabComponent = e.getChild();
277
if (tabComponent instanceof UIResource)
279
this.trackTab(tabComponent);
285
* @seejava.awt.event.ContainerAdapter#componentRemoved(java.awt.event.
289
public void componentRemoved(ContainerEvent e) {
290
// fix for defect 135 - memory leaks on tabbed panes
291
final Component tabComponent = e.getChild();
292
if (tabComponent == null)
294
// System.err.println(this.hashCode() + " removing for " +
295
// tabComponent.hashCode());
296
if (tabComponent instanceof UIResource)
298
for (PropertyChangeListener pcl : this.listeners.get(tabComponent))
299
tabComponent.removePropertyChangeListener(pcl);
300
this.listeners.get(tabComponent).clear();
301
this.listeners.remove(tabComponent);
303
// has running timeline?
304
Timeline timeline = modifiedTimelines.get(tabComponent);
305
if (timeline != null) {
307
modifiedTimelines.remove(tabComponent);
310
// this.cleanListeners(tabComponent);
316
* Listener for rollover animation effects.
318
* @author Kirill Grouchnikov
320
protected class MouseRolloverHandler implements MouseListener,
321
MouseMotionListener {
323
* Index of the tab that was rolloed over on the previous mouse event.
325
int prevRolledOver = -1;
328
* Indicates whether the previous mouse event was located in a close
331
boolean prevInCloseButton = false;
334
* Tab index of the last mouse pressed event that happened in a close
337
int tabOfPressedCloseButton = -1;
343
* java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
346
public void mouseClicked(final MouseEvent e) {
347
final int tabIndex = SubstanceTabbedPaneUI.this.tabForCoordinate(
348
SubstanceTabbedPaneUI.this.tabPane, e.getX(), e.getY());
349
TabCloseCallback closeCallback = SubstanceCoreUtilities
350
.getTabCloseCallback(e, SubstanceTabbedPaneUI.this.tabPane,
352
if (closeCallback == null)
355
final TabCloseKind tabCloseKind = closeCallback.onAreaClick(
356
SubstanceTabbedPaneUI.this.tabPane, tabIndex, e);
357
if (tabCloseKind == TabCloseKind.NONE)
360
SwingUtilities.invokeLater(new Runnable() {
363
SubstanceTabbedPaneUI.this.tryCloseTabs(tabIndex,
373
* java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
377
public void mouseDragged(MouseEvent e) {
378
this.handleMouseMoveDrag(e);
385
* java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
388
public void mouseEntered(MouseEvent e) {
389
setRolloverTab(tabForCoordinate(tabPane, e.getX(), e.getY()));
396
* java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
399
public void mousePressed(MouseEvent e) {
400
if (!tabPane.isEnabled()) {
403
int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
404
if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
405
Rectangle rect = new Rectangle();
406
rect = getTabBounds(tabIndex, rect);
407
Rectangle close = getCloseButtonRectangleForEvents(tabIndex,
408
rect.x, rect.y, rect.width, rect.height);
409
boolean inCloseButton = close.contains(e.getPoint());
410
this.tabOfPressedCloseButton = inCloseButton ? tabIndex : -1;
411
if (tabIndex != tabPane.getSelectedIndex()) {
412
// enhancement 307 - don't select tab on pressing its
417
// Clicking on unselected tab, change selection, do NOT
419
// This will trigger the focusIndex to change by way
421
tabPane.setSelectedIndex(tabIndex);
422
} else if (tabPane.isRequestFocusEnabled()) {
423
// Clicking on selected tab, try and give the tabbedpane
424
// focus. Repaint will occur in focusGained.
425
tabPane.requestFocus();
434
* java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent
438
public void mouseMoved(MouseEvent e) {
439
this.handleMouseMoveDrag(e);
443
* Handles the move and drag mouse events.
446
* Mouse event to handle.
448
private void handleMouseMoveDrag(MouseEvent e) {
449
if (e.getSource() != tabPane)
452
setRolloverTab(tabForCoordinate(tabPane, e.getX(), e.getY()));
453
if (!AnimationConfigurationManager.getInstance()
454
.isAnimationAllowed(AnimationFacet.ROLLOVER, tabPane)) {
458
SubstanceTabbedPaneUI.this.substanceMouseLocation = e.getPoint();
459
int currRolledOver = SubstanceTabbedPaneUI.this.getRolloverTab();
460
TabCloseCallback tabCloseCallback = SubstanceCoreUtilities
461
.getTabCloseCallback(e, tabPane, currRolledOver);
462
// System.err.println("Mouse moved " + currRolledOver + ":" +
464
if (currRolledOver == this.prevRolledOver) {
465
if (currRolledOver >= 0) {
466
Rectangle rect = new Rectangle();
467
rect = getTabBounds(currRolledOver, rect);
468
Rectangle close = getCloseButtonRectangleForEvents(
469
currRolledOver, rect.x, rect.y, rect.width,
471
// System.out.println("move " + rect + " " + close + " "
473
boolean inCloseButton = close.contains(e.getPoint());
474
if (this.prevInCloseButton == inCloseButton)
476
this.prevInCloseButton = inCloseButton;
477
if (tabCloseCallback != null) {
479
String closeButtonTooltip = tabCloseCallback
480
.getCloseButtonTooltip(tabPane,
482
tabPane.setToolTipTextAt(currRolledOver,
485
String areaTooltip = tabCloseCallback
486
.getAreaTooltip(tabPane, currRolledOver);
487
tabPane.setToolTipTextAt(currRolledOver,
491
if ((currRolledOver >= 0)
492
&& (currRolledOver < tabPane.getTabCount())) {
493
StateTransitionTracker tracker = getTracker(
494
currRolledOver, true,
495
currRolledOver == currSelectedIndex);
496
tracker.getModel().setRollover(false);
497
tracker.endTransition();
501
if ((this.prevRolledOver >= 0)
502
&& (this.prevRolledOver < tabPane.getTabCount())
503
&& tabPane.isEnabledAt(this.prevRolledOver)) {
504
StateTransitionTracker tracker = getTracker(prevRolledOver,
505
true, prevRolledOver == currSelectedIndex);
506
tracker.getModel().setRollover(false);
508
if ((currRolledOver >= 0)
509
&& (currRolledOver < tabPane.getTabCount())
510
&& tabPane.isEnabledAt(currRolledOver)) {
511
StateTransitionTracker tracker = getTracker(currRolledOver,
512
false, currRolledOver == currSelectedIndex);
513
tracker.getModel().setRollover(true);
516
this.prevRolledOver = currRolledOver;
523
* java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
526
public void mouseExited(MouseEvent e) {
528
// fix for bug 69 - non-selected non-rollover tab
529
// may remain with close button after moving mouse quickly
530
// to inner JTabbedPane
531
if ((this.prevRolledOver >= 0)
532
&& (this.prevRolledOver < SubstanceTabbedPaneUI.this.tabPane
534
&& SubstanceTabbedPaneUI.this.tabPane
535
.isEnabledAt(this.prevRolledOver)) {
536
// only the previously rolled-over tab needs to be
537
// repainted (fade-out) instead of repainting the
538
// whole tab as before.
539
StateTransitionTracker tracker = getTracker(prevRolledOver,
540
true, prevRolledOver == currSelectedIndex);
541
tracker.getModel().setRollover(false);
543
if (SubstanceCoreUtilities.getTabCloseCallback(e, tabPane,
544
this.prevRolledOver) != null) {
545
tabPane.setToolTipTextAt(this.prevRolledOver, null);
548
this.prevRolledOver = -1;
555
* java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
558
public void mouseReleased(final MouseEvent e) {
559
// enhancement 307 - moving the tab close to be on mouse release
560
// and not on mouse press.
561
final int tabIndex = SubstanceTabbedPaneUI.this.tabForCoordinate(
562
SubstanceTabbedPaneUI.this.tabPane, e.getX(), e.getY());
563
// check that the mouse release is on the same tab as
564
// mouse press, and that the tab has close button
565
if (SubstanceCoreUtilities.hasCloseButton(
566
SubstanceTabbedPaneUI.this.tabPane, tabIndex)
567
&& (tabIndex == this.tabOfPressedCloseButton)) {
568
SwingUtilities.invokeLater(new Runnable() {
572
&& SubstanceTabbedPaneUI.this.tabPane
573
.isEnabledAt(tabIndex)) {
574
Rectangle rect = new Rectangle();
575
rect = SubstanceTabbedPaneUI.this.getTabBounds(
578
Rectangle close = SubstanceTabbedPaneUI.this
579
.getCloseButtonRectangleForEvents(tabIndex,
580
rect.x, rect.y, rect.width,
582
// System.out.println("press " + close + " "
584
if (close.contains(e.getPoint())) {
585
TabCloseCallback closeCallback = SubstanceCoreUtilities
586
.getTabCloseCallback(
588
SubstanceTabbedPaneUI.this.tabPane,
591
TabCloseKind tabCloseKind = (closeCallback == null) ? TabCloseKind.THIS
594
SubstanceTabbedPaneUI.this.tabPane,
597
SubstanceTabbedPaneUI.this.tryCloseTabs(
598
tabIndex, tabCloseKind);
603
this.tabOfPressedCloseButton = -1;
609
* Creates the new UI delegate.
611
public SubstanceTabbedPaneUI() {
613
this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>();
614
this.currSelectedIndex = -1;
620
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installListeners()
623
protected void installListeners() {
624
super.installListeners();
625
// Install listener to repaint the tabbed pane
626
// on mouse move (for rollover effects).
627
this.substanceRolloverHandler = new MouseRolloverHandler();
628
this.tabPane.addMouseMotionListener(this.substanceRolloverHandler);
629
this.tabPane.addMouseListener(this.substanceRolloverHandler);
631
// Add container listener to wire property change listener
632
// on each tab in the tabbed pane.
633
this.substanceContainerListener = new TabbedContainerListener();
634
this.substanceContainerListener.trackExistingTabs();
636
for (int i = 0; i < this.tabPane.getTabCount(); i++) {
637
Component tabComp = this.tabPane.getComponentAt(i);
638
if (SubstanceCoreUtilities.isTabModified(tabComp)) {
639
trackTabModification(i, tabComp);
643
this.tabPane.addContainerListener(this.substanceContainerListener);
645
this.substanceSelectionListener = new ChangeListener() {
647
public void stateChanged(ChangeEvent e) {
648
SwingUtilities.invokeLater(new Runnable() {
651
if (SubstanceTabbedPaneUI.this.tabPane == null)
653
int selected = SubstanceTabbedPaneUI.this.tabPane
656
// fix for issue 437 - track the selection change,
657
// fading out the previously selected tab
658
if ((currSelectedIndex >= 0)
659
&& (currSelectedIndex < SubstanceTabbedPaneUI.this.tabPane
661
&& SubstanceTabbedPaneUI.this.tabPane
662
.isEnabledAt(currSelectedIndex)) {
663
StateTransitionTracker tracker = getTracker(
665
getRolloverTabIndex() == currSelectedIndex,
667
tracker.getModel().setSelected(false);
669
currSelectedIndex = selected;
671
&& (selected < SubstanceTabbedPaneUI.this.tabPane
673
&& SubstanceTabbedPaneUI.this.tabPane
674
.isEnabledAt(selected)) {
675
StateTransitionTracker tracker = getTracker(
677
getRolloverTabIndex() == selected, false);
678
tracker.getModel().setSelected(true);
684
this.tabPane.getModel().addChangeListener(
685
this.substanceSelectionListener);
691
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallListeners()
694
protected void uninstallListeners() {
695
super.uninstallListeners();
696
if (this.substanceRolloverHandler != null) {
698
.removeMouseMotionListener(this.substanceRolloverHandler);
699
this.tabPane.removeMouseListener(this.substanceRolloverHandler);
700
this.substanceRolloverHandler = null;
702
if (this.substanceContainerListener != null) {
703
for (Map.Entry<Component, List<PropertyChangeListener>> entry : this.substanceContainerListener.listeners
705
Component comp = entry.getKey();
706
// System.out.println(this.containerListener.hashCode() +"
707
// removing all for" + comp.hashCode());
708
for (PropertyChangeListener pcl : entry.getValue()) {
709
comp.removePropertyChangeListener(pcl);
712
this.substanceContainerListener.listeners.clear();
715
.removeContainerListener(this.substanceContainerListener);
716
this.substanceContainerListener = null;
718
this.tabPane.getModel().removeChangeListener(
719
this.substanceSelectionListener);
720
this.substanceSelectionListener = null;
726
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installDefaults()
729
protected void installDefaults() {
730
super.installDefaults();
732
this.substanceContentOpaque = UIManager
733
.getBoolean("TabbedPane.contentOpaque");
735
this.modifiedTimelines = new HashMap<Component, Timeline>();
736
this.currSelectedIndex = this.tabPane.getSelectedIndex();
742
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallDefaults()
745
protected void uninstallDefaults() {
746
for (Timeline timeline : this.modifiedTimelines.values())
748
this.modifiedTimelines.clear();
749
super.uninstallDefaults();
753
* Retrieves tab background.
761
* @param tabPlacement
764
* Color scheme for coloring the background.
765
* @param borderScheme
766
* Color scheme for coloring the border.
767
* @param paintOnlyBorder
768
* If <code>true</code>, only the border will be painted.
769
* @return Tab background of specified parameters.
771
private static BufferedImage getTabBackground(JTabbedPane tabPane,
772
int width, int height, int tabPlacement,
773
SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
774
boolean paintOnlyBorder) {
775
SubstanceFillPainter fillPainter = SubstanceCoreUtilities
776
.getFillPainter(tabPane);
777
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
778
.getBorderPainter(tabPane);
779
SubstanceButtonShaper shaper = SubstanceCoreUtilities
780
.getButtonShaper(tabPane);
782
int borderDelta = (int) Math.ceil(2.0 * SubstanceSizeUtils
783
.getBorderStrokeWidth(SubstanceSizeUtils
784
.getComponentFontSize(tabPane)));
785
int borderInsets = (int) Math.floor(SubstanceSizeUtils
786
.getBorderStrokeWidth(SubstanceSizeUtils
787
.getComponentFontSize(tabPane)) / 2.0);
788
int dy = 2 + borderDelta;
789
Set<Side> straightSides = EnumSet.of(Side.BOTTOM);
791
int cornerRadius = height / 3;
792
if (shaper instanceof ClassicButtonShaper) {
793
cornerRadius = (int) SubstanceSizeUtils
794
.getClassicButtonCornerRadius(SubstanceSizeUtils
795
.getComponentFontSize(tabPane));
796
if ((tabPlacement == TOP) || (tabPlacement == BOTTOM))
802
GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width,
803
height + dy, cornerRadius, straightSides, borderInsets);
805
BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
807
Graphics2D resGraphics = result.createGraphics();
809
if (!paintOnlyBorder) {
810
fillPainter.paintContourBackground(resGraphics, tabPane, width,
811
height + dy, contour, false, fillScheme, true);
814
int borderThickness = (int) SubstanceSizeUtils
815
.getBorderStrokeWidth(SubstanceSizeUtils
816
.getComponentFontSize(tabPane));
817
GeneralPath contourInner = borderPainter.isPaintingInnerContour() ? SubstanceOutlineUtilities
818
.getBaseOutline(width, height + dy, cornerRadius
819
- borderThickness, straightSides, borderThickness
823
borderPainter.paintBorder(resGraphics, tabPane, width, height + dy,
824
contour, contourInner, borderScheme);
826
resGraphics.dispose();
831
* Retrieves tab background that will be shown on the screen. Unlike
833
* , the result is rotated as necessary (for {@link SwingConstants#LEFT} and
834
* {@link SwingConstants#RIGHT} placement) and blended for selected tabs.
845
* Indication whether the tab is selected.
846
* @param tabPlacement
851
* Color scheme for coloring the background.
852
* @param borderScheme
853
* Color scheme for coloring the border.
854
* @return Tab background of specified parameters.
856
private static BufferedImage getFinalTabBackgroundImage(
857
JTabbedPane tabPane, int tabIndex, int x, int y, int width,
858
int height, boolean isSelected, int tabPlacement,
859
SubstanceConstants.Side side, SubstanceColorScheme colorScheme,
860
SubstanceColorScheme borderScheme) {
862
SubstanceFillPainter fillPainter = SubstanceCoreUtilities
863
.getFillPainter(tabPane);
864
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
865
.getBorderPainter(tabPane);
866
SubstanceButtonShaper shaper = SubstanceCoreUtilities
867
.getButtonShaper(tabPane);
868
Component compForBackground = tabPane.getTabComponentAt(tabIndex);
869
if (compForBackground == null)
870
compForBackground = tabPane.getComponentAt(tabIndex);
871
if (compForBackground == null)
872
compForBackground = tabPane;
873
Color tabColor = compForBackground.getBackground();
874
if (isSelected && (tabColor instanceof UIResource)) {
875
// special handling of tabs placed in decoration areas
876
tabColor = SubstanceColorUtilities
877
.getBackgroundFillColor(compForBackground);
879
HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
880
isSelected, tabPlacement, fillPainter.getDisplayName(),
881
borderPainter.getDisplayName(), shaper.getDisplayName(),
882
tabPlacement == SwingConstants.BOTTOM, side.name(), colorScheme
883
.getDisplayName(), borderScheme.getDisplayName(),
886
SubstanceSkin skin = SubstanceCoreUtilities.getSkin(tabPane);
887
BufferedImage result = SubstanceTabbedPaneUI.backgroundMap.get(key);
888
if (result == null) {
889
BufferedImage backgroundImage = null;
891
switch (tabPlacement) {
893
return SubstanceImageCreator.getRotated(
894
getFinalTabBackgroundImage(tabPane, tabIndex, x, y,
895
width, height, isSelected, SwingConstants.TOP,
896
side, colorScheme, borderScheme), 2);
900
backgroundImage = SubstanceTabbedPaneUI.getTabBackground(
901
tabPane, width, height, SwingConstants.TOP,
902
colorScheme, borderScheme, false);
904
int fw = backgroundImage.getWidth();
905
int fh = backgroundImage.getHeight();
906
BufferedImage fade = SubstanceCoreUtilities.getBlankImage(
908
Graphics2D fadeGraphics = fade.createGraphics();
909
fadeGraphics.setColor(tabColor);
910
fadeGraphics.fillRect(0, 0, fw, fh);
911
if (skin.getWatermark() != null) {
912
fadeGraphics.translate(-x, -y);
913
skin.getWatermark().drawWatermarkImage(fadeGraphics,
914
tabPane, x, y, fw, fh);
915
fadeGraphics.translate(x, y);
917
fadeGraphics.drawImage(SubstanceTabbedPaneUI
918
.getTabBackground(tabPane, width, height,
919
tabPlacement, colorScheme, borderScheme,
922
backgroundImage = SubstanceCoreUtilities
923
.blendImagesVertical(backgroundImage, fade, skin
924
.getSelectedTabFadeStart(), skin
925
.getSelectedTabFadeEnd());
928
SubstanceTabbedPaneUI.backgroundMap.put(key, backgroundImage);
930
return backgroundMap.get(key);
934
* Retrieves the image of the close button.
939
* Close button width.
941
* Close button height.
942
* @param toPaintBorder
943
* Indication whether the button background (including contour)
944
* needs to be painted.
946
* Color scheme for coloring the background.
948
* Color scheme for painting the close mark.
949
* @return Image of the close button of specified parameters.
951
private static BufferedImage getCloseButtonImage(JTabbedPane tabPane,
952
int width, int height, boolean toPaintBorder,
953
SubstanceColorScheme fillScheme, SubstanceColorScheme markScheme) {
954
SubstanceFillPainter fillPainter = SubstanceCoreUtilities
955
.getFillPainter(tabPane);
956
if (fillPainter == null)
959
HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
960
toPaintBorder, fillPainter.getDisplayName(), fillScheme
961
.getDisplayName(), markScheme.getDisplayName());
962
BufferedImage result = SubstanceTabbedPaneUI.closeButtonMap.get(key);
963
if (result == null) {
964
result = SubstanceCoreUtilities.getBlankImage(width, height);
965
Graphics2D finalGraphics = (Graphics2D) result.getGraphics();
968
GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
969
width, height, 1, null);
970
fillPainter.paintContourBackground(finalGraphics, tabPane,
971
width, height, contour, false, fillScheme, true);
972
// finalGraphics.drawImage(background, 0, 0, null);
973
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
974
.getBorderPainter(tabPane);
975
finalGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
976
RenderingHints.VALUE_ANTIALIAS_ON);
977
borderPainter.paintBorder(finalGraphics, tabPane, width,
978
height, contour, null, markScheme);
981
finalGraphics.setStroke(new BasicStroke(SubstanceSizeUtils
982
.getTabCloseButtonStrokeWidth(SubstanceSizeUtils
983
.getComponentFontSize(tabPane))));
985
int delta = (int) (Math.floor(SubstanceSizeUtils
986
.getBorderStrokeWidth(SubstanceSizeUtils
987
.getComponentFontSize(tabPane))));
990
int iconSize = width - delta;
992
Icon closeIcon = SubstanceImageCreator.getCloseIcon(iconSize,
993
markScheme, markScheme);
994
closeIcon.paintIcon(tabPane, finalGraphics, delta / 2, delta / 2);
996
SubstanceTabbedPaneUI.closeButtonMap.put(key, result);
1005
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBackground(java.awt.
1006
* Graphics, int, int, int, int, int, int, boolean)
1009
protected void paintTabBackground(Graphics g, int tabPlacement,
1010
final int tabIndex, final int x, final int y, int w, int h,
1011
boolean isSelected) {
1012
Graphics2D graphics = (Graphics2D) g.create();
1013
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
1016
boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
1017
ComponentState currState = this.getTabState(tabIndex);
1018
StateTransitionTracker.ModelStateInfo modelStateInfo = this
1019
.getModelStateInfo(tabIndex);
1021
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
1022
.getColorScheme(this.tabPane, tabIndex,
1023
ColorSchemeAssociationKind.TAB_BORDER, currState);
1024
SubstanceColorScheme baseColorScheme = SubstanceColorSchemeUtilities
1025
.getColorScheme(this.tabPane, tabIndex,
1026
ColorSchemeAssociationKind.TAB, currState);
1027
BufferedImage fullOpacity = null;
1029
// check if have windowModified property
1030
Component comp = this.tabPane.getComponentAt(tabIndex);
1031
boolean isWindowModified = SubstanceCoreUtilities.isTabModified(comp);
1032
boolean toMarkModifiedCloseButton = SubstanceCoreUtilities
1033
.toAnimateCloseIconOfModifiedTab(this.tabPane, tabIndex);
1034
if (isWindowModified && isEnabled && !toMarkModifiedCloseButton) {
1035
SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
1036
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
1038
float cyclePos = this.modifiedTimelines.get(comp)
1039
.getTimelinePosition();
1041
BufferedImage layer1 = SubstanceTabbedPaneUI
1042
.getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
1043
w, h, isSelected, tabPlacement,
1044
SubstanceConstants.Side.BOTTOM, colorScheme,
1046
BufferedImage layer2 = SubstanceTabbedPaneUI
1047
.getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
1048
w, h, isSelected, tabPlacement,
1049
SubstanceConstants.Side.BOTTOM, colorScheme2,
1052
fullOpacity = SubstanceCoreUtilities.getBlankImage(w, h);
1053
Graphics2D g2d = fullOpacity.createGraphics();
1054
if (cyclePos < 1.0f)
1055
g2d.drawImage(layer1, 0, 0, null);
1056
if (cyclePos > 0.0f) {
1057
g2d.setComposite(AlphaComposite.SrcOver.derive(cyclePos));
1058
g2d.drawImage(layer2, 0, 0, null);
1062
BufferedImage layerBase = SubstanceTabbedPaneUI
1063
.getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
1064
w, h, isSelected, tabPlacement,
1065
SubstanceConstants.Side.BOTTOM, baseColorScheme,
1068
if ((modelStateInfo == null) || currState.isDisabled()
1069
|| (modelStateInfo.getStateContributionMap().size() == 1)) {
1070
fullOpacity = layerBase;
1072
fullOpacity = SubstanceCoreUtilities.getBlankImage(w, h);
1073
Graphics2D g2d = fullOpacity.createGraphics();
1074
// draw the base layer
1075
g2d.drawImage(layerBase, 0, 0, null);
1077
// draw the other active layers
1078
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
1079
.getStateContributionMap().entrySet()) {
1080
ComponentState activeState = activeEntry.getKey();
1081
if (activeState == currState)
1084
float stateContribution = activeEntry.getValue()
1086
if (stateContribution > 0.0f) {
1087
g2d.setComposite(AlphaComposite.SrcOver
1088
.derive(stateContribution));
1089
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
1090
.getColorScheme(this.tabPane, tabIndex,
1091
ColorSchemeAssociationKind.TAB,
1093
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
1094
.getColorScheme(this.tabPane, tabIndex,
1095
ColorSchemeAssociationKind.TAB_BORDER,
1097
BufferedImage layer = SubstanceTabbedPaneUI
1098
.getFinalTabBackgroundImage(this.tabPane,
1099
tabIndex, x, y, w, h, isSelected,
1101
SubstanceConstants.Side.BOTTOM,
1102
fillScheme, borderScheme);
1103
g2d.drawImage(layer, 0, 0, null);
1109
// at this point the 'fillOpacity' has all the relevant layers for the
1112
SubstanceColorScheme baseMarkScheme = SubstanceColorSchemeUtilities
1113
.getColorScheme(this.tabPane, tabIndex,
1114
ColorSchemeAssociationKind.MARK, currState);
1116
// fix for defect 138
1117
graphics.clip(new Rectangle(x, y, w, h));
1119
boolean isRollover = (this.getRolloverTab() == tabIndex);
1121
float finalAlpha = 0.5f;
1122
StateTransitionTracker tabTracker = this.stateTransitionMultiTracker
1123
.getTracker(tabIndex);
1124
if (modelStateInfo != null) {
1125
finalAlpha += 0.5f * tabTracker
1126
.getFacetStrength(ComponentStateFacet.ROLLOVER);
1127
if (tabTracker.getFacetStrength(ComponentStateFacet.SELECTION) == 1.0f) {
1131
ComponentState tabState = getTabState(tabIndex);
1132
if (tabState.isFacetActive(ComponentStateFacet.ROLLOVER)
1133
|| tabState.isFacetActive(ComponentStateFacet.SELECTION)) {
1138
finalAlpha *= SubstanceColorSchemeUtilities.getAlpha(this.tabPane
1139
.getComponentAt(tabIndex), currState);
1141
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
1142
this.tabPane, finalAlpha, g));
1143
graphics.drawImage(fullOpacity, x, y, null);
1145
// Check if requested to paint close buttons.
1146
if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)
1149
float alpha = (isSelected || isRollover) ? 1.0f : 0.0f;
1151
if (tabTracker != null) {
1153
.getFacetStrength(ComponentStateFacet.ROLLOVER);
1157
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
1158
this.tabPane, finalAlpha * alpha, g));
1160
// paint close button
1161
Rectangle orig = this.getCloseButtonRectangleForDraw(tabIndex,
1164
boolean toPaintCloseBorder = false;
1166
if (this.substanceMouseLocation != null) {
1167
Rectangle bounds = new Rectangle();
1168
bounds = this.getTabBounds(tabIndex, bounds);
1169
if (toRotateTabsOnPlacement(tabPlacement)) {
1170
bounds = new Rectangle(bounds.x, bounds.y,
1171
bounds.height, bounds.width);
1173
Rectangle rect = this.getCloseButtonRectangleForEvents(
1174
tabIndex, bounds.x, bounds.y, bounds.width,
1176
// System.out.println("paint " + bounds + " " + rect +"
1178
// + mouseLocation);
1179
if (rect.contains(this.substanceMouseLocation)) {
1180
toPaintCloseBorder = true;
1185
if (isWindowModified && isEnabled && toMarkModifiedCloseButton) {
1186
SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
1187
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
1189
float cyclePos = this.modifiedTimelines.get(comp)
1190
.getTimelinePosition();
1192
BufferedImage layer1 = SubstanceTabbedPaneUI
1193
.getCloseButtonImage(this.tabPane, orig.width,
1194
orig.height, toPaintCloseBorder,
1195
colorScheme, baseMarkScheme);
1196
BufferedImage layer2 = SubstanceTabbedPaneUI
1197
.getCloseButtonImage(this.tabPane, orig.width,
1198
orig.height, toPaintCloseBorder,
1199
colorScheme2, baseMarkScheme);
1201
if (cyclePos < 1.0f) {
1202
graphics.drawImage(layer1, orig.x, orig.y, null);
1204
if (cyclePos > 0.0f) {
1205
graphics.setComposite(AlphaComposite.SrcOver
1207
graphics.drawImage(layer2, orig.x, orig.y, null);
1210
BufferedImage layerBase = SubstanceTabbedPaneUI
1211
.getCloseButtonImage(this.tabPane, orig.width,
1212
orig.height, toPaintCloseBorder,
1213
baseColorScheme, baseMarkScheme);
1215
if ((modelStateInfo == null)
1216
|| currState.isDisabled()
1217
|| (modelStateInfo.getStateContributionMap().size() == 1)) {
1218
graphics.drawImage(layerBase, orig.x, orig.y, null);
1220
BufferedImage complete = SubstanceCoreUtilities
1221
.getBlankImage(orig.width, orig.height);
1222
Graphics2D g2d = complete.createGraphics();
1223
// draw the base layer
1224
g2d.drawImage(layerBase, 0, 0, null);
1226
// draw the other active layers
1227
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
1228
.getStateContributionMap().entrySet()) {
1229
ComponentState activeState = activeEntry.getKey();
1230
if (activeState == currState)
1233
float stateContribution = activeEntry.getValue()
1235
if (stateContribution > 0.0f) {
1236
g2d.setComposite(AlphaComposite.SrcOver
1237
.derive(stateContribution));
1238
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
1239
.getColorScheme(this.tabPane, tabIndex,
1240
ColorSchemeAssociationKind.TAB,
1242
SubstanceColorScheme markScheme = SubstanceColorSchemeUtilities
1246
ColorSchemeAssociationKind.MARK,
1248
BufferedImage layer = SubstanceTabbedPaneUI
1249
.getCloseButtonImage(this.tabPane,
1250
orig.width, orig.height,
1251
toPaintCloseBorder, fillScheme,
1253
g2d.drawImage(layer, 0, 0, null);
1257
graphics.drawImage(complete, orig.x, orig.y, null);
1270
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintFocusIndicator(java.awt
1271
* .Graphics, int, java.awt.Rectangle[], int, java.awt.Rectangle,
1272
* java.awt.Rectangle, boolean)
1275
protected void paintFocusIndicator(Graphics g, int tabPlacement,
1276
Rectangle[] rects, int tabIndex, Rectangle iconRect,
1277
Rectangle textRect, boolean isSelected) {
1278
// empty to remove Basic functionality
1285
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBorder(java.awt.Graphics
1286
* , int, int, int, int, int, int, boolean)
1289
protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex,
1290
int x, int y, int w, int h, boolean isSelected) {
1291
// empty to remove Basic functionality
1297
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#createScrollButton(int)
1300
protected JButton createScrollButton(final int direction) {
1301
SubstanceScrollButton ssb = new SubstanceScrollButton(direction);
1302
Icon icon = new TransitionAwareIcon(ssb,
1303
new TransitionAwareIcon.Delegate() {
1305
public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
1306
// fix for defect 279 - tab pane might not yet have the
1308
int fontSize = SubstanceSizeUtils
1309
.getComponentFontSize(tabPane);
1310
return SubstanceImageCreator.getArrowIcon(fontSize,
1313
}, "substance.tabbedpane.scroll." + direction);
1321
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabHeight(int,
1325
protected int calculateTabHeight(int tabPlacement, int tabIndex,
1327
boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
1329
return this.getTabExtraWidth(tabPlacement, tabIndex)
1330
+ super.calculateTabWidth(tabPlacement, tabIndex, this
1332
return super.calculateTabHeight(tabPlacement, tabIndex, fontHeight);
1338
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabWidth(int, int,
1339
* java.awt.FontMetrics)
1342
protected int calculateTabWidth(int tabPlacement, int tabIndex,
1343
FontMetrics metrics) {
1344
boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
1346
return super.calculateTabHeight(tabPlacement, tabIndex, metrics
1348
int result = this.getTabExtraWidth(tabPlacement, tabIndex)
1349
+ super.calculateTabWidth(tabPlacement, tabIndex, metrics);
1356
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabHeight(int)
1359
protected int calculateMaxTabHeight(int tabPlacement) {
1360
if (toRotateTabsOnPlacement(tabPlacement))
1361
return super.calculateMaxTabHeight(tabPlacement);
1363
for (int i = 0; i < this.tabPane.getTabCount(); i++)
1364
result = Math.max(result, this.calculateTabHeight(tabPlacement, i,
1365
this.getFontMetrics().getHeight()));
1372
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabRunOverlay(int)
1375
protected int getTabRunOverlay(int tabPlacement) {
1376
boolean toSwap = this.toRotateTabsOnPlacement(tabPlacement);
1378
return super.getTabRunOverlay(tabPlacement);
1384
public void paint(Graphics g, JComponent c) {
1385
int selectedIndex = tabPane.getSelectedIndex();
1386
int tabPlacement = tabPane.getTabPlacement();
1388
ensureCurrentLayout();
1390
// If scrollable tabs are enabled, the tab area will be
1391
// painted by the scrollable tab panel instead.
1392
if (tabPane.getLayout().getClass() == TabbedPaneLayout.class) {
1393
paintTabArea(g, tabPlacement, selectedIndex);
1396
int width = tabPane.getWidth();
1397
int height = tabPane.getHeight();
1398
Insets insets = tabPane.getInsets();
1400
int x = insets.left;
1402
int w = width - insets.right - insets.left;
1403
int h = height - insets.top - insets.bottom;
1405
switch (tabPlacement) {
1407
x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1408
w -= (x - insets.left);
1411
w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
1414
h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1418
y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
1419
h -= (y - insets.top);
1422
Graphics2D g2d = (Graphics2D) g.create(x, y, w, h);
1423
BackgroundPaintingUtils.update(g2d, c, false);
1425
paintContentBorder(g, tabPlacement, selectedIndex);
1431
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTab(java.awt.Graphics,
1432
* int, java.awt.Rectangle[], int, java.awt.Rectangle, java.awt.Rectangle)
1435
protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
1436
int tabIndex, Rectangle iconRect, Rectangle textRect) {
1437
boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
1439
Graphics2D tempG = (Graphics2D) g.create();
1440
Rectangle tabRect = rects[tabIndex];
1441
Rectangle correctRect = new Rectangle(tabRect.x, tabRect.y,
1442
tabRect.height, tabRect.width);
1443
if (tabPlacement == SwingConstants.LEFT) {
1444
// rotate 90 degrees counterclockwise for LEFT orientation
1445
tempG.rotate(-Math.PI / 2, tabRect.x, tabRect.y);
1446
tempG.translate(-tabRect.height, 0);
1448
// rotate 90 degrees clockwise for RIGHT orientation
1449
tempG.rotate(Math.PI / 2, tabRect.x, tabRect.y);
1450
tempG.translate(0, -tabRect.getWidth());
1452
tempG.setColor(Color.red);
1453
rects[tabIndex] = correctRect;
1454
super.paintTab(tempG, tabPlacement, rects, tabIndex, iconRect,
1456
rects[tabIndex] = tabRect;
1459
if (tabPane.getLayout().getClass() == TabbedPaneLayout.class) {
1460
super.paintTab(g, tabPlacement, rects, tabIndex, iconRect,
1463
// scrolled tabs are painted by
1464
// BasicTabbedPaneUI.ScrollableTabPanel
1465
// which does not have the right rendering hints
1466
Graphics2D g2d = (Graphics2D) g.create();
1467
RenderingUtils.installDesktopHints(g2d, tabPane);
1468
super.paintTab(g2d, tabPlacement, rects, tabIndex, iconRect,
1479
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabArea(java.awt.Graphics,
1483
protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
1484
if (this.substanceContentOpaque) {
1485
int width = calculateTabAreaWidth(tabPlacement, runCount,
1487
if ((tabPlacement == SwingConstants.TOP)
1488
|| (tabPlacement == SwingConstants.BOTTOM))
1489
width = Math.max(width, tabPane.getWidth());
1490
int height = calculateTabAreaHeight(tabPlacement, runCount,
1492
if (toRotateTabsOnPlacement(tabPlacement))
1493
height = Math.max(height, tabPane.getHeight());
1495
// restrict the painting to the tab area only
1496
Graphics2D g2d = (Graphics2D) g.create(0, 0, width, height);
1497
BackgroundPaintingUtils.update(g2d, this.tabPane, true);
1500
super.paintTabArea(g, tabPlacement, selectedIndex);
1504
* Retrieves the close button rectangle for drawing purposes.
1509
* X coordinate of the tab.
1511
* Y coordinate of the tab.
1516
* @return The close button rectangle.
1518
protected Rectangle getCloseButtonRectangleForDraw(int tabIndex, int x,
1519
int y, int width, int height) {
1520
int dimension = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
1523
int borderDelta = (int) Math.ceil(3.0 * SubstanceSizeUtils
1524
.getBorderStrokeWidth(SubstanceSizeUtils
1525
.getComponentFontSize(this.tabPane)));
1527
int xs = this.tabPane.getComponentOrientation().isLeftToRight() ? (x
1528
+ width - dimension - borderDelta) : (x + borderDelta);
1529
int ys = y + (height - dimension) / 2 + 1;
1530
return new Rectangle(xs, ys, dimension, dimension);
1534
* Retrieves the close button rectangle for event handling.
1539
* X coordinate of the tab.
1541
* Y coordinate of the tab.
1546
* @return The close button rectangle.
1548
protected Rectangle getCloseButtonRectangleForEvents(int tabIndex, int x,
1549
int y, int w, int h) {
1550
int tabPlacement = this.tabPane.getTabPlacement();
1551
boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
1553
return this.getCloseButtonRectangleForDraw(tabIndex, x, y, w, h);
1555
int dimension = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
1558
Point2D transCorner = null;
1559
Rectangle rectForDraw = this.getCloseButtonRectangleForDraw(tabIndex,
1561
if (tabPlacement == SwingConstants.LEFT) {
1562
AffineTransform trans = new AffineTransform();
1563
trans.rotate(-Math.PI / 2, x, y);
1564
trans.translate(-h, 0);
1565
Point2D.Double origCorner = new Point2D.Double(rectForDraw
1566
.getMaxX(), rectForDraw.getMinY());
1567
transCorner = trans.transform(origCorner, null);
1569
// rotate 90 degrees clockwise for RIGHT orientation
1570
AffineTransform trans = new AffineTransform();
1571
trans.rotate(Math.PI / 2, x, y);
1572
trans.translate(0, -w);
1573
Point2D.Double origCorner = new Point2D.Double(rectForDraw
1574
.getMinX(), rectForDraw.getMaxY());
1575
transCorner = trans.transform(origCorner, null);
1577
return new Rectangle((int) transCorner.getX(),
1578
(int) transCorner.getY(), dimension, dimension);
1582
* Implementation of the fade tracker callback that repaints a single tab.
1584
* @author Kirill Grouchnikov
1586
protected class TabRepaintCallback extends UIThreadTimelineCallbackAdapter {
1588
* The associated tabbed pane.
1590
protected JTabbedPane tabbedPane;
1593
* The associated tab index.
1595
protected int tabIndex;
1598
* Creates new tab repaint callback.
1601
* The associated tabbed pane.
1603
* The associated tab index.
1605
public TabRepaintCallback(JTabbedPane tabPane, int tabIndex) {
1606
this.tabbedPane = tabPane;
1607
this.tabIndex = tabIndex;
1611
public void onTimelinePulse(float durationFraction,
1612
float timelinePosition) {
1617
public void onTimelineStateChanged(TimelineState oldState,
1618
TimelineState newState, float durationFraction,
1619
float timelinePosition) {
1624
* Repaints the relevant tab.
1626
protected void repaintTab() {
1627
SwingUtilities.invokeLater(new Runnable() {
1630
if (SubstanceTabbedPaneUI.this.tabPane == null) {
1631
// may happen if the LAF was switched in the meantime
1634
SubstanceTabbedPaneUI.this.ensureCurrentLayout();
1635
int tabCount = SubstanceTabbedPaneUI.this.tabPane
1638
&& (TabRepaintCallback.this.tabIndex < tabCount)
1639
&& (TabRepaintCallback.this.tabIndex < SubstanceTabbedPaneUI.this.rects.length)) {
1640
// need to retrieve the tab rectangle since the tabs
1641
// can be moved while animating (especially when the
1642
// current layout is SCROLL_LAYOUT)
1643
Rectangle rect = SubstanceTabbedPaneUI.this
1645
SubstanceTabbedPaneUI.this.tabPane,
1646
TabRepaintCallback.this.tabIndex);
1647
// System.out.println("Repainting " + tabIndex);
1648
SubstanceTabbedPaneUI.this.tabPane.repaint(rect);
1656
* Ensures the current layout.
1658
protected void ensureCurrentLayout() {
1659
if (!this.tabPane.isValid()) {
1660
this.tabPane.validate();
1663
* If tabPane doesn't have a peer yet, the validate() call will silently
1664
* fail. We handle that by forcing a layout if tabPane is still invalid.
1667
if (!this.tabPane.isValid()) {
1668
LayoutManager lm = this.tabPane.getLayout();
1669
if (lm instanceof BasicTabbedPaneUI.TabbedPaneLayout) {
1670
BasicTabbedPaneUI.TabbedPaneLayout layout = (BasicTabbedPaneUI.TabbedPaneLayout) lm;
1671
layout.calculateLayoutInfo();
1677
* Tries closing tabs based on the specified tab index and tab close kind.
1681
* @param tabCloseKind
1684
protected void tryCloseTabs(int tabIndex, TabCloseKind tabCloseKind) {
1685
if (tabCloseKind == null)
1687
if (tabCloseKind == TabCloseKind.NONE)
1690
if (tabCloseKind == TabCloseKind.ALL_BUT_THIS) {
1691
// close all but this
1692
Set<Integer> indexes = new HashSet<Integer>();
1693
for (int i = 0; i < this.tabPane.getTabCount(); i++)
1696
this.tryCloseTabs(indexes);
1699
if (tabCloseKind == TabCloseKind.ALL) {
1701
Set<Integer> indexes = new HashSet<Integer>();
1702
for (int i = 0; i < this.tabPane.getTabCount(); i++)
1704
this.tryCloseTabs(indexes);
1707
this.tryCloseTab(tabIndex);
1711
* Tries closing a single tab.
1716
protected void tryCloseTab(int tabIndex) {
1717
Component component = this.tabPane.getComponentAt(tabIndex);
1718
Set<Component> componentSet = new HashSet<Component>();
1719
componentSet.add(component);
1721
// check if there's at least one listener
1722
// that vetoes the closing
1723
boolean isVetoed = false;
1724
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1725
.getAllTabCloseListeners(this.tabPane)) {
1726
if (listener instanceof VetoableTabCloseListener) {
1727
VetoableTabCloseListener vetoableListener = (VetoableTabCloseListener) listener;
1729
|| vetoableListener.vetoTabClosing(this.tabPane,
1732
if (listener instanceof VetoableMultipleTabCloseListener) {
1733
VetoableMultipleTabCloseListener vetoableListener = (VetoableMultipleTabCloseListener) listener;
1735
|| vetoableListener.vetoTabsClosing(this.tabPane,
1742
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1743
.getAllTabCloseListeners(this.tabPane)) {
1744
if (listener instanceof TabCloseListener)
1745
((TabCloseListener) listener).tabClosing(this.tabPane,
1747
if (listener instanceof MultipleTabCloseListener)
1748
((MultipleTabCloseListener) listener).tabsClosing(this.tabPane,
1752
this.tabPane.remove(tabIndex);
1753
if (this.tabPane.getTabCount() > 0) {
1754
this.selectPreviousTab(0);
1755
this.selectNextTab(this.tabPane.getSelectedIndex());
1757
this.tabPane.repaint();
1759
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1760
.getAllTabCloseListeners(this.tabPane)) {
1761
if (listener instanceof TabCloseListener)
1762
((TabCloseListener) listener)
1763
.tabClosed(this.tabPane, component);
1764
if (listener instanceof MultipleTabCloseListener)
1765
((MultipleTabCloseListener) listener).tabsClosed(this.tabPane,
1771
* Tries closing the specified tabs.
1776
protected void tryCloseTabs(Set<Integer> tabIndexes) {
1777
Set<Component> componentSet = new HashSet<Component>();
1778
for (int tabIndex : tabIndexes) {
1779
componentSet.add(this.tabPane.getComponentAt(tabIndex));
1782
// check if there's at least one listener
1783
// that vetoes the closing
1784
boolean isVetoed = false;
1785
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1786
.getAllTabCloseListeners(this.tabPane)) {
1787
if (listener instanceof VetoableMultipleTabCloseListener) {
1788
VetoableMultipleTabCloseListener vetoableListener = (VetoableMultipleTabCloseListener) listener;
1790
|| vetoableListener.vetoTabsClosing(this.tabPane,
1797
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1798
.getAllTabCloseListeners(this.tabPane)) {
1799
if (listener instanceof MultipleTabCloseListener)
1800
((MultipleTabCloseListener) listener).tabsClosing(this.tabPane,
1804
for (Component toRemove : componentSet) {
1805
this.tabPane.remove(toRemove);
1808
if (this.tabPane.getTabCount() > 0) {
1809
this.selectPreviousTab(0);
1810
this.selectNextTab(this.tabPane.getSelectedIndex());
1812
this.tabPane.repaint();
1814
for (BaseTabCloseListener listener : SubstanceLookAndFeel
1815
.getAllTabCloseListeners(this.tabPane)) {
1816
if (listener instanceof MultipleTabCloseListener)
1817
((MultipleTabCloseListener) listener).tabsClosed(this.tabPane,
1825
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftX(int, int,
1829
protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
1830
boolean isSelected) {
1832
if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)) {
1833
if (this.tabPane.getComponentOrientation().isLeftToRight()) {
1834
delta = 5 - SubstanceCoreUtilities.getCloseButtonSize(
1835
this.tabPane, tabIndex);
1837
delta = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
1842
+ super.getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
1848
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftY(int, int,
1852
protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
1853
boolean isSelected) {
1855
if (tabPlacement == SwingConstants.BOTTOM)
1863
* Returns extra width for the specified tab.
1865
* @param tabPlacement
1869
* @return Extra width for the specified tab.
1871
protected int getTabExtraWidth(int tabPlacement, int tabIndex) {
1873
SubstanceButtonShaper shaper = SubstanceCoreUtilities
1874
.getButtonShaper(this.tabPane);
1875
if (shaper instanceof ClassicButtonShaper)
1876
extraWidth = (int) (2.0 * SubstanceSizeUtils
1877
.getClassicButtonCornerRadius(SubstanceSizeUtils
1878
.getComponentFontSize(this.tabPane)));
1880
extraWidth = super.calculateTabHeight(tabPlacement, tabIndex, this
1881
.getFontMetrics().getHeight()) / 3;
1883
if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)
1884
&& this.tabPane.isEnabledAt(tabIndex)) {
1885
extraWidth += (4 + SubstanceCoreUtilities.getCloseButtonSize(
1886
this.tabPane, tabIndex));
1889
// System.out.println(tabPane.getTitleAt(tabIndex) + ":" + extraWidth);
1894
* Returns the index of the tab currently being rolled-over.
1896
* @return Index of the tab currently being rolled-over.
1898
public int getRolloverTabIndex() {
1899
return this.getRolloverTab();
1903
* Sets new value for tab area insets.
1908
public void setTabAreaInsets(Insets insets) {
1909
Insets old = this.tabAreaInsets;
1910
this.tabAreaInsets = insets;
1911
// Fire a property change event so that the tabbed
1912
// pane can revalidate itself
1913
LafWidgetUtilities.firePropertyChangeEvent(this.tabPane,
1914
"tabAreaInsets", old, tabAreaInsets);
1918
* Returns tab area insets.
1920
* @return Tab area insets.
1922
public Insets getTabAreaInsets() {
1923
return this.tabAreaInsets;
1927
* Returns the tab rectangle for the specified tab.
1931
* @return The tab rectangle for the specified parameters.
1933
public Rectangle getTabRectangle(int tabIndex) {
1934
return this.rects[tabIndex];
1938
* Returns the memory usage string.
1940
* @return The memory usage string.
1942
public static String getMemoryUsage() {
1943
StringBuffer sb = new StringBuffer();
1944
sb.append("SubstanceTabbedPaneUI: \n");
1945
sb.append("\t" + SubstanceTabbedPaneUI.backgroundMap.size()
1947
return sb.toString();
1953
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#shouldPadTabRun(int, int)
1956
protected boolean shouldPadTabRun(int tabPlacement, int run) {
1957
// Don't pad last run
1958
return this.runCount > 1 && run < this.runCount - 1;
1964
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#createLayoutManager()
1967
protected LayoutManager createLayoutManager() {
1968
if (this.tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
1969
return super.createLayoutManager();
1971
return new TabbedPaneLayout();
1975
* Layout for the tabbed pane.
1977
* @author Kirill Grouchnikov
1979
public class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
1981
* Creates a new layout.
1983
public TabbedPaneLayout() {
1984
SubstanceTabbedPaneUI.this.super();
1990
* @seejavax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#
1991
* normalizeTabRuns(int, int, int, int)
1994
protected void normalizeTabRuns(int tabPlacement, int tabCount,
1995
int start, int max) {
1996
// Only normalize the runs for top & bottom; normalizing
1997
// doesn't look right for Metal's vertical tabs
1998
// because the last run isn't padded and it looks odd to have
1999
// fat tabs in the first vertical runs, but slimmer ones in the
2000
// last (this effect isn't noticeable for horizontal tabs).
2001
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2002
super.normalizeTabRuns(tabPlacement, tabCount, start, max);
2010
* javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#rotateTabRuns
2014
protected void rotateTabRuns(int tabPlacement, int selectedRun) {
2015
// Don't rotate runs!
2022
* javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#padSelectedTab
2026
protected void padSelectedTab(int tabPlacement, int selectedIndex) {
2027
// Don't pad selected tab
2034
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getContentBorderInsets(int)
2037
protected Insets getContentBorderInsets(int tabPlacement) {
2038
Insets insets = SubstanceSizeUtils
2039
.getTabbedPaneContentInsets(SubstanceSizeUtils
2040
.getComponentFontSize(this.tabPane));
2042
TabContentPaneBorderKind kind = SubstanceCoreUtilities
2043
.getContentBorderKind(this.tabPane);
2044
boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
2045
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2046
boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
2047
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2048
int delta = isDouble ? (int) (3.0 * SubstanceSizeUtils
2049
.getBorderStrokeWidth(SubstanceSizeUtils
2050
.getComponentFontSize(tabPane))) : 0;
2053
switch (tabPlacement) {
2055
return new Insets(insets.top + delta, 0, 0, 0);
2057
return new Insets(0, insets.left + delta, 0, 0);
2059
return new Insets(0, 0, 0, insets.right + delta);
2061
return new Insets(0, 0, insets.bottom + delta, 0);
2064
switch (tabPlacement) {
2066
return new Insets(insets.top + delta, insets.left,
2067
insets.bottom, insets.right);
2069
return new Insets(insets.top, insets.left + delta,
2070
insets.bottom, insets.right);
2072
return new Insets(insets.top, insets.left, insets.bottom,
2073
insets.right + delta);
2075
return new Insets(insets.top, insets.left, insets.bottom
2076
+ delta, insets.right);
2086
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorder(java.awt.
2087
* Graphics, int, int)
2090
protected void paintContentBorder(Graphics g, int tabPlacement,
2091
int selectedIndex) {
2092
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
2093
.getColorScheme(this.tabPane, selectedIndex,
2094
ColorSchemeAssociationKind.TAB, ComponentState.ENABLED);
2095
this.highlight = scheme.isDark() ? SubstanceColorUtilities
2096
.getAlphaColor(scheme.getUltraDarkColor(), 100) : scheme
2098
super.paintContentBorder(g, tabPlacement, selectedIndex);
2105
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderBottomEdge
2106
* (java.awt.Graphics, int, int, int, int, int, int)
2109
protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
2110
int selectedIndex, int x, int y, int w, int h) {
2111
TabContentPaneBorderKind kind = SubstanceCoreUtilities
2112
.getContentBorderKind(this.tabPane);
2113
boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
2114
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2115
boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
2116
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2118
if (tabPlacement != SwingConstants.BOTTOM)
2121
int ribbonDelta = (int) (2.0 * SubstanceSizeUtils
2122
.getBorderStrokeWidth(SubstanceSizeUtils
2123
.getComponentFontSize(tabPane)));
2125
Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
2126
selectedIndex, this.calcRect);
2128
Graphics2D g2d = (Graphics2D) g.create();
2129
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2130
RenderingHints.VALUE_ANTIALIAS_ON);
2131
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
2132
RenderingHints.VALUE_STROKE_NORMALIZE);
2133
float strokeWidth = SubstanceSizeUtils
2134
.getBorderStrokeWidth(SubstanceSizeUtils
2135
.getComponentFontSize(tabPane));
2136
int joinKind = BasicStroke.JOIN_ROUND;
2137
int capKind = BasicStroke.CAP_BUTT;
2138
g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
2139
int offset = (int) (strokeWidth / 2.0);
2141
boolean isUnbroken = (tabPlacement != BOTTOM || selectedIndex < 0
2142
|| (selRect.y - 1 > h) || (selRect.x < x || selRect.x > x + w));
2149
// Draw unbroken line if tabs are not on BOTTOM, OR
2150
// selected tab is not in run adjacent to content, OR
2151
// selected tab is not visible (SCROLL_TAB_LAYOUT)
2152
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
2153
.getColorScheme(this.tabPane, selectedIndex,
2154
ColorSchemeAssociationKind.TAB_BORDER,
2155
ComponentState.SELECTED);
2156
Color darkShadowColor = SubstanceColorUtilities
2157
.getMidBorderColor(borderScheme);
2159
g2d.setColor(this.highlight);
2160
g2d.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
2162
// Break line to show visual connection to selected tab
2163
SubstanceButtonShaper shaper = SubstanceCoreUtilities
2164
.getButtonShaper(this.tabPane);
2165
int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
2166
int borderInsets = (int) Math.floor(SubstanceSizeUtils
2167
.getBorderStrokeWidth(SubstanceSizeUtils
2168
.getComponentFontSize(tabPane)) / 2.0);
2169
GeneralPath bottomOutline = new GeneralPath();
2170
bottomOutline.moveTo(x, y + h - 1);
2171
bottomOutline.lineTo(selRect.x + borderInsets, y + h - 1);
2172
int bumpHeight = super.calculateTabHeight(tabPlacement, 0,
2173
SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
2174
bottomOutline.lineTo(selRect.x + borderInsets, y + h + bumpHeight);
2175
if (selRect.x + selRect.width < x + w - 1) {
2176
int selectionEndX = selRect.x + selRect.width - delta - 1
2178
bottomOutline.lineTo(selectionEndX, y + h - 1 + bumpHeight);
2179
bottomOutline.lineTo(selectionEndX, y + h - 1);
2180
bottomOutline.lineTo(x + w - 1, y + h - 1);
2182
g2d.setPaint(new GradientPaint(x, y + h - 1, darkShadowColor, x, y
2183
+ h - 1 + bumpHeight, SubstanceColorUtilities
2184
.getAlphaColor(darkShadowColor, 0)));
2185
g2d.draw(bottomOutline);
2189
if (tabPlacement == BOTTOM) {
2190
g2d.setColor(this.highlight);
2191
// g2d.drawLine(x+1, y + h - 2 - ribbonDelta, x + w - 2,
2192
// y + h - 2 - ribbonDelta);
2193
g2d.setColor(darkShadowColor);
2194
g2d.drawLine(x, y + h - 1 - ribbonDelta, x + w - 1, y + h - 1
2197
if (tabPlacement == LEFT) {
2198
g2d.setPaint(new GradientPaint(x, y + h - 1, darkShadowColor, x
2199
+ 4 * ribbonDelta, y + h - 1, this.highlight));
2200
g2d.drawLine(x, y + h - 1, x + 4 * ribbonDelta, y + h - 1);
2202
if (tabPlacement == RIGHT) {
2203
g2d.setPaint(new GradientPaint(x + w - 1 - 4 * ribbonDelta, y
2204
+ h - 1, this.highlight, x + w - 1, y + h - 1,
2206
g2d.drawLine(x + w - 1 - 4 * ribbonDelta, y + h - 1, x + w - 1,
2218
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderLeftEdge(java
2219
* .awt.Graphics, int, int, int, int, int, int)
2222
protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
2223
int selectedIndex, int x, int y, int w, int h) {
2224
TabContentPaneBorderKind kind = SubstanceCoreUtilities
2225
.getContentBorderKind(this.tabPane);
2226
boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
2227
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2228
boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
2229
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2231
if (tabPlacement != SwingConstants.LEFT)
2234
int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
2235
.getBorderStrokeWidth(SubstanceSizeUtils
2236
.getComponentFontSize(tabPane)));
2238
Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
2239
selectedIndex, this.calcRect);
2241
Graphics2D g2d = (Graphics2D) g.create();
2242
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2243
RenderingHints.VALUE_ANTIALIAS_ON);
2244
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
2245
RenderingHints.VALUE_STROKE_NORMALIZE);
2246
float strokeWidth = SubstanceSizeUtils
2247
.getBorderStrokeWidth(SubstanceSizeUtils
2248
.getComponentFontSize(tabPane));
2249
int joinKind = BasicStroke.JOIN_ROUND;
2250
int capKind = BasicStroke.CAP_BUTT;
2251
g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
2252
int offset = (int) (strokeWidth / 2.0);
2254
boolean isUnbroken = (tabPlacement != LEFT || selectedIndex < 0
2255
|| (selRect.x + selRect.width + 1 < x) || (selRect.y < y || selRect.y > y
2263
// Draw unbroken line if tabs are not on LEFT, OR
2264
// selected tab is not in run adjacent to content, OR
2265
// selected tab is not visible (SCROLL_TAB_LAYOUT)
2266
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
2267
.getColorScheme(this.tabPane, selectedIndex,
2268
ColorSchemeAssociationKind.TAB_BORDER,
2269
ComponentState.SELECTED);
2270
Color darkShadowColor = SubstanceColorUtilities
2271
.getMidBorderColor(borderScheme);
2273
g2d.setColor(this.highlight);
2274
g2d.drawLine(x, y, x, y + h);
2276
// Break line to show visual connection to selected tab
2277
SubstanceButtonShaper shaper = SubstanceCoreUtilities
2278
.getButtonShaper(this.tabPane);
2279
int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
2281
int borderInsets = (int) Math.floor(SubstanceSizeUtils
2282
.getBorderStrokeWidth(SubstanceSizeUtils
2283
.getComponentFontSize(tabPane)) / 2.0);
2284
GeneralPath leftOutline = new GeneralPath();
2285
leftOutline.moveTo(x, y);
2286
leftOutline.lineTo(x, selRect.y + borderInsets);
2287
int bumpWidth = super.calculateTabHeight(tabPlacement, 0,
2288
SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
2289
leftOutline.lineTo(x - bumpWidth, selRect.y + borderInsets);
2290
if (selRect.y + selRect.height < y + h) {
2291
int selectionEndY = selRect.y + selRect.height - delta - 1
2293
leftOutline.lineTo(x - bumpWidth, selectionEndY);
2294
leftOutline.lineTo(x, selectionEndY);
2295
leftOutline.lineTo(x, y + h);
2297
g2d.setPaint(new GradientPaint(x, y, darkShadowColor,
2298
x - bumpWidth, y, SubstanceColorUtilities.getAlphaColor(
2299
darkShadowColor, 0)));
2300
g2d.draw(leftOutline);
2305
if (tabPlacement == LEFT) {
2306
g2d.setColor(darkShadowColor);
2307
g2d.drawLine(x + ribbonDelta, y, x + ribbonDelta, y + h);
2308
// g2d.setColor(this.highlight);
2309
// g2d.drawLine(x + 1 + ribbonDelta, y + 1, x + 1 + ribbonDelta,
2313
if (tabPlacement == TOP) {
2314
g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x, y + 4
2315
* ribbonDelta, this.highlight));
2316
g2d.drawLine(x, y, x, y + 4 * ribbonDelta);
2318
if (tabPlacement == BOTTOM) {
2319
g2d.setPaint(new GradientPaint(x, y + h - 1 - 4 * ribbonDelta,
2320
this.highlight, x, y + h - 1, darkShadowColor));
2321
g2d.drawLine(x, y + h - 1 - 4 * ribbonDelta, x, y + h - 1);
2331
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderRightEdge(
2332
* java.awt.Graphics, int, int, int, int, int, int)
2335
protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
2336
int selectedIndex, int x, int y, int w, int h) {
2337
TabContentPaneBorderKind kind = SubstanceCoreUtilities
2338
.getContentBorderKind(this.tabPane);
2339
boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
2340
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2341
boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
2342
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2344
if (tabPlacement != SwingConstants.RIGHT)
2347
int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
2348
.getBorderStrokeWidth(SubstanceSizeUtils
2349
.getComponentFontSize(tabPane)));
2351
Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
2352
selectedIndex, this.calcRect);
2354
Graphics2D g2d = (Graphics2D) g.create();
2355
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2356
RenderingHints.VALUE_ANTIALIAS_ON);
2357
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
2358
RenderingHints.VALUE_STROKE_NORMALIZE);
2359
float strokeWidth = SubstanceSizeUtils
2360
.getBorderStrokeWidth(SubstanceSizeUtils
2361
.getComponentFontSize(tabPane));
2362
int joinKind = BasicStroke.JOIN_ROUND;
2363
int capKind = BasicStroke.CAP_BUTT;
2364
g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
2365
int offset = (int) (strokeWidth / 2.0);
2367
boolean isUnbroken = (tabPlacement != RIGHT || selectedIndex < 0
2368
|| (selRect.x - 1 > w) || (selRect.y < y || selRect.y > y + h));
2375
// Draw unbroken line if tabs are not on RIGHT, OR
2376
// selected tab is not in run adjacent to content, OR
2377
// selected tab is not visible (SCROLL_TAB_LAYOUT)
2378
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
2379
.getColorScheme(this.tabPane, selectedIndex,
2380
ColorSchemeAssociationKind.TAB_BORDER,
2381
ComponentState.SELECTED);
2382
Color darkShadowColor = SubstanceColorUtilities
2383
.getMidBorderColor(borderScheme);
2385
g2d.setColor(this.highlight);
2386
g2d.drawLine(x + w - 1, y, x + w - 1, y + h);
2388
// Break line to show visual connection to selected tab
2389
SubstanceButtonShaper shaper = SubstanceCoreUtilities
2390
.getButtonShaper(this.tabPane);
2391
int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
2393
int borderInsets = (int) Math.floor(SubstanceSizeUtils
2394
.getBorderStrokeWidth(SubstanceSizeUtils
2395
.getComponentFontSize(tabPane)) / 2.0);
2396
GeneralPath rightOutline = new GeneralPath();
2397
rightOutline.moveTo(x + w - 1, y);
2398
rightOutline.lineTo(x + w - 1, selRect.y + borderInsets);
2399
int bumpWidth = super.calculateTabHeight(tabPlacement, 0,
2400
SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
2402
.lineTo(x + w - 1 + bumpWidth, selRect.y + borderInsets);
2403
if (selRect.y + selRect.height < y + h) {
2404
int selectionEndY = selRect.y + selRect.height - delta - 1
2406
rightOutline.lineTo(x + w - 1 + bumpWidth, selectionEndY);
2407
rightOutline.lineTo(x + w - 1, selectionEndY);
2408
rightOutline.lineTo(x + w - 1, y + h);
2410
g2d.setPaint(new GradientPaint(x + w - 1, y, darkShadowColor, x + w
2411
- 1 + bumpWidth, y, SubstanceColorUtilities.getAlphaColor(
2412
darkShadowColor, 0)));
2413
g2d.draw(rightOutline);
2417
if (tabPlacement == RIGHT) {
2418
g2d.setColor(this.highlight);
2419
// g2d.drawLine(x + w - 2 - ribbonDelta, y + 1, x + w - 2 -
2420
// ribbonDelta, y + h - 1);
2421
g2d.setColor(darkShadowColor);
2422
g2d.drawLine(x + w - 1 - ribbonDelta, y, x + w - 1
2423
- ribbonDelta, y + h);
2425
if (tabPlacement == TOP) {
2426
g2d.setPaint(new GradientPaint(x + w - 1, y, darkShadowColor, x
2427
+ w - 1, y + 4 * ribbonDelta, this.highlight));
2428
g2d.drawLine(x + w - 1, y, x + w - 1, y + 4 * ribbonDelta);
2430
if (tabPlacement == BOTTOM) {
2431
g2d.setPaint(new GradientPaint(x + w - 1, y + h - 1 - 4
2432
* ribbonDelta, this.highlight, x + w - 1, y + h - 1,
2434
g2d.drawLine(x + w - 1, y + h - 1 - 4 * ribbonDelta, x + w - 1,
2445
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderTopEdge(java
2446
* .awt.Graphics, int, int, int, int, int, int)
2449
protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
2450
int selectedIndex, int x, int y, int w, int h) {
2451
TabContentPaneBorderKind kind = SubstanceCoreUtilities
2452
.getContentBorderKind(this.tabPane);
2453
boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
2454
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2455
boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
2456
|| (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
2458
if (tabPlacement != SwingConstants.TOP)
2461
int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
2462
.getBorderStrokeWidth(SubstanceSizeUtils
2463
.getComponentFontSize(tabPane)));
2465
Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
2466
selectedIndex, this.calcRect);
2468
Graphics2D g2d = (Graphics2D) g.create();
2469
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
2470
RenderingHints.VALUE_ANTIALIAS_ON);
2471
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
2472
RenderingHints.VALUE_STROKE_NORMALIZE);
2473
float strokeWidth = SubstanceSizeUtils
2474
.getBorderStrokeWidth(SubstanceSizeUtils
2475
.getComponentFontSize(tabPane));
2476
int joinKind = BasicStroke.JOIN_ROUND;
2477
int capKind = BasicStroke.CAP_BUTT;
2478
g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
2479
int offset = (int) (strokeWidth / 2.0);
2481
boolean isUnbroken = (tabPlacement != TOP || selectedIndex < 0
2482
|| (selRect.y + selRect.height + 1 < y) || (selRect.x < x || selRect.x > x
2490
// Draw unbroken line if tabs are not on TOP, OR
2491
// selected tab is not in run adjacent to content, OR
2492
// selected tab is not visible (SCROLL_TAB_LAYOUT)
2493
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
2494
.getColorScheme(this.tabPane, selectedIndex,
2495
ColorSchemeAssociationKind.TAB_BORDER,
2496
ComponentState.SELECTED);
2497
Color darkShadowColor = SubstanceColorUtilities
2498
.getMidBorderColor(borderScheme);
2500
g2d.setColor(this.highlight);
2501
g2d.drawLine(x, y, x + w - 1, y);
2503
// Break line to show visual connection to selected tab
2504
SubstanceButtonShaper shaper = SubstanceCoreUtilities
2505
.getButtonShaper(this.tabPane);
2506
int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
2507
int borderInsets = (int) Math.floor(SubstanceSizeUtils
2508
.getBorderStrokeWidth(SubstanceSizeUtils
2509
.getComponentFontSize(tabPane)) / 2.0);
2510
GeneralPath topOutline = new GeneralPath();
2511
topOutline.moveTo(x, y);
2512
topOutline.lineTo(selRect.x + borderInsets, y);
2513
int bumpHeight = super.calculateTabHeight(tabPlacement, 0,
2514
SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
2515
topOutline.lineTo(selRect.x + borderInsets, y - bumpHeight);
2516
if (selRect.x + selRect.width < x + w - 1) {
2517
int selectionEndX = selRect.x + selRect.width - delta - 1
2519
topOutline.lineTo(selectionEndX, y - bumpHeight);
2520
topOutline.lineTo(selectionEndX, y);
2521
topOutline.lineTo(x + w - 1, y);
2523
g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x, y
2524
- bumpHeight, SubstanceColorUtilities.getAlphaColor(
2525
darkShadowColor, 0)));
2526
g2d.draw(topOutline);
2530
if (tabPlacement == TOP) {
2531
g2d.setColor(darkShadowColor);
2532
g2d.drawLine(x, y + ribbonDelta, x + w - 1, y + ribbonDelta);
2533
g2d.setColor(this.highlight);
2534
// g2d.drawLine(x, y + 1 + ribbonDelta, x + w - 1, y + 1 +
2537
if (tabPlacement == LEFT) {
2538
g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x + 4
2539
* ribbonDelta, y, this.highlight));
2540
g2d.drawLine(x, y, x + 4 * ribbonDelta, y);
2542
if (tabPlacement == RIGHT) {
2543
g2d.setPaint(new GradientPaint(x + w - 1 - 4 * ribbonDelta, y,
2544
this.highlight, x + w - 1, y, darkShadowColor));
2545
g2d.drawLine(x + w - 1 - 4 * ribbonDelta, y, x + w - 1, y);
2553
public Rectangle getTabBounds(JTabbedPane pane, int i) {
2554
this.ensureCurrentLayout();
2555
Rectangle tabRect = new Rectangle();
2556
return this.getTabBounds(i, tabRect);
2560
// * Returns the previous state for the specified tab.
2562
// * @param tabIndex
2564
// * @return The previous state for the specified tab.
2566
// protected ComponentState getPrevTabState(int tabIndex) {
2567
// StateTransitionTracker tracker = this.stateTransitionMultiTracker
2568
// .getTracker(tabIndex);
2569
// if (tracker == null) {
2570
// return getTabState(tabIndex);
2572
// ComponentState fromTracker = tracker.getPrevModelState();
2573
// boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
2574
// return ComponentState.getState(isEnabled, fromTracker
2575
// .isFacetActive(AnimationFacet.ROLLOVER), fromTracker
2576
// .isFacetActive(AnimationFacet.SELECTION));
2580
protected StateTransitionTracker.ModelStateInfo getModelStateInfo(
2582
if (this.stateTransitionMultiTracker.size() == 0)
2584
StateTransitionTracker tracker = this.stateTransitionMultiTracker
2585
.getTracker(tabIndex);
2586
if (tracker == null) {
2589
return tracker.getModelStateInfo();
2594
* Returns the current state for the specified tab.
2598
* @return The current state for the specified tab.
2600
protected ComponentState getTabState(int tabIndex) {
2601
boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
2602
StateTransitionTracker tracker = this.stateTransitionMultiTracker
2603
.getTracker(tabIndex);
2604
if (tracker == null) {
2605
boolean isRollover = this.getRolloverTabIndex() == tabIndex;
2606
boolean isSelected = this.tabPane.getSelectedIndex() == tabIndex;
2607
return ComponentState.getState(isEnabled, isRollover, isSelected);
2609
ComponentState fromTracker = tracker.getModelStateInfo()
2610
.getCurrModelState();
2611
return ComponentState.getState(isEnabled, fromTracker
2612
.isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
2613
.isFacetActive(ComponentStateFacet.SELECTION));
2621
* javax.swing.plaf.basic.BasicTabbedPaneUI#paintText(java.awt.Graphics,
2622
* int, java.awt.Font, java.awt.FontMetrics, int, java.lang.String,
2623
* java.awt.Rectangle, boolean)
2626
protected void paintText(Graphics g, int tabPlacement, Font font,
2627
FontMetrics metrics, int tabIndex, String title,
2628
Rectangle textRect, boolean isSelected) {
2631
View v = this.getTextViewForTab(tabIndex);
2634
v.paint(g, textRect);
2637
int mnemIndex = this.tabPane.getDisplayedMnemonicIndexAt(tabIndex);
2638
StateTransitionTracker.ModelStateInfo modelStateInfo = this
2639
.getModelStateInfo(tabIndex);
2640
ComponentState currState = this.getTabState(tabIndex);
2642
// System.out.println("Tab " + title + ":" + currState);
2644
if (modelStateInfo != null) {
2645
Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
2646
.getStateContributionMap();
2647
SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
2648
.getColorScheme(tabPane, tabIndex,
2649
ColorSchemeAssociationKind.TAB, currState);
2650
if (currState.isDisabled() || (activeStates == null)
2651
|| (activeStates.size() == 1)) {
2652
fg = colorScheme.getForegroundColor();
2655
float aggrGreen = 0;
2658
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
2660
ComponentState activeState = activeEntry.getKey();
2661
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
2662
.getColorScheme(tabPane, tabIndex,
2663
ColorSchemeAssociationKind.TAB,
2665
Color schemeFg = scheme.getForegroundColor();
2666
float contribution = activeEntry.getValue()
2668
// // System.out.println("\t" + activeState + ":"
2669
// + contribution + ":" + scheme.getDisplayName()
2670
// + ":" + schemeFg);
2671
aggrRed += schemeFg.getRed() * contribution;
2672
aggrGreen += schemeFg.getGreen() * contribution;
2673
aggrBlue += schemeFg.getBlue() * contribution;
2675
fg = new Color((int) aggrRed, (int) aggrGreen,
2679
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
2680
.getColorScheme(tabPane, tabIndex,
2681
ColorSchemeAssociationKind.TAB, currState);
2682
fg = scheme.getForegroundColor();
2685
Graphics2D graphics = (Graphics2D) g.create();
2686
if (currState.isDisabled()) {
2687
Color bgFillColor = SubstanceColorUtilities
2688
.getBackgroundFillColor(this.tabPane);
2689
fg = SubstanceColorUtilities.getInterpolatedColor(fg,
2690
bgFillColor, SubstanceColorSchemeUtilities.getAlpha(
2691
this.tabPane.getComponentAt(tabIndex),
2694
graphics.clip(getTabRectangle(tabIndex));
2695
SubstanceTextUtilities.paintText(graphics, this.tabPane, textRect,
2696
title, mnemIndex, graphics.getFont(), fg, null);
2702
protected void paintIcon(Graphics g, int tabPlacement, int tabIndex,
2703
Icon icon, Rectangle iconRect, boolean isSelected) {
2707
if (SubstanceCoreUtilities.useThemedDefaultIcon(this.tabPane)) {
2708
ComponentState currState = this.getTabState(tabIndex);
2709
StateTransitionTracker tabTracker = stateTransitionMultiTracker
2710
.getTracker(tabIndex);
2712
if (tabTracker == null) {
2713
if (currState.isFacetActive(ComponentStateFacet.ROLLOVER)
2715
.isFacetActive(ComponentStateFacet.SELECTION)
2716
|| currState.isDisabled()) {
2717
// use the original (full color or disabled) icon
2718
super.paintIcon(g, tabPlacement, tabIndex, icon, iconRect,
2724
Icon themed = SubstanceCoreUtilities.getThemedIcon(this.tabPane,
2726
if (tabTracker == null) {
2727
super.paintIcon(g, tabPlacement, tabIndex, themed, iconRect,
2730
Graphics2D g2d = (Graphics2D) g.create();
2731
super.paintIcon(g2d, tabPlacement, tabIndex, icon, iconRect,
2734
.setComposite(LafWidgetUtilities
2738
.getFacetStrength(ComponentStateFacet.ROLLOVER),
2740
super.paintIcon(g2d, tabPlacement, tabIndex, themed, iconRect,
2745
super.paintIcon(g, tabPlacement, tabIndex, icon, iconRect,
2751
protected MouseListener createMouseListener() {
2756
* Extension point to allow horizontal orientation of left / right placed
2759
* @param tabPlacement
2761
* @return Indication whether the tabs in the specified placement should be
2764
protected boolean toRotateTabsOnPlacement(int tabPlacement) {
2765
Object rotateProperty = tabPane.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS);
2766
if (!(rotateProperty instanceof Boolean)) {
2767
rotateProperty = UIManager.get(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS);
2770
boolean rotate = (rotateProperty instanceof Boolean) ? (Boolean)rotateProperty : true;
2772
return rotate && ( (tabPlacement == SwingConstants.LEFT) || (tabPlacement == SwingConstants.RIGHT) );
2775
private StateTransitionTracker getTracker(final int tabIndex,
2776
boolean initialRollover, boolean initialSelected) {
2777
StateTransitionTracker tracker = stateTransitionMultiTracker
2778
.getTracker(tabIndex);
2779
if (tracker == null) {
2780
ButtonModel model = new DefaultButtonModel();
2781
model.setSelected(initialSelected);
2782
model.setRollover(initialRollover);
2783
tracker = new StateTransitionTracker(tabPane, model);
2784
tracker.registerModelListeners();
2785
tracker.setRepaintCallback(new RepaintCallback() {
2787
public TimelineCallback getRepaintCallback() {
2788
return new TabRepaintCallback(tabPane, tabIndex);
2791
stateTransitionMultiTracker.addTracker(tabIndex, tracker);
2796
private void trackTabModification(int tabIndex, Component tabComponent) {
2797
Timeline modifiedTimeline = new Timeline(tabPane);
2798
AnimationConfigurationManager.getInstance().configureModifiedTimeline(
2800
modifiedTimeline.addCallback(new TabRepaintCallback(tabPane, tabIndex));
2801
modifiedTimeline.playLoop(RepeatBehavior.REVERSE);
2802
modifiedTimelines.put(tabComponent, modifiedTimeline);
b'\\ No newline at end of file'