2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.ui
.tabs
.impl
;
18 import com
.intellij
.openapi
.Disposable
;
19 import com
.intellij
.openapi
.actionSystem
.*;
20 import com
.intellij
.openapi
.application
.ModalityState
;
21 import com
.intellij
.openapi
.project
.Project
;
22 import com
.intellij
.openapi
.ui
.ShadowAction
;
23 import com
.intellij
.openapi
.ui
.TestableUi
;
24 import com
.intellij
.openapi
.util
.*;
25 import com
.intellij
.openapi
.util
.registry
.Registry
;
26 import com
.intellij
.openapi
.wm
.*;
27 import com
.intellij
.openapi
.wm
.impl
.content
.GraphicsConfig
;
28 import com
.intellij
.ui
.CaptionPanel
;
29 import com
.intellij
.ui
.tabs
.*;
30 import com
.intellij
.ui
.tabs
.impl
.singleRow
.SingleRowLayout
;
31 import com
.intellij
.ui
.tabs
.impl
.singleRow
.SingleRowPassInfo
;
32 import com
.intellij
.ui
.tabs
.impl
.table
.TableLayout
;
33 import com
.intellij
.ui
.tabs
.impl
.table
.TablePassInfo
;
34 import com
.intellij
.util
.ui
.Animator
;
35 import com
.intellij
.util
.ui
.TimedDeadzone
;
36 import com
.intellij
.util
.ui
.UIUtil
;
37 import com
.intellij
.util
.ui
.update
.LazyUiDisposable
;
38 import org
.jetbrains
.annotations
.NonNls
;
39 import org
.jetbrains
.annotations
.NotNull
;
40 import org
.jetbrains
.annotations
.Nullable
;
43 import javax
.swing
.event
.PopupMenuEvent
;
44 import javax
.swing
.event
.PopupMenuListener
;
45 import javax
.swing
.plaf
.ComponentUI
;
47 import java
.awt
.event
.*;
48 import java
.awt
.geom
.Line2D
;
49 import java
.awt
.image
.BufferedImage
;
50 import java
.beans
.PropertyChangeEvent
;
51 import java
.beans
.PropertyChangeListener
;
53 import java
.util
.List
;
55 public class JBTabsImpl
extends JComponent
56 implements JBTabs
, PropertyChangeListener
, TimerListener
, DataProvider
, PopupMenuListener
, Disposable
, JBTabsPresentation
, TestableUi
{
58 static DataKey
<JBTabsImpl
> NAVIGATION_ACTIONS_KEY
= DataKey
.create("JBTabs");
60 ActionManager myActionManager
;
61 public final List
<TabInfo
> myVisibleInfos
= new ArrayList
<TabInfo
>();
62 private final Map
<TabInfo
, Integer
> myHiddenInfos
= new HashMap
<TabInfo
, Integer
>();
64 private TabInfo mySelectedInfo
;
65 public final Map
<TabInfo
, TabLabel
> myInfo2Label
= new HashMap
<TabInfo
, TabLabel
>();
66 public final Map
<TabInfo
, Toolbar
> myInfo2Toolbar
= new HashMap
<TabInfo
, Toolbar
>();
67 public Dimension myHeaderFitSize
;
69 private Insets myInnerInsets
= new Insets(0, 0, 0, 0);
71 private final List
<EventListener
> myTabMouseListeners
= new ArrayList
<EventListener
>();
72 private final List
<TabsListener
> myTabListeners
= new ArrayList
<TabsListener
>();
73 public boolean myFocused
;
75 private Getter
<ActionGroup
> myPopupGroup
;
76 private String myPopupPlace
;
79 DefaultActionGroup myNavigationActions
;
81 PopupMenuListener myPopupListener
;
82 JPopupMenu myActivePopup
;
84 public boolean myHorizontalSide
= true;
86 private boolean myStealthTabMode
= false;
88 private DataProvider myDataProvider
;
90 private final WeakHashMap
<Component
, Component
> myDeferredToRemove
= new WeakHashMap
<Component
, Component
>();
92 private final SingleRowLayout mySingleRowLayout
= new SingleRowLayout(this);
93 private final TableLayout myTableLayout
= new TableLayout(this);
96 private TabLayout myLayout
= mySingleRowLayout
;
97 LayoutPassInfo myLastLayoutPass
;
98 private TabInfo myLastPaintedSelection
;
100 public boolean myForcedRelayout
;
102 private UiDecorator myUiDecorator
;
103 static final UiDecorator ourDefaultDecorator
= new DefautDecorator();
105 private boolean myPaintFocus
;
107 private boolean myHideTabs
= false;
108 @Nullable private Project myProject
;
110 private boolean myRequestFocusOnLastFocusedComponent
= false;
111 private boolean myListenerAdded
;
112 final Set
<TabInfo
> myAttractions
= new HashSet
<TabInfo
>();
113 private Animator myAnimator
;
114 private List
<TabInfo
> myAllTabs
;
115 private boolean myPaintBlocked
;
116 private BufferedImage myImage
;
117 private IdeFocusManager myFocusManager
;
118 private boolean myAdjustBorders
= true;
120 boolean myAddNavigationGroup
= true;
122 private boolean myGhostsAlwaysVisible
= false;
123 private boolean myDisposed
;
124 private boolean myToDrawBorderIfTabsHidden
= true;
125 private Color myActiveTabFillIn
;
127 private boolean myTabLabelActionsAutoHide
;
129 private final TabActionsAutoHideListener myTabActionsAutoHideListener
= new TabActionsAutoHideListener();
130 private IdeGlassPane myGlassPane
;
131 @NonNls private static final String LAYOUT_DONE
= "Layout.done";
133 private TimedDeadzone
.Length myTabActionsMouseDeadzone
= TimedDeadzone
.DEFAULT
;
135 private long myRemoveDefferredRequest
;
136 private boolean myTestMode
;
138 private JBTabsPosition myPosition
= JBTabsPosition
.top
;
140 private final TabsBorder myBorder
= new TabsBorder(this);
141 private BaseNavigationAction myNextAction
;
142 private BaseNavigationAction myPrevAction
;
144 private boolean myWasEverShown
;
146 private boolean myTabDraggingEnabled
;
147 private DragHelper myDragHelper
;
148 private boolean myNavigationActionsEnabled
= true;
149 private boolean myUseBufferedPaint
= true;
151 public JBTabsImpl(@NotNull Project project
) {
152 this(project
, project
);
155 public JBTabsImpl(@NotNull Project project
, @NotNull Disposable parent
) {
156 this(project
, ActionManager
.getInstance(), IdeFocusManager
.getInstance(project
), parent
);
159 public JBTabsImpl(@Nullable Project project
, IdeFocusManager focusManager
, @NotNull Disposable parent
) {
160 this(project
, ActionManager
.getInstance(), focusManager
, parent
);
163 public JBTabsImpl(@Nullable Project project
, ActionManager actionManager
, IdeFocusManager focusManager
, @NotNull Disposable parent
) {
165 myActionManager
= actionManager
;
166 myFocusManager
= focusManager
!= null ? focusManager
: IdeFocusManager
.getGlobalInstance();
169 setPaintBorder(-1, -1, -1, -1);
171 Disposer
.register(parent
, this);
173 myNavigationActions
= new DefaultActionGroup();
175 if (myActionManager
!= null) {
176 myNextAction
= new SelectNextAction(this, myActionManager
);
177 myPrevAction
= new SelectPreviousAction(this, myActionManager
);
179 myNavigationActions
.add(myNextAction
);
180 myNavigationActions
.add(myPrevAction
);
183 setUiDecorator(null);
185 myPopupListener
= new PopupMenuListener() {
186 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
189 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
190 disposePopupListener();
193 public void popupMenuCanceled(final PopupMenuEvent e
) {
194 disposePopupListener();
198 addMouseListener(new MouseAdapter() {
199 public void mousePressed(final MouseEvent e
) {
200 if (mySingleRowLayout
.myLastSingRowLayout
!= null &&
201 mySingleRowLayout
.myLastSingRowLayout
.moreRect
!= null &&
202 mySingleRowLayout
.myLastSingRowLayout
.moreRect
.contains(e
.getPoint())) {
208 myAnimator
= new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
209 public void paintNow(final float frame
, final float totalFrames
, final float cycle
) {
210 repaintAttractions();
213 myAnimator
.setTakInitialDelay(false);
215 setFocusCycleRoot(true);
216 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
217 public Component
getDefaultComponent(final Container aContainer
) {
222 add(mySingleRowLayout
.myLeftGhost
);
223 add(mySingleRowLayout
.myRightGhost
);
226 new LazyUiDisposable
<JBTabsImpl
>(parent
, this, this, project
) {
227 protected void initialize(@NotNull Disposable parent
, @NotNull JBTabsImpl child
, @Nullable Project project
) {
230 Disposer
.register(child
, myAnimator
);
231 Disposer
.register(child
, new Disposable() {
232 public void dispose() {
238 final IdeGlassPane gp
= IdeGlassPaneUtil
.find(child
);
240 gp
.addMouseMotionPreprocessor(myTabActionsAutoHideListener
, child
);
244 UIUtil
.addAwtListener(new AWTEventListener() {
245 public void eventDispatched(final AWTEvent event
) {
246 if (mySingleRowLayout
.myMorePopup
!= null) return;
247 processFocusChange();
249 }, AWTEvent
.FOCUS_EVENT_MASK
, child
);
251 myDragHelper
= new DragHelper(child
);
252 myDragHelper
.start();
255 if (myProject
!= null && myFocusManager
== IdeFocusManager
.getGlobalInstance()) {
256 myFocusManager
= IdeFocusManager
.getInstance(myProject
);
263 public JBTabs
setNavigationActiondBinding(String prevActionId
, String nextActionId
) {
264 if (myNextAction
!= null) {
265 myNextAction
.reconnect(nextActionId
);
267 if (myPrevAction
!= null) {
268 myPrevAction
.reconnect(prevActionId
);
274 public JBTabs
setNavigationActionsEnabled(boolean enabled
) {
275 myNavigationActionsEnabled
= enabled
;
279 public final boolean isDisposed() {
283 public void dispose() {
285 mySelectedInfo
= null;
287 myAttractions
.clear();
288 myVisibleInfos
.clear();
289 myUiDecorator
= null;
291 myActivePopup
= null;
292 myInfo2Label
.clear();
293 myInfo2Toolbar
.clear();
294 myTabListeners
.clear();
297 void resetTabsCache() {
301 private void processFocusChange() {
302 Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
308 if (owner
== this || SwingUtilities
.isDescendingFrom(owner
, this)) {
316 private void repaintAttractions() {
317 boolean needsUpdate
= false;
318 for (TabInfo each
: myVisibleInfos
) {
319 TabLabel eachLabel
= myInfo2Label
.get(each
);
320 needsUpdate
|= eachLabel
.repaintAttraction();
324 relayout(true, false);
328 public void addNotify() {
333 public void removeNotify() {
334 super.removeNotify();
340 if (myGlassPane
!= null) {
341 myGlassPane
.removeMouseMotionPreprocessor(myTabActionsAutoHideListener
);
346 private void addTimerUpdate() {
347 if (myActionManager
!= null && !myListenerAdded
) {
348 myActionManager
.addTimerListener(500, this);
349 myListenerAdded
= true;
353 private void removeTimerUpdate() {
354 if (myActionManager
!= null && myListenerAdded
) {
355 myActionManager
.removeTimerListener(this);
356 myListenerAdded
= false;
360 void setTestMode(final boolean testMode
) {
361 myTestMode
= testMode
;
364 public void layoutComp(SingleRowPassInfo data
, int deltaX
, int deltaY
, int deltaWidth
, int deltaHeight
) {
365 if (data
.hToolbar
!= null) {
366 final int toolbarHeight
= data
.hToolbar
.getPreferredSize().height
;
367 final Rectangle compRect
= layoutComp(deltaX
, toolbarHeight
+ deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
368 layout(data
.hToolbar
, compRect
.x
, compRect
.y
- toolbarHeight
, compRect
.width
, toolbarHeight
);
370 else if (data
.vToolbar
!= null) {
371 final int toolbarWidth
= data
.vToolbar
.getPreferredSize().width
;
372 final Rectangle compRect
= layoutComp(toolbarWidth
+ deltaX
, deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
373 layout(data
.vToolbar
, compRect
.x
- toolbarWidth
, compRect
.y
, toolbarWidth
, compRect
.height
);
376 layoutComp(deltaX
, deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
380 class TabActionsAutoHideListener
extends MouseMotionAdapter
{
382 private TabLabel myCurrentOverLabel
;
383 private Point myLastOverPoint
;
386 public void mouseMoved(final MouseEvent e
) {
387 if (!myTabLabelActionsAutoHide
) return;
389 final Point point
= SwingUtilities
.convertPoint(e
.getComponent(), e
.getX(), e
.getY(), JBTabsImpl
.this);
390 myLastOverPoint
= point
;
394 void processMouseOver() {
395 if (!myTabLabelActionsAutoHide
) return;
397 if (myLastOverPoint
== null) return;
399 if (myLastOverPoint
.x
>= 0 && myLastOverPoint
.x
< getWidth() && myLastOverPoint
.y
> 0 && myLastOverPoint
.y
< getHeight()) {
400 final TabLabel label
= myInfo2Label
.get(_findInfo(myLastOverPoint
, true));
402 if (myCurrentOverLabel
!= null) {
403 myCurrentOverLabel
.toggleShowActions(false);
405 label
.toggleShowActions(true);
406 myCurrentOverLabel
= label
;
411 if (myCurrentOverLabel
!= null) {
412 myCurrentOverLabel
.toggleShowActions(false);
413 myCurrentOverLabel
= null;
419 public ModalityState
getModalityState() {
420 return ModalityState
.stateForComponent(this);
424 updateTabActions(false);
427 public void updateTabActions(final boolean validateNow
) {
428 final Ref
<Boolean
> changed
= new Ref
<Boolean
>(Boolean
.FALSE
);
429 for (final TabInfo eachInfo
: myInfo2Label
.keySet()) {
430 updateTab(new Runnable() {
432 final boolean changes
= myInfo2Label
.get(eachInfo
).updateTabActions();
433 changed
.set(changed
.get().booleanValue() || changes
);
438 if (changed
.get().booleanValue()) {
441 paintImmediately(0, 0, getWidth(), getHeight());
446 private void showMorePopup(final MouseEvent e
) {
447 mySingleRowLayout
.myMorePopup
= new JPopupMenu();
448 for (final TabInfo each
: myVisibleInfos
) {
449 final JCheckBoxMenuItem item
= new JCheckBoxMenuItem(each
.getText());
450 mySingleRowLayout
.myMorePopup
.add(item
);
451 if (getSelectedInfo() == each
) {
452 item
.setSelected(true);
454 item
.addActionListener(new ActionListener() {
455 public void actionPerformed(final ActionEvent e
) {
461 mySingleRowLayout
.myMorePopup
.addPopupMenuListener(new PopupMenuListener() {
462 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
465 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
466 mySingleRowLayout
.myMorePopup
= null;
469 public void popupMenuCanceled(final PopupMenuEvent e
) {
470 mySingleRowLayout
.myMorePopup
= null;
474 mySingleRowLayout
.myMorePopup
.show(this, e
.getX(), e
.getY());
478 private JComponent
getToFocus() {
479 final TabInfo info
= getSelectedInfo();
481 if (info
== null) return null;
483 JComponent toFocus
= null;
485 if (isRequestFocusOnLastFocusedComponent() && info
.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
486 toFocus
= info
.getLastFocusOwner();
489 if (toFocus
== null && info
.getPreferredFocusableComponent() == null) {
494 if (toFocus
== null) {
495 toFocus
= info
.getPreferredFocusableComponent();
496 final JComponent policyToFocus
= myFocusManager
.getFocusTargetFor(toFocus
);
497 if (policyToFocus
!= null) {
498 toFocus
= policyToFocus
;
505 public void requestFocus() {
506 final JComponent toFocus
= getToFocus();
507 if (toFocus
!= null) {
508 toFocus
.requestFocus();
511 super.requestFocus();
515 public boolean requestFocusInWindow() {
516 final JComponent toFocus
= getToFocus();
517 if (toFocus
!= null) {
518 return toFocus
.requestFocusInWindow();
521 return super.requestFocusInWindow();
525 private JBTabsImpl
findTabs(Component c
) {
526 Component eachParent
= c
;
527 while (eachParent
!= null) {
528 if (eachParent
instanceof JBTabsImpl
) {
529 return (JBTabsImpl
)eachParent
;
531 eachParent
= eachParent
.getParent();
539 public TabInfo
addTab(TabInfo info
, int index
) {
540 if (getTabs().contains(info
)) {
541 return getTabs().get(getTabs().indexOf(info
));
544 info
.getChangeSupport().addPropertyChangeListener(this);
545 final TabLabel label
= new TabLabel(this, info
);
546 myInfo2Label
.put(info
, label
);
549 myVisibleInfos
.add(info
);
551 else if (index
> myVisibleInfos
.size() - 1) {
552 myVisibleInfos
.add(info
);
555 myVisibleInfos
.add(index
, info
);
563 updateSideComponent(info
);
564 updateTabActions(info
);
570 updateAll(false, false);
572 if (info
.isHidden()) {
576 if (getTabCount() == 1) {
577 fireBeforeSelectionChanged(null, info
);
578 fireSelectionChanged(null, info
);
586 public TabInfo
addTab(TabInfo info
) {
587 return addTab(info
, -1);
590 public ActionGroup
getPopupGroup() {
591 return myPopupGroup
!= null ? myPopupGroup
.get() : null;
594 public String
getPopupPlace() {
598 public JBTabs
setPopupGroup(@NotNull final ActionGroup popupGroup
, @NotNull String place
, final boolean addNavigationGroup
) {
599 return setPopupGroup(new Getter
<ActionGroup
>() {
600 public ActionGroup
get() {
603 }, place
, addNavigationGroup
);
606 public JBTabs
setPopupGroup(@NotNull final Getter
<ActionGroup
> popupGroup
,
607 @NotNull final String place
,
608 final boolean addNavigationGroup
) {
609 myPopupGroup
= popupGroup
;
610 myPopupPlace
= place
;
611 myAddNavigationGroup
= addNavigationGroup
;
615 private void updateAll(final boolean forcedRelayout
, final boolean now
) {
616 mySelectedInfo
= getSelectedInfo();
617 updateContainer(forcedRelayout
, now
);
620 updateTabActions(false);
624 private boolean isMyChildIsFocusedNow() {
625 final Component owner
= getFocusOwner();
626 if (owner
== null) return false;
629 if (mySelectedInfo
!= null) {
630 if (!SwingUtilities
.isDescendingFrom(owner
, mySelectedInfo
.getComponent())) return false;
633 return SwingUtilities
.isDescendingFrom(owner
, this);
637 private static JComponent
getFocusOwner() {
638 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
639 return (JComponent
)(owner
instanceof JComponent ? owner
: null);
642 public ActionCallback
select(@NotNull TabInfo info
, boolean requestFocus
) {
643 return _setSelected(info
, requestFocus
);
646 private ActionCallback
_setSelected(final TabInfo info
, final boolean requestFocus
) {
647 if (mySelectedInfo
!= null && mySelectedInfo
.equals(info
)) {
649 return new ActionCallback
.Done();
652 return requestFocus(getToFocus());
656 if (myRequestFocusOnLastFocusedComponent
&& mySelectedInfo
!= null) {
657 if (isMyChildIsFocusedNow()) {
658 mySelectedInfo
.setLastFocusOwner(getFocusOwner());
662 TabInfo oldInfo
= mySelectedInfo
;
663 mySelectedInfo
= info
;
664 final TabInfo newInfo
= getSelectedInfo();
666 fireBeforeSelectionChanged(oldInfo
, newInfo
);
668 updateContainer(false, true);
670 fireSelectionChanged(oldInfo
, newInfo
);
673 final JComponent toFocus
= getToFocus();
674 if (myProject
!= null && toFocus
!= null) {
675 final ActionCallback result
= new ActionCallback();
676 requestFocus(toFocus
).doWhenProcessed(new Runnable() {
679 result
.setRejected();
682 removeDeferred().notifyWhenDone(result
);
690 return removeDeferred();
694 return removeDeferred();
698 private void fireBeforeSelectionChanged(TabInfo oldInfo
, TabInfo newInfo
) {
699 if (oldInfo
!= newInfo
) {
700 for (TabsListener eachListener
: myTabListeners
) {
701 eachListener
.beforeSelectionChanged(oldInfo
, newInfo
);
706 private void fireSelectionChanged(TabInfo oldInfo
, TabInfo newInfo
) {
707 if (oldInfo
!= newInfo
) {
708 for (TabsListener eachListener
: myTabListeners
) {
709 if (eachListener
!= null) {
710 eachListener
.selectionChanged(oldInfo
, newInfo
);
716 private ActionCallback
requestFocus(final JComponent toFocus
) {
717 if (toFocus
== null) return new ActionCallback
.Done();
720 toFocus
.requestFocus();
721 return new ActionCallback
.Done();
724 return myFocusManager
.requestFocus(new FocusCommand
.ByComponent(toFocus
), true);
727 private ActionCallback
removeDeferred() {
728 final ActionCallback callback
= new ActionCallback();
730 final long executionRequest
= ++myRemoveDefferredRequest
;
732 final Runnable onDone
= new Runnable() {
734 if (myRemoveDefferredRequest
== executionRequest
) {
742 myFocusManager
.doWhenFocusSettlesDown(onDone
);
747 private void queueForRemove(Component c
) {
748 if (c
instanceof JComponent
) {
749 addToDeferredRemove(c
);
756 private void unqueueFromRemove(Component c
) {
757 myDeferredToRemove
.remove(c
);
760 private void removeDeferredNow() {
761 for (Component each
: myDeferredToRemove
.keySet()) {
762 if (each
!= null && each
.getParent() == this) {
766 myDeferredToRemove
.clear();
769 private void printRemoveInfo(final Component each
) {
770 TabInfo removingInfo
= null;
771 final List
<TabInfo
> all
= getTabs();
772 for (TabInfo eachInfo
: all
) {
773 if (eachInfo
.getComponent() == each
) {
774 removingInfo
= eachInfo
;
779 //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each));
782 public void propertyChange(final PropertyChangeEvent evt
) {
783 final TabInfo tabInfo
= (TabInfo
)evt
.getSource();
784 if (TabInfo
.ACTION_GROUP
.equals(evt
.getPropertyName())) {
785 updateSideComponent(tabInfo
);
786 relayout(false, false);
788 else if (TabInfo
.COMPONENT
.equals(evt
.getPropertyName())) {
789 relayout(true, false);
791 else if (TabInfo
.TEXT
.equals(evt
.getPropertyName())) {
794 else if (TabInfo
.ICON
.equals(evt
.getPropertyName())) {
797 else if (TabInfo
.TAB_COLOR
.equals(evt
.getPropertyName())) {
798 updateColor(tabInfo
);
800 else if (TabInfo
.ALERT_STATUS
.equals(evt
.getPropertyName())) {
801 boolean start
= ((Boolean
)evt
.getNewValue()).booleanValue();
802 updateAttraction(tabInfo
, start
);
804 else if (TabInfo
.TAB_ACTION_GROUP
.equals(evt
.getPropertyName())) {
805 updateTabActions(tabInfo
);
806 relayout(false, false);
808 else if (TabInfo
.HIDDEN
.equals(evt
.getPropertyName())) {
810 relayout(false, false);
812 else if (TabInfo
.ENABLED
.equals(evt
.getPropertyName())) {
817 private void updateEnabling() {
818 final List
<TabInfo
> all
= getTabs();
819 for (TabInfo each
: all
) {
820 final TabLabel eachLabel
= myInfo2Label
.get(each
);
821 eachLabel
.setTabEnabled(each
.isEnabled());
824 final TabInfo selected
= getSelectedInfo();
825 if (selected
!= null && !selected
.isEnabled()) {
826 final TabInfo toSelect
= getToSelectOnRemoveOf(selected
);
827 if (toSelect
!= null) {
828 select(toSelect
, myFocusManager
.getFocusedDescendantFor(this) != null);
833 private void updateHiding() {
834 boolean update
= false;
836 Iterator
<TabInfo
> visible
= myVisibleInfos
.iterator();
837 while (visible
.hasNext()) {
838 TabInfo each
= visible
.next();
839 if (each
.isHidden() && !myHiddenInfos
.containsKey(each
)) {
840 myHiddenInfos
.put(each
, myVisibleInfos
.indexOf(each
));
847 Iterator
<TabInfo
> hidden
= myHiddenInfos
.keySet().iterator();
848 while (hidden
.hasNext()) {
849 TabInfo each
= hidden
.next();
850 if (!each
.isHidden() && myHiddenInfos
.containsKey(each
)) {
851 myVisibleInfos
.add(getIndexInVisibleArray(each
), each
);
860 if (mySelectedInfo
!= null && myHiddenInfos
.containsKey(mySelectedInfo
)) {
861 mySelectedInfo
= getToSelectOnRemoveOf(mySelectedInfo
);
863 updateAll(true, false);
867 private int getIndexInVisibleArray(TabInfo each
) {
868 Integer index
= myHiddenInfos
.get(each
);
870 index
= Integer
.valueOf(myVisibleInfos
.size());
873 if (index
> myVisibleInfos
.size()) {
874 index
= myVisibleInfos
.size();
877 if (index
.intValue() < 0) {
881 return index
.intValue();
884 private void updateIcon(final TabInfo tabInfo
) {
885 updateTab(new Runnable() {
887 myInfo2Label
.get(tabInfo
).setIcon(tabInfo
.getIcon());
892 private void updateColor(final TabInfo tabInfo
) {
893 myInfo2Label
.get(tabInfo
).setInactiveStateImage(null);
895 updateTab(new Runnable() {
902 private void updateTab(Runnable update
, TabInfo info
) {
903 final TabLabel label
= myInfo2Label
.get(info
);
904 final Dimension before
= label
.getPreferredSize();
906 if (label
.getRootPane() != null) {
907 final Dimension after
= label
.getPreferredSize();
908 if (after
.equals(before
)) {
912 revalidateAndRepaint(false);
917 void revalidateAndRepaint(final boolean layoutNow
) {
919 if (myVisibleInfos
.isEmpty()) {
921 final Component nonOpaque
= UIUtil
.findUltimateParent(this);
922 if (nonOpaque
!= null && getParent() != null) {
923 final Rectangle toRepaint
= SwingUtilities
.convertRectangle(getParent(), getBounds(), nonOpaque
);
924 nonOpaque
.repaint(toRepaint
.x
, toRepaint
.y
, toRepaint
.width
, toRepaint
.height
);
942 private void updateAttraction(final TabInfo tabInfo
, boolean start
) {
944 myAttractions
.add(tabInfo
);
947 myAttractions
.remove(tabInfo
);
948 tabInfo
.setBlinkCount(0);
951 if (start
&& !myAnimator
.isRunning()) {
954 else if (!start
&& myAttractions
.isEmpty()) {
955 myAnimator
.suspend();
956 repaintAttractions();
960 private void updateText(final TabInfo tabInfo
) {
961 updateTab(new Runnable() {
963 final TabLabel label
= myInfo2Label
.get(tabInfo
);
964 label
.setText(tabInfo
.getColoredText());
965 label
.setToolTipText(tabInfo
.getTooltipText());
970 private void updateSideComponent(final TabInfo tabInfo
) {
971 final Toolbar old
= myInfo2Toolbar
.get(tabInfo
);
976 final Toolbar toolbar
= createToolbarComponent(tabInfo
);
977 myInfo2Toolbar
.put(tabInfo
, toolbar
);
981 private void updateTabActions(final TabInfo info
) {
982 myInfo2Label
.get(info
).setTabActions(info
.getTabLabelActions());
986 public TabInfo
getSelectedInfo() {
987 if (!myVisibleInfos
.contains(mySelectedInfo
)) {
988 mySelectedInfo
= null;
990 return mySelectedInfo
!= null ? mySelectedInfo
: !myVisibleInfos
.isEmpty() ? myVisibleInfos
.get(0) : null;
994 private TabInfo
getToSelectOnRemoveOf(TabInfo info
) {
995 if (!myVisibleInfos
.contains(info
)) return null;
996 if (mySelectedInfo
!= info
) return null;
998 if (myVisibleInfos
.size() == 1) return null;
1000 int index
= myVisibleInfos
.indexOf(info
);
1002 TabInfo result
= null;
1004 result
= findEnabledBackward(index
- 1);
1007 if (result
== null) {
1008 result
= findEnabledForward(index
+ 1);
1014 private TabInfo
findEnabledForward(int from
) {
1016 while (index
< myVisibleInfos
.size() && index
>= 0) {
1017 final TabInfo each
= myVisibleInfos
.get(index
);
1018 if (each
.isEnabled()) return each
;
1025 private TabInfo
findEnabledBackward(int from
) {
1027 while (index
>= 0 && from
< myVisibleInfos
.size()) {
1028 final TabInfo each
= myVisibleInfos
.get(index
);
1029 if (each
.isEnabled()) return each
;
1036 protected Toolbar
createToolbarComponent(final TabInfo tabInfo
) {
1037 return new Toolbar(this, tabInfo
);
1041 public TabInfo
getTabAt(final int tabIndex
) {
1042 return getTabs().get(tabIndex
);
1046 public List
<TabInfo
> getTabs() {
1047 if (myAllTabs
!= null) return myAllTabs
;
1049 ArrayList
<TabInfo
> result
= new ArrayList
<TabInfo
>();
1050 result
.addAll(myVisibleInfos
);
1052 for (TabInfo each
: myHiddenInfos
.keySet()) {
1053 result
.add(getIndexInVisibleArray(each
), each
);
1061 public TabInfo
getTargetInfo() {
1062 return myPopupInfo
!= null ? myPopupInfo
: getSelectedInfo();
1065 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
1068 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
1072 public void popupMenuCanceled(final PopupMenuEvent e
) {
1076 private void resetPopup() {
1077 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
1078 SwingUtilities
.invokeLater(new Runnable() {
1085 public void setPaintBlocked(boolean blocked
, final boolean takeSnapshot
) {
1086 if (blocked
&& !myPaintBlocked
) {
1088 myImage
= new BufferedImage(getWidth(), getHeight(), BufferedImage
.TYPE_INT_ARGB
);
1089 final Graphics2D g
= myImage
.createGraphics();
1095 myPaintBlocked
= blocked
;
1097 if (!myPaintBlocked
) {
1098 if (myImage
!= null) {
1108 private void addToDeferredRemove(final Component c
) {
1109 if (!myDeferredToRemove
.containsKey(c
)) {
1110 myDeferredToRemove
.put(c
, c
);
1114 public boolean isToDrawBorderIfTabsHidden() {
1115 return myToDrawBorderIfTabsHidden
;
1119 public JBTabsPresentation
setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden
) {
1120 myToDrawBorderIfTabsHidden
= toDrawBorderIfTabsHidden
;
1125 public JBTabs
getJBTabs() {
1129 public static class Toolbar
extends JPanel
{
1130 private final JBTabsImpl myTabs
;
1131 private final TabInfo myInfo
;
1133 public Toolbar(JBTabsImpl tabs
, TabInfo info
) {
1137 setLayout(new BorderLayout());
1139 final ActionGroup group
= info
.getGroup();
1140 final JComponent side
= info
.getSideComponent();
1142 if (group
!= null && myTabs
.myActionManager
!= null) {
1143 final String place
= info
.getPlace();
1144 ActionToolbar toolbar
=
1145 myTabs
.myActionManager
.createActionToolbar(place
!= null ? place
: ActionPlaces
.UNKNOWN
, group
, myTabs
.myHorizontalSide
);
1146 toolbar
.setTargetComponent(info
.getActionsContextComponent());
1147 final JComponent actionToolbar
= toolbar
.getComponent();
1148 add(actionToolbar
, BorderLayout
.CENTER
);
1152 if (group
!= null) {
1153 add(side
, BorderLayout
.EAST
);
1156 add(side
, BorderLayout
.CENTER
);
1161 public boolean isEmpty() {
1162 return getComponentCount() == 0;
1167 public void doLayout() {
1169 myHeaderFitSize
= computeHeaderFitSize();
1171 final Collection
<TabLabel
> labels
= myInfo2Label
.values();
1172 for (TabLabel each
: labels
) {
1173 each
.setTabActionsAutoHide(myTabLabelActionsAutoHide
);
1177 if (isSingleRow()) {
1178 myLastLayoutPass
= mySingleRowLayout
.layoutSingleRow();
1179 myTableLayout
.myLastTableLayout
= null;
1182 myLastLayoutPass
= myTableLayout
.layoutTable();
1183 mySingleRowLayout
.myLastSingRowLayout
= null;
1186 if (isStealthModeEffective() && !isHideTabs()) {
1187 final TabLabel label
= getSelectedLabel();
1188 final Rectangle bounds
= label
.getBounds();
1189 final Insets insets
= getLayoutInsets();
1190 layout(label
, insets
.left
, bounds
.y
, getWidth() - insets
.right
- insets
.left
, bounds
.height
);
1194 moveDraggedTabLabel();
1196 myTabActionsAutoHideListener
.processMouseOver();
1199 myForcedRelayout
= false;
1202 applyResetComponents();
1205 void moveDraggedTabLabel() {
1206 if (myDragHelper
!= null && myDragHelper
.myDragRec
!= null) {
1207 final TabLabel selectedLabel
= myInfo2Label
.get(getSelectedInfo());
1208 if (selectedLabel
!= null) {
1209 final Rectangle bounds
= selectedLabel
.getBounds();
1210 if (isHorizontalTabs()) {
1211 selectedLabel
.setBounds(myDragHelper
.myDragRec
.x
, bounds
.y
, bounds
.width
, bounds
.height
);
1214 selectedLabel
.setBounds(bounds
.x
, myDragHelper
.myDragRec
.y
, bounds
.width
, bounds
.height
);
1220 private Dimension
computeHeaderFitSize() {
1221 final Max max
= computeMaxSize();
1223 if (myPosition
== JBTabsPosition
.top
|| myPosition
== JBTabsPosition
.bottom
) {
1224 return new Dimension(getSize().width
, myHorizontalSide ? Math
.max(max
.myLabel
.height
, max
.myToolbar
.height
) : max
.myLabel
.height
);
1227 return new Dimension(max
.myLabel
.width
+ (myHorizontalSide ?
0 : max
.myToolbar
.width
), getSize().height
);
1231 public Rectangle
layoutComp(int componentX
, int componentY
, final JComponent comp
, int deltaWidth
, int deltaHeight
) {
1232 final Insets insets
= getLayoutInsets();
1234 final Insets border
= isHideTabs() ?
new Insets(0, 0, 0, 0) : myBorder
.getEffectiveBorder();
1235 final boolean noTabsVisible
= isStealthModeEffective() || isHideTabs();
1237 if (noTabsVisible
) {
1238 border
.top
= getBorder(-1);
1239 border
.bottom
= getBorder(-1);
1240 border
.left
= getBorder(-1);
1241 border
.right
= getBorder(-1);
1244 final Insets inner
= getInnerInsets();
1245 border
.top
+= inner
.top
;
1246 border
.bottom
+= inner
.bottom
;
1247 border
.left
+= inner
.left
;
1248 border
.right
+= inner
.right
;
1251 int x
= insets
.left
+ componentX
+ border
.left
;
1252 int y
= insets
.top
+ componentY
+ border
.top
;
1253 int width
= getWidth() - insets
.left
- insets
.right
- componentX
- border
.left
- border
.right
;
1254 int height
= getHeight() - insets
.top
- insets
.bottom
- componentY
- border
.top
- border
.bottom
;
1256 if (!noTabsVisible
) {
1257 width
+= deltaWidth
;
1258 height
+= deltaHeight
;
1261 return layout(comp
, x
, y
, width
, height
);
1265 public JBTabsPresentation
setInnerInsets(final Insets innerInsets
) {
1266 myInnerInsets
= innerInsets
;
1270 public Insets
getInnerInsets() {
1271 return myInnerInsets
;
1274 public Insets
getLayoutInsets() {
1275 Insets insets
= getInsets();
1276 if (insets
== null) {
1277 insets
= new Insets(0, 0, 0, 0);
1282 private int fixInset(int inset
, int addin
) {
1283 return inset
+ addin
;
1287 public int getToolbarInset() {
1288 return getArcSize() + 1;
1291 public void resetLayout(boolean resetLabels
) {
1293 mySingleRowLayout
.myLeftGhost
.reset();
1294 mySingleRowLayout
.myRightGhost
.reset();
1297 for (TabInfo each
: myVisibleInfos
) {
1298 reset(each
, resetLabels
);
1301 for (TabInfo each
: myHiddenInfos
.keySet()) {
1302 reset(each
, resetLabels
);
1305 for (Component eachDeferred
: myDeferredToRemove
.keySet()) {
1306 resetLayout((JComponent
)eachDeferred
);
1310 private void reset(final TabInfo each
, final boolean resetLabels
) {
1311 final JComponent c
= each
.getComponent();
1316 resetLayout(myInfo2Toolbar
.get(each
));
1319 resetLayout(myInfo2Label
.get(each
));
1324 private int getArcSize() {
1328 public int getGhostTabLength() {
1333 protected void paintComponent(final Graphics g
) {
1334 super.paintComponent(g
);
1336 if (myVisibleInfos
.isEmpty()) return;
1338 Graphics2D g2d
= (Graphics2D
)g
;
1340 final GraphicsConfig config
= new GraphicsConfig(g2d
);
1341 config
.setAntialiasing(true);
1344 g2d
.setColor(getBackground());
1345 final Rectangle clip
= g2d
.getClipBounds();
1346 g2d
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1348 final TabInfo selected
= getSelectedInfo();
1350 boolean leftGhostExists
= isSingleRow();
1351 boolean rightGhostExists
= isSingleRow();
1353 if (!isStealthModeEffective() && !isHideTabs()) {
1354 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.lastGhostVisible
) {
1355 paintLastGhost(g2d
);
1359 paintNonSelectedTabs(g2d
, leftGhostExists
);
1361 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.firstGhostVisible
) {
1362 paintFirstGhost(g2d
);
1367 config
.setAntialiasing(false);
1369 if (isSideComponentVertical()) {
1370 Toolbar toolbarComp
= myInfo2Toolbar
.get(mySelectedInfo
);
1371 if (toolbarComp
!= null && !toolbarComp
.isEmpty()) {
1372 Rectangle toolBounds
= toolbarComp
.getBounds();
1373 g2d
.setColor(CaptionPanel
.CNT_ACTIVE_COLOR
);
1374 g2d
.drawLine((int)toolBounds
.getMaxX(), toolBounds
.y
, (int)toolBounds
.getMaxX(), (int)toolBounds
.getMaxY() - 1);
1381 private Color
getActiveTabColor(final Color c
) {
1382 final TabInfo info
= getSelectedInfo();
1387 final Color tabColor
= info
.getTabColor();
1388 return tabColor
== null ? c
: tabColor
;
1391 private void paintSelectionAndBorder(Graphics2D g2d
) {
1392 if (getSelectedLabel() == null) return;
1394 final ShapeInfo shapeInfo
= computeSelectedLabelShape();
1395 if (!isHideTabs()) {
1396 g2d
.setColor(getBackground());
1397 g2d
.fill(shapeInfo
.fillPath
.getShape());
1401 int paintTopY
= shapeInfo
.labelTopY
;
1402 int paintBottomY
= shapeInfo
.labelBottomY
;
1403 final boolean paintFocused
= myPaintFocus
&& (myFocused
|| myActivePopup
!= null);
1404 Color bgPreFill
= null;
1406 final Color bgColor
= getActiveTabColor(getActiveTabFillIn());
1407 if (bgColor
== null) {
1408 shapeInfo
.from
= UIUtil
.getFocusedFillColor();
1409 shapeInfo
.to
= UIUtil
.getFocusedFillColor();
1412 bgPreFill
= bgColor
;
1414 paintBottomY
= shapeInfo
.labelTopY
+ shapeInfo
.labelPath
.deltaY(getArcSize() - 2);
1415 shapeInfo
.from
= UIUtil
.toAlpha(UIUtil
.getFocusedFillColor(), alpha
);
1416 shapeInfo
.to
= UIUtil
.toAlpha(getActiveTabFillIn(), alpha
);
1420 final Color bgColor
= getActiveTabColor(getActiveTabFillIn());
1421 if (isPaintFocus()) {
1422 if (bgColor
== null) {
1424 shapeInfo
.from
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound().brighter(), alpha
);
1425 shapeInfo
.to
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound(), alpha
);
1429 shapeInfo
.from
= UIUtil
.toAlpha(bgColor
, alpha
);
1430 shapeInfo
.to
= UIUtil
.toAlpha(bgColor
, alpha
);
1435 final Color tabColor
= getActiveTabColor(null);
1436 shapeInfo
.from
= UIUtil
.toAlpha(tabColor
== null ? Color
.white
: tabColor
, alpha
);
1437 shapeInfo
.to
= UIUtil
.toAlpha(tabColor
== null ? Color
.white
: tabColor
, alpha
);
1441 if (!isHideTabs()) {
1442 if (bgPreFill
!= null) {
1443 g2d
.setColor(bgPreFill
);
1444 g2d
.fill(shapeInfo
.fillPath
.getShape());
1447 final Line2D
.Float gradientLine
=
1448 shapeInfo
.fillPath
.transformLine(shapeInfo
.fillPath
.getX(), paintTopY
, shapeInfo
.fillPath
.getX(), paintBottomY
);
1451 g2d
.setPaint(new GradientPaint((float)gradientLine
.getX1(), (float)gradientLine
.getY1(),
1452 shapeInfo
.fillPath
.transformY1(shapeInfo
.from
, shapeInfo
.to
), (float)gradientLine
.getX2(),
1453 (float)gradientLine
.getY2(), shapeInfo
.fillPath
.transformY1(shapeInfo
.to
, shapeInfo
.from
)));
1454 g2d
.fill(shapeInfo
.fillPath
.getShape());
1457 final Color tabColor
= getActiveTabColor(null);
1458 Color borderColor
= tabColor
== null ? UIUtil
.getBoundsColor(paintFocused
) : tabColor
.darker();
1459 g2d
.setColor(borderColor
);
1461 if (!isHideTabs()) {
1462 g2d
.draw(shapeInfo
.path
.getShape());
1465 paintBorder(g2d
, shapeInfo
, borderColor
);
1468 private ShapeInfo
computeSelectedLabelShape() {
1469 final ShapeInfo shape
= new ShapeInfo();
1471 shape
.path
= getEffectiveLayout().createShapeTransform(getSize());
1472 shape
.insets
= shape
.path
.transformInsets(getLayoutInsets());
1473 shape
.labelPath
= shape
.path
.createTransform(getSelectedLabel().getBounds());
1475 shape
.labelBottomY
= shape
.labelPath
.getMaxY() + shape
.labelPath
.deltaY(1);
1476 shape
.labelTopY
= shape
.labelPath
.getY();
1477 shape
.labelLeftX
= shape
.labelPath
.getX();
1478 shape
.labelRightX
= shape
.labelPath
.getX() + shape
.labelPath
.deltaX(shape
.labelPath
.getWidth());
1480 shape
.path
.moveTo(shape
.insets
.left
, shape
.labelBottomY
);
1481 shape
.path
.lineTo(shape
.labelLeftX
, shape
.labelBottomY
);
1482 shape
.path
.lineTo(shape
.labelLeftX
, shape
.labelTopY
+ shape
.labelPath
.deltaY(getArcSize()));
1483 shape
.path
.quadTo(shape
.labelLeftX
, shape
.labelTopY
, shape
.labelLeftX
+ shape
.labelPath
.deltaX(getArcSize()), shape
.labelTopY
);
1485 int lastX
= shape
.path
.getWidth() - shape
.path
.deltaX(shape
.insets
.right
+ 1);
1487 if (isStealthModeEffective()) {
1488 shape
.path
.lineTo(lastX
- shape
.path
.deltaX(getArcSize()), shape
.labelTopY
);
1489 shape
.path
.quadTo(lastX
, shape
.labelTopY
, lastX
, shape
.labelTopY
+ shape
.path
.deltaY(getArcSize()));
1490 shape
.path
.lineTo(lastX
, shape
.labelBottomY
);
1493 shape
.path
.lineTo(shape
.labelRightX
- shape
.path
.deltaX(getArcSize()), shape
.labelTopY
);
1494 shape
.path
.quadTo(shape
.labelRightX
, shape
.labelTopY
, shape
.labelRightX
, shape
.labelTopY
+ shape
.path
.deltaY(getArcSize()));
1495 if (myLastLayoutPass
.hasCurveSpaceFor(getSelectedInfo())) {
1496 shape
.path
.lineTo(shape
.labelRightX
, shape
.labelBottomY
- shape
.path
.deltaY(getArcSize()));
1497 shape
.path
.quadTo(shape
.labelRightX
, shape
.labelBottomY
, shape
.labelRightX
+ shape
.path
.deltaX(getArcSize()), shape
.labelBottomY
);
1500 shape
.path
.lineTo(shape
.labelRightX
, shape
.labelBottomY
);
1504 shape
.path
.lineTo(lastX
, shape
.labelBottomY
);
1506 if (isStealthModeEffective()) {
1507 shape
.path
.closePath();
1510 shape
.fillPath
= shape
.path
.copy();
1511 if (!isHideTabs()) {
1512 shape
.fillPath
.lineTo(lastX
, shape
.labelBottomY
+ shape
.fillPath
.deltaY(1));
1513 shape
.fillPath
.lineTo(shape
.labelLeftX
, shape
.labelBottomY
+ shape
.fillPath
.deltaY(1));
1514 shape
.fillPath
.closePath();
1519 private TabLabel
getSelectedLabel() {
1520 return myInfo2Label
.get(getSelectedInfo());
1523 static class ShapeInfo
{
1524 ShapeTransform path
;
1525 ShapeTransform fillPath
;
1526 ShapeTransform labelPath
;
1537 private void paintFirstGhost(Graphics2D g2d
) {
1538 final ShapeTransform path
= getEffectiveLayout().createShapeTransform(mySingleRowLayout
.myLastSingRowLayout
.firstGhost
);
1540 int topX
= path
.getX() + path
.deltaX(getCurveArc());
1541 int topY
= path
.getY() + path
.deltaY(getSelectionTabVShift());
1542 int bottomX
= path
.getMaxX() + path
.deltaX(1);
1543 int bottomY
= path
.getMaxY() + path
.deltaY(1);
1545 path
.moveTo(topX
, topY
);
1547 final boolean isLeftFromSelection
= mySingleRowLayout
.myLastSingRowLayout
.toLayout
.indexOf(getSelectedInfo()) == 0;
1549 if (isLeftFromSelection
) {
1550 path
.lineTo(bottomX
, topY
);
1553 path
.lineTo(bottomX
- getArcSize(), topY
);
1554 path
.quadTo(bottomX
, topY
, bottomX
, topY
+ path
.deltaY(getArcSize()));
1557 path
.lineTo(bottomX
, bottomY
);
1558 path
.lineTo(topX
, bottomY
);
1560 path
.quadTo(topX
- path
.deltaX(getCurveArc() * 2 - 1), bottomY
- path
.deltaY(Math
.abs(bottomY
- topY
) / 4), topX
,
1561 bottomY
- path
.deltaY(Math
.abs(bottomY
- topY
) / 2));
1563 path
.quadTo(topX
+ path
.deltaX(getCurveArc() - 1), topY
+ path
.deltaY(Math
.abs(bottomY
- topY
) / 4), topX
, topY
);
1567 g2d
.setColor(getBackground());
1568 g2d
.fill(path
.getShape());
1570 g2d
.setColor(getBoundsColor());
1571 g2d
.draw(path
.getShape());
1573 g2d
.setColor(getTopBlickColor());
1574 g2d
.drawLine(topX
+ path
.deltaX(1), topY
+ path
.deltaY(1), bottomX
- path
.deltaX(getArcSize()), topY
+ path
.deltaY(1));
1576 g2d
.setColor(getRightBlockColor());
1577 g2d
.drawLine(bottomX
- path
.deltaX(1), topY
+ path
.deltaY(getArcSize()), bottomX
- path
.deltaX(1), bottomY
- path
.deltaY(1));
1580 private void paintLastGhost(Graphics2D g2d
) {
1581 final ShapeTransform path
= getEffectiveLayout().createShapeTransform(mySingleRowLayout
.myLastSingRowLayout
.lastGhost
);
1583 int topX
= path
.getX() - path
.deltaX(getArcSize());
1584 int topY
= path
.getY() + path
.deltaY(getSelectionTabVShift());
1585 int bottomX
= path
.getMaxX() - path
.deltaX(getCurveArc());
1586 int bottomY
= path
.getMaxY() + path
.deltaY(1);
1588 path
.moveTo(topX
, topY
);
1589 path
.lineTo(bottomX
, topY
);
1590 path
.quadTo(bottomX
- getCurveArc(), topY
+ (bottomY
- topY
) / 4, bottomX
, topY
+ (bottomY
- topY
) / 2);
1591 path
.quadTo(bottomX
+ getCurveArc(), bottomY
- (bottomY
- topY
) / 4, bottomX
, bottomY
);
1592 path
.lineTo(topX
, bottomY
);
1596 g2d
.setColor(getBackground());
1597 g2d
.fill(path
.getShape());
1599 g2d
.setColor(getBoundsColor());
1600 g2d
.draw(path
.getShape());
1602 g2d
.setColor(getTopBlickColor());
1603 g2d
.drawLine(topX
, topY
+ path
.deltaY(1), bottomX
- path
.deltaX(getCurveArc()), topY
+ path
.deltaY(1));
1606 private int getCurveArc() {
1610 private Color
getBoundsColor() {
1614 private Color
getRightBlockColor() {
1615 return Color
.lightGray
;
1618 private Color
getTopBlickColor() {
1622 private void paintNonSelectedTabs(final Graphics2D g2d
, final boolean leftGhostExists
) {
1623 TabInfo selected
= getSelectedInfo();
1624 if (myLastPaintedSelection
== null || !myLastPaintedSelection
.equals(selected
)) {
1625 List
<TabInfo
> tabs
= getTabs();
1626 for (TabInfo each
: tabs
) {
1627 myInfo2Label
.get(each
).setInactiveStateImage(null);
1631 for (int eachRow
= 0; eachRow
< myLastLayoutPass
.getRowCount(); eachRow
++) {
1632 for (int eachColumn
= myLastLayoutPass
.getColumnCount(eachRow
) - 1; eachColumn
>= 0; eachColumn
--) {
1633 final TabInfo each
= myLastLayoutPass
.getTabAt(eachRow
, eachColumn
);
1634 if (getSelectedInfo() == each
) {
1637 paintNonSelected(g2d
, each
, leftGhostExists
);
1641 myLastPaintedSelection
= selected
;
1644 private void paintNonSelected(final Graphics2D g2d
, final TabInfo each
, final boolean leftGhostExists
) {
1645 final TabLabel label
= myInfo2Label
.get(each
);
1646 if (label
.getBounds().width
== 0) return;
1648 int imageInsets
= getArcSize() + 1;
1650 Rectangle bounds
= label
.getBounds();
1652 int x
= bounds
.x
- imageInsets
;
1654 int width
= bounds
.width
+ imageInsets
* 2 + 1;
1655 int height
= bounds
.height
+ getArcSize() + 1;
1657 if (isToBufferPainting()) {
1658 BufferedImage img
= label
.getInactiveStateImage(bounds
);
1661 img
= new BufferedImage(width
, height
, BufferedImage
.TYPE_INT_ARGB
);
1662 Graphics2D imgG2d
= img
.createGraphics();
1663 imgG2d
.addRenderingHints(g2d
.getRenderingHints());
1664 doPaintInactictive(imgG2d
, leftGhostExists
, label
, new Rectangle(imageInsets
, 0, label
.getWidth(), label
.getHeight()));
1668 g2d
.drawImage(img
, x
, y
, width
, height
, null);
1670 label
.setInactiveStateImage(img
);
1672 doPaintInactictive(g2d
, leftGhostExists
, label
, label
.getBounds());
1673 label
.setInactiveStateImage(null);
1677 private boolean isToBufferPainting() {
1678 return Registry
.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint
;
1681 private void doPaintInactictive(Graphics2D g2d
, boolean leftGhostExists
, TabLabel label
, Rectangle effectiveBounds
) {
1682 int tabIndex
= myVisibleInfos
.indexOf(label
.getInfo());
1684 final int arc
= getArcSize();
1685 Color topBlickColor
= getTopBlickColor();
1686 Color rightBlockColor
= getRightBlockColor();
1687 Color boundsColor
= getBoundsColor();
1688 Color backgroundColor
= getBackground();
1690 final Color tabColor
= label
.getInfo().getTabColor();
1691 if (tabColor
!= null) {
1692 backgroundColor
= tabColor
;
1693 boundsColor
= tabColor
.darker();
1694 topBlickColor
= tabColor
.brighter().brighter();
1695 rightBlockColor
= tabColor
;
1698 final TabInfo selected
= getSelectedInfo();
1699 final int selectionTabVShift
= getSelectionTabVShift();
1702 final TabInfo prev
= myLastLayoutPass
.getPreviousFor(myVisibleInfos
.get(tabIndex
));
1703 final TabInfo next
= myLastLayoutPass
.getNextFor(myVisibleInfos
.get(tabIndex
));
1706 boolean firstShowing
= prev
== null;
1707 if (!firstShowing
&& !leftGhostExists
) {
1708 firstShowing
= myInfo2Label
.get(prev
).getBounds().width
== 0;
1711 boolean lastShowing
= next
== null;
1713 lastShowing
= myInfo2Label
.get(next
).getBounds().width
== 0;
1716 boolean leftFromSelection
= selected
!= null && tabIndex
== myVisibleInfos
.indexOf(selected
) - 1;
1718 Rectangle originalBounds
= effectiveBounds
;
1719 final ShapeTransform shape
= getEffectiveLayout().createShapeTransform(originalBounds
);
1721 int leftX
= firstShowing ? shape
.getX() : shape
.getX() - shape
.deltaX(arc
+ 1);
1722 int topY
= shape
.getY() + shape
.deltaY(selectionTabVShift
);
1723 int rigthX
= !lastShowing
&& leftFromSelection ? shape
.getMaxX() + shape
.deltaX(arc
+ 1) : shape
.getMaxX();
1724 int bottomY
= shape
.getMaxY() + shape
.deltaY(1);
1726 shape
.moveTo(leftX
, bottomY
);
1727 shape
.lineTo(leftX
, topY
+ shape
.deltaY(arc
));
1728 shape
.quadTo(leftX
, topY
, leftX
+ shape
.deltaX(arc
), topY
);
1729 shape
.lineTo(rigthX
- shape
.deltaX(arc
), topY
);
1730 shape
.quadTo(rigthX
, topY
, rigthX
, topY
+ shape
.deltaY(arc
));
1731 shape
.lineTo(rigthX
, bottomY
);
1733 if (!isSingleRow()) {
1734 final TablePassInfo info
= myTableLayout
.myLastTableLayout
;
1735 if (!info
.isInSelectionRow(label
.getInfo())) {
1736 shape
.lineTo(rigthX
, bottomY
+ shape
.deltaY(getArcSize()));
1737 shape
.lineTo(leftX
, bottomY
+ shape
.deltaY(getArcSize()));
1738 shape
.lineTo(leftX
, bottomY
);
1744 g2d
.setColor(backgroundColor
);
1745 g2d
.fill(shape
.getShape());
1749 final Line2D
.Float gradientLine
=
1750 shape
.transformLine(0, topY
, 0, topY
+ shape
.deltaY((int) (shape
.getHeight() / 1.5 )));
1752 final GradientPaint gp
=
1753 new GradientPaint(gradientLine
.x1
, gradientLine
.y1
,
1754 shape
.transformY1(backgroundColor
.brighter().brighter(), backgroundColor
),
1755 gradientLine
.x2
, gradientLine
.y2
,
1756 shape
.transformY1(backgroundColor
, backgroundColor
.brighter().brighter()));
1757 final Paint old
= g2d
.getPaint();
1759 g2d
.fill(shape
.getShape());
1762 g2d
.setColor(topBlickColor
);
1764 shape
.transformLine(leftX
+ shape
.deltaX(arc
+ 1), topY
+ shape
.deltaY(1), rigthX
- shape
.deltaX(arc
- 1), topY
+ shape
.deltaY(1)));
1766 g2d
.setColor(rightBlockColor
);
1767 g2d
.draw(shape
.transformLine(rigthX
- shape
.deltaX(1), topY
+ shape
.deltaY(arc
- 1), rigthX
- shape
.deltaX(1), bottomY
));
1769 g2d
.setColor(boundsColor
);
1770 g2d
.draw(shape
.getShape());
1773 public int getSelectionTabVShift() {
1777 private void paintBorder(Graphics2D g2d
, ShapeInfo shape
, final Color borderColor
) {
1779 final ShapeTransform shaper
= shape
.path
.copy().reset();
1781 final Insets paintBorder
= shape
.path
.transformInsets(myBorder
.getEffectiveBorder());
1783 int topY
= shape
.labelPath
.getMaxY() + shape
.labelPath
.deltaY(1);
1785 int bottomY
= topY
+ paintBorder
.top
- 2;
1786 int middleY
= topY
+ (bottomY
- topY
) / 2;
1789 final int boundsX
= shape
.path
.getX() + shape
.path
.deltaX(shape
.insets
.left
);
1792 isHideTabs() ? shape
.path
.getY() + shape
.path
.deltaY(shape
.insets
.top
) : shape
.labelPath
.getMaxY() + shape
.path
.deltaY(1);
1794 final int boundsHeight
= Math
.abs(shape
.path
.getMaxY() - boundsY
) - shape
.insets
.bottom
- paintBorder
.bottom
;
1795 final int boundsWidth
= Math
.abs(shape
.path
.getMaxX() - (shape
.insets
.left
+ shape
.insets
.right
));
1797 if (paintBorder
.top
> 0) {
1799 if (isToDrawBorderIfTabsHidden()) {
1800 g2d
.setColor(borderColor
);
1801 g2d
.fill(shaper
.reset().doRect(boundsX
, boundsY
, boundsWidth
, 1).getShape());
1805 Color tabFillColor
= getActiveTabColor(null);
1806 if (tabFillColor
== null) {
1807 tabFillColor
= shape
.path
.transformY1(shape
.to
, shape
.from
);
1810 g2d
.setColor(tabFillColor
);
1811 g2d
.fill(shaper
.reset().doRect(boundsX
, topY
+ shape
.path
.deltaY(1), boundsWidth
, paintBorder
.top
- 1).getShape());
1813 g2d
.setColor(borderColor
);
1814 if (paintBorder
.top
== 2) {
1815 final Line2D
.Float line
= shape
.path
.transformLine(boundsX
, topY
, boundsX
+ shape
.path
.deltaX(boundsWidth
- 1), topY
);
1817 g2d
.drawLine((int)line
.x1
, (int)line
.y1
, (int)line
.x2
, (int)line
.y2
);
1819 else if (paintBorder
.top
> 2) {
1823 if (myPosition
== JBTabsPosition
.bottom
|| myPosition
== JBTabsPosition
.right
) {
1827 final int topLine
= topY
+ shape
.path
.deltaY(paintBorder
.top
- 1);
1828 g2d
.fill(shaper
.reset().doRect(boundsX
, topLine
+ deltaY
, boundsWidth
- 1, 1).getShape());
1833 g2d
.setColor(borderColor
);
1836 g2d
.fill(shaper
.reset().doRect(boundsX
, Math
.abs(shape
.path
.getMaxY() - shape
.insets
.bottom
- paintBorder
.bottom
), boundsWidth
,
1837 paintBorder
.bottom
).getShape());
1840 g2d
.fill(shaper
.reset().doRect(boundsX
, boundsY
, paintBorder
.left
, boundsHeight
).getShape());
1843 g2d
.fill(shaper
.reset()
1844 .doRect(shape
.path
.getMaxX() - shape
.insets
.right
- paintBorder
.right
, boundsY
, paintBorder
.right
, boundsHeight
).getShape());
1848 public boolean isStealthModeEffective() {
1849 return myStealthTabMode
&& getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition
.top
;
1853 private boolean isNavigationVisible() {
1854 if (myStealthTabMode
&& getTabCount() == 1) return false;
1855 return !myVisibleInfos
.isEmpty();
1859 public void paint(final Graphics g
) {
1860 Rectangle clip
= g
.getClipBounds();
1865 if (myPaintBlocked
) {
1866 if (myImage
!= null) {
1867 g
.drawImage(myImage
, 0, 0, getWidth(), getHeight(), null);
1875 protected void paintChildren(final Graphics g
) {
1876 super.paintChildren(g
);
1878 final GraphicsConfig config
= new GraphicsConfig(g
);
1879 config
.setAntialiasing(true);
1880 paintSelectionAndBorder((Graphics2D
)g
);
1883 final TabLabel selected
= getSelectedLabel();
1884 if (selected
!= null) {
1885 selected
.paintImage(g
);
1888 mySingleRowLayout
.myMoreIcon
.paintIcon(this, g
);
1891 private Max
computeMaxSize() {
1892 Max max
= new Max();
1893 for (TabInfo eachInfo
: myVisibleInfos
) {
1894 final TabLabel label
= myInfo2Label
.get(eachInfo
);
1895 max
.myLabel
.height
= Math
.max(max
.myLabel
.height
, label
.getPreferredSize().height
);
1896 max
.myLabel
.width
= Math
.max(max
.myLabel
.width
, label
.getPreferredSize().width
);
1897 final Toolbar toolbar
= myInfo2Toolbar
.get(eachInfo
);
1898 if (myLayout
.isSideComponentOnTabs() && toolbar
!= null && !toolbar
.isEmpty()) {
1899 max
.myToolbar
.height
= Math
.max(max
.myToolbar
.height
, toolbar
.getPreferredSize().height
);
1900 max
.myToolbar
.width
= Math
.max(max
.myToolbar
.width
, toolbar
.getPreferredSize().width
);
1904 max
.myToolbar
.height
++;
1909 public Dimension
getMinimumSize() {
1910 return computeSize(new Transform
<JComponent
, Dimension
>() {
1911 public Dimension
transform(JComponent component
) {
1912 return component
.getMinimumSize();
1917 public Dimension
getPreferredSize() {
1918 return computeSize(new Transform
<JComponent
, Dimension
>() {
1919 public Dimension
transform(JComponent component
) {
1920 return component
.getPreferredSize();
1925 private Dimension
computeSize(Transform
<JComponent
, Dimension
> transform
, int tabCount
) {
1926 Dimension size
= new Dimension();
1927 for (TabInfo each
: myVisibleInfos
) {
1928 final JComponent c
= each
.getComponent();
1930 final Dimension eachSize
= transform
.transform(c
);
1931 size
.width
= Math
.max(eachSize
.width
, size
.width
);
1932 size
.height
= Math
.max(eachSize
.height
, size
.height
);
1936 addHeaderSize(size
, tabCount
);
1940 private void addHeaderSize(Dimension size
, final int tabsCount
) {
1941 Dimension header
= computeHeaderPreferredSize(tabsCount
);
1943 size
.height
+= header
.height
;
1944 size
.width
+= header
.width
;
1946 final Insets insets
= getLayoutInsets();
1947 size
.width
+= insets
.left
+ insets
.right
+ 1;
1948 size
.height
+= insets
.top
+ insets
.bottom
+ 1;
1951 private Dimension
computeHeaderPreferredSize(int tabsCount
) {
1952 final Iterator
<TabInfo
> infos
= myInfo2Label
.keySet().iterator();
1953 Dimension size
= new Dimension();
1956 final boolean horizontal
= getTabsPosition() == JBTabsPosition
.top
|| getTabsPosition() == JBTabsPosition
.bottom
;
1958 while (infos
.hasNext()) {
1959 final boolean canGrow
= currentTab
< tabsCount
;
1961 TabInfo eachInfo
= infos
.next();
1962 final TabLabel eachLabel
= myInfo2Label
.get(eachInfo
);
1963 final Dimension eachPrefSize
= eachLabel
.getPreferredSize();
1966 size
.width
+= eachPrefSize
.width
;
1968 size
.height
= Math
.max(size
.height
, eachPrefSize
.height
);
1971 size
.width
= Math
.max(size
.width
, eachPrefSize
.width
);
1973 size
.height
+= eachPrefSize
.height
;
1980 if (isSingleRow() && isGhostsAlwaysVisible()) {
1982 size
.width
+= getGhostTabLength() * 2;
1985 size
.height
+= getGhostTabLength() * 2;
1990 size
.height
+= myBorder
.getTabBorderSize();
1993 size
.width
+= myBorder
.getTabBorderSize();
1999 public int getTabCount() {
2000 return getTabs().size();
2004 public JBTabsPresentation
getPresentation() {
2008 public ActionCallback
removeTab(final JComponent component
) {
2009 return removeTab(findInfo(component
));
2012 public ActionCallback
removeTab(final TabInfo info
) {
2013 return removeTab(info
, null);
2016 public ActionCallback
removeTab(final TabInfo info
, @Nullable TabInfo forcedSelectionTranfer
) {
2017 return removeTab(info
, forcedSelectionTranfer
, true);
2020 public ActionCallback
removeTab(final TabInfo info
, @Nullable TabInfo forcedSelectionTranfer
, boolean transferFocus
) {
2021 if (info
== null || !getTabs().contains(info
)) return new ActionCallback
.Done();
2023 final ActionCallback result
= new ActionCallback();
2026 if (forcedSelectionTranfer
== null) {
2027 toSelect
= getToSelectOnRemoveOf(info
);
2030 assert myVisibleInfos
.contains(forcedSelectionTranfer
) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer
;
2031 toSelect
= forcedSelectionTranfer
;
2035 if (toSelect
!= null) {
2036 processRemove(info
, false);
2037 _setSelected(toSelect
, transferFocus
).doWhenProcessed(new Runnable() {
2039 removeDeferred().notifyWhenDone(result
);
2044 processRemove(info
, true);
2045 removeDeferred().notifyWhenDone(result
);
2048 if (myVisibleInfos
.isEmpty()) {
2049 removeDeferredNow();
2052 revalidateAndRepaint(true);
2057 private void processRemove(final TabInfo info
, boolean forcedNow
) {
2058 remove(myInfo2Label
.get(info
));
2059 remove(myInfo2Toolbar
.get(info
));
2061 JComponent tabComponent
= info
.getComponent();
2063 if (!isToDeferRemoveForLater(tabComponent
) || forcedNow
) {
2064 remove(tabComponent
);
2067 queueForRemove(tabComponent
);
2070 myVisibleInfos
.remove(info
);
2071 myHiddenInfos
.remove(info
);
2072 myInfo2Label
.remove(info
);
2073 myInfo2Toolbar
.remove(info
);
2076 updateAll(false, false);
2079 public TabInfo
findInfo(Component component
) {
2080 for (TabInfo each
: getTabs()) {
2081 if (each
.getComponent() == component
) return each
;
2087 public TabInfo
findInfo(String text
) {
2088 if (text
== null) return null;
2090 for (TabInfo each
: getTabs()) {
2091 if (text
.equals(each
.getText())) return each
;
2097 public TabInfo
findInfo(MouseEvent event
) {
2098 return findInfo(event
, false);
2101 private TabInfo
findInfo(final MouseEvent event
, final boolean labelsOnly
) {
2102 final Point point
= SwingUtilities
.convertPoint(event
.getComponent(), event
.getPoint(), this);
2103 return _findInfo(point
, labelsOnly
);
2106 public TabInfo
findInfo(final Object object
) {
2107 for (int i
= 0; i
< getTabCount(); i
++) {
2108 final TabInfo each
= getTabAt(i
);
2109 final Object eachObject
= each
.getObject();
2110 if (eachObject
!= null && eachObject
.equals(object
)) return each
;
2115 public TabInfo
findTabLabelBy(final Point point
) {
2116 return _findInfo(point
, true);
2119 private TabInfo
_findInfo(final Point point
, boolean labelsOnly
) {
2120 Component component
= findComponentAt(point
);
2121 if (component
== null) return null;
2122 while (component
!= this || component
!= null) {
2123 if (component
instanceof TabLabel
) {
2124 return ((TabLabel
)component
).getInfo();
2126 else if (!labelsOnly
) {
2127 final TabInfo info
= findInfo(component
);
2128 if (info
!= null) return info
;
2130 if (component
== null) break;
2131 component
= component
.getParent();
2137 public void removeAllTabs() {
2138 for (TabInfo each
: getTabs()) {
2144 private static class Max
{
2145 Dimension myLabel
= new Dimension();
2146 Dimension myToolbar
= new Dimension();
2149 private void updateContainer(boolean forced
, final boolean layoutNow
) {
2150 final TabLabel selectedLabel
= getSelectedLabel();
2152 for (TabInfo each
: myVisibleInfos
) {
2153 final JComponent eachComponent
= each
.getComponent();
2154 if (getSelectedInfo() == each
&& getSelectedInfo() != null) {
2155 unqueueFromRemove(eachComponent
);
2157 final Container parent
= eachComponent
.getParent();
2158 if (parent
!= null && parent
!= this) {
2159 parent
.remove(eachComponent
);
2162 if (eachComponent
.getParent() == null) {
2167 if (eachComponent
.getParent() == null) continue;
2168 if (isToDeferRemoveForLater(eachComponent
)) {
2169 queueForRemove(eachComponent
);
2172 remove(eachComponent
);
2177 relayout(forced
, layoutNow
);
2180 protected void addImpl(final Component comp
, final Object constraints
, final int index
) {
2181 unqueueFromRemove(comp
);
2183 if (comp
instanceof TabLabel
) {
2184 ((TabLabel
)comp
).apply(myUiDecorator
.getDecoration());
2187 super.addImpl(comp
, constraints
, index
);
2191 private boolean isToDeferRemoveForLater(JComponent c
) {
2192 return c
.getRootPane() != null;
2195 void relayout(boolean forced
, final boolean layoutNow
) {
2196 if (!myForcedRelayout
) {
2197 myForcedRelayout
= forced
;
2199 revalidateAndRepaint(layoutNow
);
2202 ActionManager
getActionManager() {
2203 return myActionManager
;
2206 public TabsBorder
getTabsBorder() {
2211 public JBTabs
addTabMouseMotionListener(@NotNull MouseMotionListener listener
) {
2213 myTabMouseListeners
.add(listener
);
2219 public JBTabs
addTabMouseListener(@NotNull MouseListener listener
) {
2221 myTabMouseListeners
.add(listener
);
2227 public JComponent
getComponent() {
2232 public JBTabs
removeTabMouseListener(@NotNull MouseListener listener
) {
2234 myTabMouseListeners
.remove(listener
);
2239 private void addListeners() {
2240 for (TabInfo eachInfo
: myVisibleInfos
) {
2241 final TabLabel label
= myInfo2Label
.get(eachInfo
);
2242 for (EventListener eachListener
: myTabMouseListeners
) {
2243 if (eachListener
instanceof MouseListener
) {
2244 label
.addMouseListener((MouseListener
)eachListener
);
2246 else if (eachListener
instanceof MouseMotionListener
) {
2247 label
.addMouseMotionListener((MouseMotionListener
)eachListener
);
2256 private void removeListeners() {
2257 for (TabInfo eachInfo
: myVisibleInfos
) {
2258 final TabLabel label
= myInfo2Label
.get(eachInfo
);
2259 for (EventListener eachListener
: myTabMouseListeners
) {
2260 if (eachListener
instanceof MouseListener
) {
2261 label
.removeMouseListener((MouseListener
)eachListener
);
2263 else if (eachListener
instanceof MouseMotionListener
) {
2264 label
.removeMouseMotionListener((MouseMotionListener
)eachListener
);
2273 private void updateListeners() {
2278 public JBTabs
addListener(@NotNull TabsListener listener
) {
2279 myTabListeners
.add(listener
);
2283 public JBTabs
removeListener(@NotNull final TabsListener listener
) {
2284 myTabListeners
.remove(listener
);
2288 protected void onPopup(final TabInfo popupInfo
) {
2291 public void setFocused(final boolean focused
) {
2292 if (myFocused
== focused
) return;
2294 myFocused
= focused
;
2301 public int getIndexOf(@Nullable final TabInfo tabInfo
) {
2302 return myVisibleInfos
.indexOf(tabInfo
);
2305 public boolean isHideTabs() {
2309 public void setHideTabs(final boolean hideTabs
) {
2310 if (isHideTabs() == hideTabs
) return;
2312 myHideTabs
= hideTabs
;
2314 relayout(true, false);
2317 public JBTabsPresentation
setPaintBorder(int top
, int left
, int right
, int bottom
) {
2318 return myBorder
.setPaintBorder(top
, left
, right
, bottom
);
2321 public JBTabsPresentation
setTabSidePaintBorder(int size
) {
2322 return myBorder
.setTabSidePaintBorder(size
);
2325 static int getBorder(int size
) {
2326 return size
== -1 ?
1 : size
;
2329 public boolean isPaintFocus() {
2330 return myPaintFocus
;
2334 public JBTabsPresentation
setAdjustBorders(final boolean adjust
) {
2335 myAdjustBorders
= adjust
;
2340 public JBTabsPresentation
setActiveTabFillIn(@Nullable final Color color
) {
2341 if (!isChanged(myActiveTabFillIn
, color
)) return this;
2343 myActiveTabFillIn
= color
;
2344 revalidateAndRepaint(false);
2348 private boolean isChanged(Object oldObject
, Object newObject
) {
2349 if (oldObject
== null && newObject
== null) return false;
2350 return oldObject
!= null && !oldObject
.equals(newObject
) || newObject
!= null && !newObject
.equals(oldObject
);
2354 public JBTabsPresentation
setTabLabelActionsAutoHide(final boolean autoHide
) {
2355 if (myTabLabelActionsAutoHide
!= autoHide
) {
2356 myTabLabelActionsAutoHide
= autoHide
;
2357 revalidateAndRepaint(false);
2363 public Color
getActiveTabFillIn() {
2364 return myActiveTabFillIn
;
2367 public JBTabsPresentation
setFocusCycle(final boolean root
) {
2368 setFocusCycleRoot(root
);
2373 public JBTabsPresentation
setPaintFocus(final boolean paintFocus
) {
2374 myPaintFocus
= paintFocus
;
2378 private abstract static class BaseNavigationAction
extends AnAction
{
2380 private final ShadowAction myShadow
;
2381 private final ActionManager myActionManager
;
2382 private final JBTabsImpl myTabs
;
2384 protected BaseNavigationAction(final String copyFromID
, JBTabsImpl tabs
, ActionManager mgr
) {
2385 myActionManager
= mgr
;
2387 myShadow
= new ShadowAction(this, myActionManager
.getAction(copyFromID
), tabs
);
2388 Disposer
.register(tabs
, myShadow
);
2389 setEnabledInModalContext(true);
2392 public final void update(final AnActionEvent e
) {
2393 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
2394 e
.getPresentation().setVisible(tabs
!= null);
2395 if (tabs
== null) return;
2397 final int selectedIndex
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
2398 final boolean enabled
= tabs
== myTabs
&& myTabs
.isNavigationVisible() && selectedIndex
>= 0 && myTabs
.myNavigationActionsEnabled
;
2399 e
.getPresentation().setEnabled(enabled
);
2401 _update(e
, tabs
, selectedIndex
);
2405 public void reconnect(String actionId
) {
2406 myShadow
.reconnect(myActionManager
.getAction(actionId
));
2409 protected abstract void _update(AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
);
2411 public final void actionPerformed(final AnActionEvent e
) {
2412 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
2413 if (tabs
== null) return;
2415 final int index
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
2416 if (index
== -1) return;
2417 _actionPerformed(e
, tabs
, index
);
2420 protected abstract void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
);
2423 private static class SelectNextAction
extends BaseNavigationAction
{
2425 private SelectNextAction(JBTabsImpl tabs
, ActionManager mgr
) {
2426 super(IdeActions
.ACTION_NEXT_TAB
, tabs
, mgr
);
2429 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
2430 e
.getPresentation().setEnabled(tabs
.findEnabledForward(selectedIndex
+ 1) != null);
2433 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
2434 tabs
.select(tabs
.findEnabledForward(selectedIndex
+ 1), true);
2438 private static class SelectPreviousAction
extends BaseNavigationAction
{
2439 private SelectPreviousAction(JBTabsImpl tabs
, ActionManager mgr
) {
2440 super(IdeActions
.ACTION_PREVIOUS_TAB
, tabs
, mgr
);
2443 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
2444 e
.getPresentation().setEnabled(tabs
.findEnabledBackward(selectedIndex
- 1) != null);
2447 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
2448 tabs
.select(tabs
.findEnabledBackward(selectedIndex
- 1), true);
2452 private void disposePopupListener() {
2453 if (myActivePopup
!= null) {
2454 myActivePopup
.removePopupMenuListener(myPopupListener
);
2455 myActivePopup
= null;
2459 public JBTabsPresentation
setStealthTabMode(final boolean stealthTabMode
) {
2460 myStealthTabMode
= stealthTabMode
;
2462 relayout(true, false);
2467 public boolean isStealthTabMode() {
2468 return myStealthTabMode
;
2471 public JBTabsPresentation
setSideComponentVertical(final boolean vertical
) {
2472 myHorizontalSide
= !vertical
;
2474 for (TabInfo each
: myVisibleInfos
) {
2475 each
.getChangeSupport().firePropertyChange(TabInfo
.ACTION_GROUP
, "new1", "new2");
2479 relayout(true, false);
2484 public JBTabsPresentation
setSingleRow(boolean singleRow
) {
2485 myLayout
= singleRow ? mySingleRowLayout
: myTableLayout
;
2487 relayout(true, false);
2492 public JBTabsPresentation
setGhostsAlwaysVisible(final boolean visible
) {
2493 myGhostsAlwaysVisible
= visible
;
2495 relayout(true, false);
2500 public boolean isGhostsAlwaysVisible() {
2501 return myGhostsAlwaysVisible
;
2504 public boolean isSingleRow() {
2505 return getEffectiveLayout() == mySingleRowLayout
;
2508 public boolean isSideComponentVertical() {
2509 return !myHorizontalSide
;
2512 private TabLayout
getEffectiveLayout() {
2513 if (myLayout
== myTableLayout
&& getTabsPosition() == JBTabsPosition
.top
) return myTableLayout
;
2514 return mySingleRowLayout
;
2517 public JBTabsPresentation
setUiDecorator(UiDecorator decorator
) {
2518 myUiDecorator
= decorator
== null ? ourDefaultDecorator
: decorator
;
2523 protected void setUI(final ComponentUI newUI
) {
2528 public void updateUI() {
2530 SwingUtilities
.invokeLater(new Runnable() {
2534 revalidateAndRepaint(false);
2539 private void applyDecoration() {
2540 if (myUiDecorator
!= null) {
2541 UiDecorator
.UiDecoration uiDecoration
= myUiDecorator
.getDecoration();
2542 for (TabLabel each
: myInfo2Label
.values()) {
2543 each
.apply(uiDecoration
);
2548 for (TabInfo each
: getTabs()) {
2552 relayout(true, false);
2555 private void adjust(final TabInfo each
) {
2556 if (myAdjustBorders
) {
2557 UIUtil
.removeScrollBorder(each
.getComponent());
2561 public void sortTabs(Comparator
<TabInfo
> comparator
) {
2562 Collections
.sort(myVisibleInfos
, comparator
);
2564 relayout(true, false);
2567 public boolean isRequestFocusOnLastFocusedComponent() {
2568 return myRequestFocusOnLastFocusedComponent
;
2571 public JBTabsPresentation
setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent
) {
2572 myRequestFocusOnLastFocusedComponent
= requestFocusOnLastFocusedComponent
;
2578 public Object
getData(@NonNls final String dataId
) {
2579 if (myDataProvider
!= null) {
2580 final Object value
= myDataProvider
.getData(dataId
);
2581 if (value
!= null) return value
;
2584 return NAVIGATION_ACTIONS_KEY
.is(dataId
) ?
this : null;
2588 public DataProvider
getDataProvider() {
2589 return myDataProvider
;
2592 public JBTabsImpl
setDataProvider(@NotNull final DataProvider dataProvider
) {
2593 myDataProvider
= dataProvider
;
2598 public boolean isSelectionClick(final MouseEvent e
, boolean canBeQuick
) {
2599 if (e
.getClickCount() == 1 || canBeQuick
) {
2600 if (!e
.isPopupTrigger()) {
2601 return e
.getButton() == MouseEvent
.BUTTON1
&& !e
.isControlDown() && !e
.isAltDown() && !e
.isMetaDown();
2609 private static class DefautDecorator
implements UiDecorator
{
2611 public UiDecoration
getDecoration() {
2612 return new UiDecoration(null, new Insets(1, 4, 1, 5));
2616 public static Rectangle
layout(JComponent c
, Rectangle bounds
) {
2617 final Rectangle now
= c
.getBounds();
2618 if (!bounds
.equals(now
)) {
2619 c
.setBounds(bounds
);
2621 c
.putClientProperty(LAYOUT_DONE
, Boolean
.TRUE
);
2626 public static Rectangle
layout(JComponent c
, int x
, int y
, int width
, int height
) {
2627 return layout(c
, new Rectangle(x
, y
, width
, height
));
2630 public static void resetLayout(JComponent c
) {
2631 if (c
== null) return;
2632 c
.putClientProperty(LAYOUT_DONE
, null);
2635 private void applyResetComponents() {
2636 for (int i
= 0; i
< getComponentCount(); i
++) {
2637 final Component each
= getComponent(i
);
2638 if (each
instanceof JComponent
) {
2639 final JComponent jc
= (JComponent
)each
;
2640 final Object done
= jc
.getClientProperty(LAYOUT_DONE
);
2641 if (!Boolean
.TRUE
.equals(done
)) {
2642 layout(jc
, new Rectangle(0, 0, 0, 0));
2650 public JBTabsPresentation
setTabLabelActionsMouseDeadzone(final TimedDeadzone
.Length length
) {
2651 myTabActionsMouseDeadzone
= length
;
2652 final List
<TabInfo
> all
= getTabs();
2653 for (TabInfo each
: all
) {
2654 final TabLabel eachLabel
= myInfo2Label
.get(each
);
2655 eachLabel
.updateTabActions();
2661 public JBTabsPresentation
setTabsPosition(final JBTabsPosition position
) {
2662 myPosition
= position
;
2663 relayout(true, false);
2667 public JBTabsPosition
getTabsPosition() {
2671 public TimedDeadzone
.Length
getTabActionsMouseDeadzone() {
2672 return myTabActionsMouseDeadzone
;
2675 public JBTabsPresentation
setTabDraggingEnabled(boolean enabled
) {
2676 myTabDraggingEnabled
= enabled
;
2680 public boolean isTabDraggingEnabled() {
2681 return myTabDraggingEnabled
&& isSingleRow();
2684 void reallocate(TabInfo source
, TabInfo target
, boolean before
) {
2685 if (source
== target
|| source
== null || target
== null) return;
2687 final int targetIndex
= myVisibleInfos
.indexOf(target
);
2688 final int sourceIndex
= myVisibleInfos
.indexOf(source
);
2690 boolean needsValidation
= false;
2692 myVisibleInfos
.remove(source
);
2693 myVisibleInfos
.add(targetIndex
, source
);
2694 needsValidation
= true;
2696 //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
2699 if (needsValidation
) {
2701 relayout(true, true);
2705 boolean isHorizontalTabs() {
2706 return getTabsPosition() == JBTabsPosition
.top
|| getTabsPosition() == JBTabsPosition
.bottom
;
2709 public void putInfo(Map
<String
, String
> info
) {
2710 final TabInfo selected
= getSelectedInfo();
2711 if (selected
!= null) {
2712 selected
.putInfo(info
);
2716 public boolean isUseBufferedPaint() {
2717 return myUseBufferedPaint
;
2720 public void setUseBufferedPaint(boolean useBufferedPaint
) {
2721 myUseBufferedPaint
= useBufferedPaint
;