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
.FocusCommand
;
27 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
28 import com
.intellij
.openapi
.wm
.IdeGlassPane
;
29 import com
.intellij
.openapi
.wm
.IdeGlassPaneUtil
;
30 import com
.intellij
.openapi
.wm
.impl
.content
.GraphicsConfig
;
31 import com
.intellij
.ui
.CaptionPanel
;
32 import com
.intellij
.ui
.tabs
.*;
33 import com
.intellij
.ui
.tabs
.impl
.singleRow
.SingleRowLayout
;
34 import com
.intellij
.ui
.tabs
.impl
.singleRow
.SingleRowPassInfo
;
35 import com
.intellij
.ui
.tabs
.impl
.table
.TableLayout
;
36 import com
.intellij
.ui
.tabs
.impl
.table
.TablePassInfo
;
37 import com
.intellij
.util
.ui
.Animator
;
38 import com
.intellij
.util
.ui
.TimedDeadzone
;
39 import com
.intellij
.util
.ui
.UIUtil
;
40 import com
.intellij
.util
.ui
.update
.LazyUiDisposable
;
41 import org
.jetbrains
.annotations
.NonNls
;
42 import org
.jetbrains
.annotations
.NotNull
;
43 import org
.jetbrains
.annotations
.Nullable
;
46 import javax
.swing
.event
.PopupMenuEvent
;
47 import javax
.swing
.event
.PopupMenuListener
;
48 import javax
.swing
.plaf
.ComponentUI
;
50 import java
.awt
.event
.*;
51 import java
.awt
.geom
.Line2D
;
52 import java
.awt
.image
.BufferedImage
;
53 import java
.beans
.PropertyChangeEvent
;
54 import java
.beans
.PropertyChangeListener
;
56 import java
.util
.List
;
58 public class JBTabsImpl
extends JComponent
59 implements JBTabs
, PropertyChangeListener
, TimerListener
, DataProvider
, PopupMenuListener
, Disposable
, JBTabsPresentation
, TestableUi
{
61 static DataKey
<JBTabsImpl
> NAVIGATION_ACTIONS_KEY
= DataKey
.create("JBTabs");
63 ActionManager myActionManager
;
64 public final List
<TabInfo
> myVisibleInfos
= new ArrayList
<TabInfo
>();
65 private final Map
<TabInfo
, Integer
> myHiddenInfos
= new HashMap
<TabInfo
, Integer
>();
67 private TabInfo mySelectedInfo
;
68 public final Map
<TabInfo
, TabLabel
> myInfo2Label
= new HashMap
<TabInfo
, TabLabel
>();
69 public final Map
<TabInfo
, Toolbar
> myInfo2Toolbar
= new HashMap
<TabInfo
, Toolbar
>();
70 public Dimension myHeaderFitSize
;
72 private Insets myInnerInsets
= new Insets(0, 0, 0, 0);
74 private final List
<EventListener
> myTabMouseListeners
= new ArrayList
<EventListener
>();
75 private final List
<TabsListener
> myTabListeners
= new ArrayList
<TabsListener
>();
76 public boolean myFocused
;
78 private Getter
<ActionGroup
> myPopupGroup
;
79 private String myPopupPlace
;
82 DefaultActionGroup myNavigationActions
;
84 PopupMenuListener myPopupListener
;
85 JPopupMenu myActivePopup
;
87 public boolean myHorizontalSide
= true;
89 private boolean myStealthTabMode
= false;
91 private DataProvider myDataProvider
;
93 private final WeakHashMap
<Component
, Component
> myDeferredToRemove
= new WeakHashMap
<Component
, Component
>();
95 private final SingleRowLayout mySingleRowLayout
= new SingleRowLayout(this);
96 private final TableLayout myTableLayout
= new TableLayout(this);
99 private TabLayout myLayout
= mySingleRowLayout
;
100 LayoutPassInfo myLastLayoutPass
;
101 private TabInfo myLastPaintedSelection
;
103 public boolean myForcedRelayout
;
105 private UiDecorator myUiDecorator
;
106 static final UiDecorator ourDefaultDecorator
= new DefautDecorator();
108 private boolean myPaintFocus
;
110 private boolean myHideTabs
= false;
111 @Nullable private Project myProject
;
113 private boolean myRequestFocusOnLastFocusedComponent
= false;
114 private boolean myListenerAdded
;
115 final Set
<TabInfo
> myAttractions
= new HashSet
<TabInfo
>();
116 private Animator myAnimator
;
117 private List
<TabInfo
> myAllTabs
;
118 private boolean myPaintBlocked
;
119 private BufferedImage myImage
;
120 private IdeFocusManager myFocusManager
;
121 private boolean myAdjustBorders
= true;
123 boolean myAddNavigationGroup
= true;
125 private boolean myGhostsAlwaysVisible
= false;
126 private boolean myDisposed
;
127 private boolean myToDrawBorderIfTabsHidden
= true;
128 private Color myActiveTabFillIn
;
130 private boolean myTabLabelActionsAutoHide
;
132 private final TabActionsAutoHideListener myTabActionsAutoHideListener
= new TabActionsAutoHideListener();
133 private IdeGlassPane myGlassPane
;
134 @NonNls private static final String LAYOUT_DONE
= "Layout.done";
136 private TimedDeadzone
.Length myTabActionsMouseDeadzone
= TimedDeadzone
.DEFAULT
;
138 private long myRemoveDefferredRequest
;
139 private boolean myTestMode
;
141 private JBTabsPosition myPosition
= JBTabsPosition
.top
;
143 private final TabsBorder myBorder
= new TabsBorder(this);
144 private BaseNavigationAction myNextAction
;
145 private BaseNavigationAction myPrevAction
;
147 private boolean myWasEverShown
;
149 private boolean myTabDraggingEnabled
;
150 private DragHelper myDragHelper
;
151 private boolean myNavigationActionsEnabled
= true;
152 private boolean myUseBufferedPaint
= true;
154 public JBTabsImpl(@NotNull Project project
) {
155 this(project
, project
);
158 public JBTabsImpl(@NotNull Project project
, @NotNull Disposable parent
) {
159 this(project
, ActionManager
.getInstance(), IdeFocusManager
.getInstance(project
), parent
);
162 public JBTabsImpl(@Nullable Project project
, IdeFocusManager focusManager
, @NotNull Disposable parent
) {
163 this(project
, ActionManager
.getInstance(), focusManager
, parent
);
166 public JBTabsImpl(@Nullable Project project
, ActionManager actionManager
, IdeFocusManager focusManager
, @NotNull Disposable parent
) {
168 myActionManager
= actionManager
;
169 myFocusManager
= focusManager
!= null ? focusManager
: IdeFocusManager
.getGlobalInstance();
172 setPaintBorder(-1, -1, -1, -1);
174 Disposer
.register(parent
, this);
176 myNavigationActions
= new DefaultActionGroup();
178 if (myActionManager
!= null) {
179 myNextAction
= new SelectNextAction(this, myActionManager
);
180 myPrevAction
= new SelectPreviousAction(this, myActionManager
);
182 myNavigationActions
.add(myNextAction
);
183 myNavigationActions
.add(myPrevAction
);
186 setUiDecorator(null);
188 myPopupListener
= new PopupMenuListener() {
189 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
192 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
193 disposePopupListener();
196 public void popupMenuCanceled(final PopupMenuEvent e
) {
197 disposePopupListener();
201 addMouseListener(new MouseAdapter() {
202 public void mousePressed(final MouseEvent e
) {
203 if (mySingleRowLayout
.myLastSingRowLayout
!= null &&
204 mySingleRowLayout
.myLastSingRowLayout
.moreRect
!= null &&
205 mySingleRowLayout
.myLastSingRowLayout
.moreRect
.contains(e
.getPoint())) {
211 myAnimator
= new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
212 public void paintNow(final float frame
, final float totalFrames
, final float cycle
) {
213 repaintAttractions();
216 myAnimator
.setTakInitialDelay(false);
218 setFocusCycleRoot(true);
219 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
220 public Component
getDefaultComponent(final Container aContainer
) {
225 add(mySingleRowLayout
.myLeftGhost
);
226 add(mySingleRowLayout
.myRightGhost
);
229 new LazyUiDisposable
<JBTabsImpl
>(parent
, this, this, project
) {
230 protected void initialize(@NotNull Disposable parent
, @NotNull JBTabsImpl child
, @Nullable Project project
) {
233 Disposer
.register(child
, myAnimator
);
234 Disposer
.register(child
, new Disposable() {
235 public void dispose() {
241 final IdeGlassPane gp
= IdeGlassPaneUtil
.find(child
);
243 gp
.addMouseMotionPreprocessor(myTabActionsAutoHideListener
, child
);
247 UIUtil
.addAwtListener(new AWTEventListener() {
248 public void eventDispatched(final AWTEvent event
) {
249 if (mySingleRowLayout
.myMorePopup
!= null) return;
250 processFocusChange();
252 }, AWTEvent
.FOCUS_EVENT_MASK
, child
);
254 myDragHelper
= new DragHelper(child
);
255 myDragHelper
.start();
258 if (myProject
!= null && myFocusManager
== IdeFocusManager
.getGlobalInstance()) {
259 myFocusManager
= IdeFocusManager
.getInstance(myProject
);
266 public JBTabs
setNavigationActiondBinding(String prevActionId
, String nextActionId
) {
267 if (myNextAction
!= null) {
268 myNextAction
.reconnect(nextActionId
);
270 if (myPrevAction
!= null) {
271 myPrevAction
.reconnect(prevActionId
);
277 public JBTabs
setNavigationActionsEnabled(boolean enabled
) {
278 myNavigationActionsEnabled
= enabled
;
282 public final boolean isDisposed() {
286 public void dispose() {
288 mySelectedInfo
= null;
290 myAttractions
.clear();
291 myVisibleInfos
.clear();
292 myUiDecorator
= null;
294 myActivePopup
= null;
295 myInfo2Label
.clear();
296 myInfo2Toolbar
.clear();
297 myTabListeners
.clear();
300 void resetTabsCache() {
304 private void processFocusChange() {
305 Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
311 if (owner
== this || SwingUtilities
.isDescendingFrom(owner
, this)) {
319 private void repaintAttractions() {
320 boolean needsUpdate
= false;
321 for (TabInfo each
: myVisibleInfos
) {
322 TabLabel eachLabel
= myInfo2Label
.get(each
);
323 needsUpdate
|= eachLabel
.repaintAttraction();
327 relayout(true, false);
331 public void addNotify() {
336 public void removeNotify() {
337 super.removeNotify();
343 if (myGlassPane
!= null) {
344 myGlassPane
.removeMouseMotionPreprocessor(myTabActionsAutoHideListener
);
349 private void addTimerUpdate() {
350 if (myActionManager
!= null && !myListenerAdded
) {
351 myActionManager
.addTimerListener(500, this);
352 myListenerAdded
= true;
356 private void removeTimerUpdate() {
357 if (myActionManager
!= null && myListenerAdded
) {
358 myActionManager
.removeTimerListener(this);
359 myListenerAdded
= false;
363 void setTestMode(final boolean testMode
) {
364 myTestMode
= testMode
;
367 public void layoutComp(SingleRowPassInfo data
, int deltaX
, int deltaY
, int deltaWidth
, int deltaHeight
) {
368 if (data
.hToolbar
!= null) {
369 final int toolbarHeight
= data
.hToolbar
.getPreferredSize().height
;
370 final Rectangle compRect
= layoutComp(deltaX
, toolbarHeight
+ deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
371 layout(data
.hToolbar
, compRect
.x
, compRect
.y
- toolbarHeight
, compRect
.width
, toolbarHeight
);
373 else if (data
.vToolbar
!= null) {
374 final int toolbarWidth
= data
.vToolbar
.getPreferredSize().width
;
375 final Rectangle compRect
= layoutComp(toolbarWidth
+ deltaX
, deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
376 layout(data
.vToolbar
, compRect
.x
- toolbarWidth
, compRect
.y
, toolbarWidth
, compRect
.height
);
379 layoutComp(deltaX
, deltaY
, data
.comp
, deltaWidth
, deltaHeight
);
383 class TabActionsAutoHideListener
extends MouseMotionAdapter
{
385 private TabLabel myCurrentOverLabel
;
386 private Point myLastOverPoint
;
389 public void mouseMoved(final MouseEvent e
) {
390 if (!myTabLabelActionsAutoHide
) return;
392 final Point point
= SwingUtilities
.convertPoint(e
.getComponent(), e
.getX(), e
.getY(), JBTabsImpl
.this);
393 myLastOverPoint
= point
;
397 void processMouseOver() {
398 if (!myTabLabelActionsAutoHide
) return;
400 if (myLastOverPoint
== null) return;
402 if (myLastOverPoint
.x
>= 0 && myLastOverPoint
.x
< getWidth() && myLastOverPoint
.y
> 0 && myLastOverPoint
.y
< getHeight()) {
403 final TabLabel label
= myInfo2Label
.get(_findInfo(myLastOverPoint
, true));
405 if (myCurrentOverLabel
!= null) {
406 myCurrentOverLabel
.toggleShowActions(false);
408 label
.toggleShowActions(true);
409 myCurrentOverLabel
= label
;
414 if (myCurrentOverLabel
!= null) {
415 myCurrentOverLabel
.toggleShowActions(false);
416 myCurrentOverLabel
= null;
422 public ModalityState
getModalityState() {
423 return ModalityState
.stateForComponent(this);
427 updateTabActions(false);
430 public void updateTabActions(final boolean validateNow
) {
431 final Ref
<Boolean
> changed
= new Ref
<Boolean
>(Boolean
.FALSE
);
432 for (final TabInfo eachInfo
: myInfo2Label
.keySet()) {
433 updateTab(new Runnable() {
435 final boolean changes
= myInfo2Label
.get(eachInfo
).updateTabActions();
436 changed
.set(changed
.get().booleanValue() || changes
);
441 if (changed
.get().booleanValue()) {
444 paintImmediately(0, 0, getWidth(), getHeight());
449 private void showMorePopup(final MouseEvent e
) {
450 mySingleRowLayout
.myMorePopup
= new JPopupMenu();
451 for (final TabInfo each
: myVisibleInfos
) {
452 final JCheckBoxMenuItem item
= new JCheckBoxMenuItem(each
.getText());
453 mySingleRowLayout
.myMorePopup
.add(item
);
454 if (getSelectedInfo() == each
) {
455 item
.setSelected(true);
457 item
.addActionListener(new ActionListener() {
458 public void actionPerformed(final ActionEvent e
) {
464 mySingleRowLayout
.myMorePopup
.addPopupMenuListener(new PopupMenuListener() {
465 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
468 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
469 mySingleRowLayout
.myMorePopup
= null;
472 public void popupMenuCanceled(final PopupMenuEvent e
) {
473 mySingleRowLayout
.myMorePopup
= null;
477 mySingleRowLayout
.myMorePopup
.show(this, e
.getX(), e
.getY());
481 private JComponent
getToFocus() {
482 final TabInfo info
= getSelectedInfo();
484 if (info
== null) return null;
486 JComponent toFocus
= null;
488 if (isRequestFocusOnLastFocusedComponent() && info
.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
489 toFocus
= info
.getLastFocusOwner();
492 if (toFocus
== null && info
.getPreferredFocusableComponent() == null) {
497 if (toFocus
== null) {
498 toFocus
= info
.getPreferredFocusableComponent();
499 final JComponent policyToFocus
= myFocusManager
.getFocusTargetFor(toFocus
);
500 if (policyToFocus
!= null) {
501 toFocus
= policyToFocus
;
508 public void requestFocus() {
509 final JComponent toFocus
= getToFocus();
510 if (toFocus
!= null) {
511 toFocus
.requestFocus();
514 super.requestFocus();
518 public boolean requestFocusInWindow() {
519 final JComponent toFocus
= getToFocus();
520 if (toFocus
!= null) {
521 return toFocus
.requestFocusInWindow();
524 return super.requestFocusInWindow();
528 private JBTabsImpl
findTabs(Component c
) {
529 Component eachParent
= c
;
530 while (eachParent
!= null) {
531 if (eachParent
instanceof JBTabsImpl
) {
532 return (JBTabsImpl
)eachParent
;
534 eachParent
= eachParent
.getParent();
542 public TabInfo
addTab(TabInfo info
, int index
) {
543 if (getTabs().contains(info
)) {
544 return getTabs().get(getTabs().indexOf(info
));
547 info
.getChangeSupport().addPropertyChangeListener(this);
548 final TabLabel label
= new TabLabel(this, info
);
549 myInfo2Label
.put(info
, label
);
552 myVisibleInfos
.add(info
);
554 else if (index
> myVisibleInfos
.size() - 1) {
555 myVisibleInfos
.add(info
);
558 myVisibleInfos
.add(index
, info
);
566 updateSideComponent(info
);
567 updateTabActions(info
);
573 updateAll(false, false);
575 if (info
.isHidden()) {
579 if (getTabCount() == 1) {
580 fireBeforeSelectionChanged(null, info
);
581 fireSelectionChanged(null, info
);
589 public TabInfo
addTab(TabInfo info
) {
590 return addTab(info
, -1);
593 public ActionGroup
getPopupGroup() {
594 return myPopupGroup
!= null ? myPopupGroup
.get() : null;
597 public String
getPopupPlace() {
601 public JBTabs
setPopupGroup(@NotNull final ActionGroup popupGroup
, @NotNull String place
, final boolean addNavigationGroup
) {
602 return setPopupGroup(new Getter
<ActionGroup
>() {
603 public ActionGroup
get() {
606 }, place
, addNavigationGroup
);
609 public JBTabs
setPopupGroup(@NotNull final Getter
<ActionGroup
> popupGroup
,
610 @NotNull final String place
,
611 final boolean addNavigationGroup
) {
612 myPopupGroup
= popupGroup
;
613 myPopupPlace
= place
;
614 myAddNavigationGroup
= addNavigationGroup
;
618 private void updateAll(final boolean forcedRelayout
, final boolean now
) {
619 mySelectedInfo
= getSelectedInfo();
620 updateContainer(forcedRelayout
, now
);
623 updateTabActions(false);
627 private boolean isMyChildIsFocusedNow() {
628 final Component owner
= getFocusOwner();
629 if (owner
== null) return false;
632 if (mySelectedInfo
!= null) {
633 if (!SwingUtilities
.isDescendingFrom(owner
, mySelectedInfo
.getComponent())) return false;
636 return SwingUtilities
.isDescendingFrom(owner
, this);
640 private static JComponent
getFocusOwner() {
641 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
642 return (JComponent
)(owner
instanceof JComponent ? owner
: null);
645 public ActionCallback
select(@NotNull TabInfo info
, boolean requestFocus
) {
646 return _setSelected(info
, requestFocus
);
649 private ActionCallback
_setSelected(final TabInfo info
, final boolean requestFocus
) {
650 if (mySelectedInfo
!= null && mySelectedInfo
.equals(info
)) {
652 return new ActionCallback
.Done();
655 return requestFocus(getToFocus());
659 if (myRequestFocusOnLastFocusedComponent
&& mySelectedInfo
!= null) {
660 if (isMyChildIsFocusedNow()) {
661 mySelectedInfo
.setLastFocusOwner(getFocusOwner());
665 TabInfo oldInfo
= mySelectedInfo
;
666 mySelectedInfo
= info
;
667 final TabInfo newInfo
= getSelectedInfo();
669 fireBeforeSelectionChanged(oldInfo
, newInfo
);
671 updateContainer(false, true);
673 fireSelectionChanged(oldInfo
, newInfo
);
676 final JComponent toFocus
= getToFocus();
677 if (myProject
!= null && toFocus
!= null) {
678 final ActionCallback result
= new ActionCallback();
679 requestFocus(toFocus
).doWhenProcessed(new Runnable() {
682 result
.setRejected();
685 removeDeferred().notifyWhenDone(result
);
693 return removeDeferred();
697 return removeDeferred();
701 private void fireBeforeSelectionChanged(TabInfo oldInfo
, TabInfo newInfo
) {
702 if (oldInfo
!= newInfo
) {
703 for (TabsListener eachListener
: myTabListeners
) {
704 eachListener
.beforeSelectionChanged(oldInfo
, newInfo
);
709 private void fireSelectionChanged(TabInfo oldInfo
, TabInfo newInfo
) {
710 if (oldInfo
!= newInfo
) {
711 for (TabsListener eachListener
: myTabListeners
) {
712 if (eachListener
!= null) {
713 eachListener
.selectionChanged(oldInfo
, newInfo
);
719 private ActionCallback
requestFocus(final JComponent toFocus
) {
720 if (toFocus
== null) return new ActionCallback
.Done();
723 toFocus
.requestFocus();
724 return new ActionCallback
.Done();
727 return myFocusManager
.requestFocus(new FocusCommand
.ByComponent(toFocus
), true);
730 private ActionCallback
removeDeferred() {
731 final ActionCallback callback
= new ActionCallback();
733 final long executionRequest
= ++myRemoveDefferredRequest
;
735 final Runnable onDone
= new Runnable() {
737 if (myRemoveDefferredRequest
== executionRequest
) {
745 myFocusManager
.doWhenFocusSettlesDown(onDone
);
750 private void queueForRemove(Component c
) {
751 if (c
instanceof JComponent
) {
752 addToDeferredRemove(c
);
759 private void unqueueFromRemove(Component c
) {
760 myDeferredToRemove
.remove(c
);
763 private void removeDeferredNow() {
764 for (Component each
: myDeferredToRemove
.keySet()) {
765 if (each
!= null && each
.getParent() == this) {
769 myDeferredToRemove
.clear();
772 private void printRemoveInfo(final Component each
) {
773 TabInfo removingInfo
= null;
774 final List
<TabInfo
> all
= getTabs();
775 for (TabInfo eachInfo
: all
) {
776 if (eachInfo
.getComponent() == each
) {
777 removingInfo
= eachInfo
;
782 //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each));
785 public void propertyChange(final PropertyChangeEvent evt
) {
786 final TabInfo tabInfo
= (TabInfo
)evt
.getSource();
787 if (TabInfo
.ACTION_GROUP
.equals(evt
.getPropertyName())) {
788 updateSideComponent(tabInfo
);
789 relayout(false, false);
791 else if (TabInfo
.COMPONENT
.equals(evt
.getPropertyName())) {
792 relayout(true, false);
794 else if (TabInfo
.TEXT
.equals(evt
.getPropertyName())) {
797 else if (TabInfo
.ICON
.equals(evt
.getPropertyName())) {
800 else if (TabInfo
.TAB_COLOR
.equals(evt
.getPropertyName())) {
801 updateColor(tabInfo
);
803 else if (TabInfo
.ALERT_STATUS
.equals(evt
.getPropertyName())) {
804 boolean start
= ((Boolean
)evt
.getNewValue()).booleanValue();
805 updateAttraction(tabInfo
, start
);
807 else if (TabInfo
.TAB_ACTION_GROUP
.equals(evt
.getPropertyName())) {
808 updateTabActions(tabInfo
);
809 relayout(false, false);
811 else if (TabInfo
.HIDDEN
.equals(evt
.getPropertyName())) {
813 relayout(false, false);
815 else if (TabInfo
.ENABLED
.equals(evt
.getPropertyName())) {
820 private void updateEnabling() {
821 final List
<TabInfo
> all
= getTabs();
822 for (TabInfo each
: all
) {
823 final TabLabel eachLabel
= myInfo2Label
.get(each
);
824 eachLabel
.setTabEnabled(each
.isEnabled());
827 final TabInfo selected
= getSelectedInfo();
828 if (selected
!= null && !selected
.isEnabled()) {
829 final TabInfo toSelect
= getToSelectOnRemoveOf(selected
);
830 if (toSelect
!= null) {
831 select(toSelect
, myFocusManager
.getFocusedDescendantFor(this) != null);
836 private void updateHiding() {
837 boolean update
= false;
839 Iterator
<TabInfo
> visible
= myVisibleInfos
.iterator();
840 while (visible
.hasNext()) {
841 TabInfo each
= visible
.next();
842 if (each
.isHidden() && !myHiddenInfos
.containsKey(each
)) {
843 myHiddenInfos
.put(each
, myVisibleInfos
.indexOf(each
));
850 Iterator
<TabInfo
> hidden
= myHiddenInfos
.keySet().iterator();
851 while (hidden
.hasNext()) {
852 TabInfo each
= hidden
.next();
853 if (!each
.isHidden() && myHiddenInfos
.containsKey(each
)) {
854 myVisibleInfos
.add(getIndexInVisibleArray(each
), each
);
863 if (mySelectedInfo
!= null && myHiddenInfos
.containsKey(mySelectedInfo
)) {
864 mySelectedInfo
= getToSelectOnRemoveOf(mySelectedInfo
);
866 updateAll(true, false);
870 private int getIndexInVisibleArray(TabInfo each
) {
871 Integer index
= myHiddenInfos
.get(each
);
873 index
= Integer
.valueOf(myVisibleInfos
.size());
876 if (index
> myVisibleInfos
.size()) {
877 index
= myVisibleInfos
.size();
880 if (index
.intValue() < 0) {
884 return index
.intValue();
887 private void updateIcon(final TabInfo tabInfo
) {
888 updateTab(new Runnable() {
890 myInfo2Label
.get(tabInfo
).setIcon(tabInfo
.getIcon());
895 private void updateColor(final TabInfo tabInfo
) {
896 myInfo2Label
.get(tabInfo
).setInactiveStateImage(null);
898 updateTab(new Runnable() {
905 private void updateTab(Runnable update
, TabInfo info
) {
906 final TabLabel label
= myInfo2Label
.get(info
);
908 if (label
.getRootPane() != null) {
909 if (label
.isValid()) {
913 revalidateAndRepaint(false);
918 void revalidateAndRepaint(final boolean layoutNow
) {
920 if (myVisibleInfos
.isEmpty()) {
922 final Component nonOpaque
= UIUtil
.findUltimateParent(this);
923 if (nonOpaque
!= null && getParent() != null) {
924 final Rectangle toRepaint
= SwingUtilities
.convertRectangle(getParent(), getBounds(), nonOpaque
);
925 nonOpaque
.repaint(toRepaint
.x
, toRepaint
.y
, toRepaint
.width
, toRepaint
.height
);
943 private void updateAttraction(final TabInfo tabInfo
, boolean start
) {
945 myAttractions
.add(tabInfo
);
948 myAttractions
.remove(tabInfo
);
949 tabInfo
.setBlinkCount(0);
952 if (start
&& !myAnimator
.isRunning()) {
955 else if (!start
&& myAttractions
.isEmpty()) {
956 myAnimator
.suspend();
957 repaintAttractions();
961 private void updateText(final TabInfo tabInfo
) {
962 updateTab(new Runnable() {
964 final TabLabel label
= myInfo2Label
.get(tabInfo
);
965 label
.setText(tabInfo
.getColoredText());
966 label
.setToolTipText(tabInfo
.getTooltipText());
971 private void updateSideComponent(final TabInfo tabInfo
) {
972 final Toolbar old
= myInfo2Toolbar
.get(tabInfo
);
977 final Toolbar toolbar
= createToolbarComponent(tabInfo
);
978 myInfo2Toolbar
.put(tabInfo
, toolbar
);
982 private void updateTabActions(final TabInfo info
) {
983 myInfo2Label
.get(info
).setTabActions(info
.getTabLabelActions());
987 public TabInfo
getSelectedInfo() {
988 if (!myVisibleInfos
.contains(mySelectedInfo
)) {
989 mySelectedInfo
= null;
991 return mySelectedInfo
!= null ? mySelectedInfo
: !myVisibleInfos
.isEmpty() ? myVisibleInfos
.get(0) : null;
995 private TabInfo
getToSelectOnRemoveOf(TabInfo info
) {
996 if (!myVisibleInfos
.contains(info
)) return null;
997 if (mySelectedInfo
!= info
) return null;
999 if (myVisibleInfos
.size() == 1) return null;
1001 int index
= myVisibleInfos
.indexOf(info
);
1003 TabInfo result
= null;
1005 result
= findEnabledBackward(index
- 1);
1008 if (result
== null) {
1009 result
= findEnabledForward(index
+ 1);
1015 private TabInfo
findEnabledForward(int from
) {
1017 while (index
< myVisibleInfos
.size() && index
>= 0) {
1018 final TabInfo each
= myVisibleInfos
.get(index
);
1019 if (each
.isEnabled()) return each
;
1026 private TabInfo
findEnabledBackward(int from
) {
1028 while (index
>= 0 && from
< myVisibleInfos
.size()) {
1029 final TabInfo each
= myVisibleInfos
.get(index
);
1030 if (each
.isEnabled()) return each
;
1037 protected Toolbar
createToolbarComponent(final TabInfo tabInfo
) {
1038 return new Toolbar(this, tabInfo
);
1042 public TabInfo
getTabAt(final int tabIndex
) {
1043 return getTabs().get(tabIndex
);
1047 public List
<TabInfo
> getTabs() {
1048 if (myAllTabs
!= null) return myAllTabs
;
1050 ArrayList
<TabInfo
> result
= new ArrayList
<TabInfo
>();
1051 result
.addAll(myVisibleInfos
);
1053 for (TabInfo each
: myHiddenInfos
.keySet()) {
1054 result
.add(getIndexInVisibleArray(each
), each
);
1062 public TabInfo
getTargetInfo() {
1063 return myPopupInfo
!= null ? myPopupInfo
: getSelectedInfo();
1066 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
1069 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
1073 public void popupMenuCanceled(final PopupMenuEvent e
) {
1077 private void resetPopup() {
1078 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
1079 SwingUtilities
.invokeLater(new Runnable() {
1086 public void setPaintBlocked(boolean blocked
, final boolean takeSnapshot
) {
1087 if (blocked
&& !myPaintBlocked
) {
1089 myImage
= new BufferedImage(getWidth(), getHeight(), BufferedImage
.TYPE_INT_ARGB
);
1090 final Graphics2D g
= myImage
.createGraphics();
1096 myPaintBlocked
= blocked
;
1098 if (!myPaintBlocked
) {
1099 if (myImage
!= null) {
1109 private void addToDeferredRemove(final Component c
) {
1110 if (!myDeferredToRemove
.containsKey(c
)) {
1111 myDeferredToRemove
.put(c
, c
);
1115 public boolean isToDrawBorderIfTabsHidden() {
1116 return myToDrawBorderIfTabsHidden
;
1120 public JBTabsPresentation
setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden
) {
1121 myToDrawBorderIfTabsHidden
= toDrawBorderIfTabsHidden
;
1126 public JBTabs
getJBTabs() {
1130 public static class Toolbar
extends JPanel
{
1131 private final JBTabsImpl myTabs
;
1132 private final TabInfo myInfo
;
1134 public Toolbar(JBTabsImpl tabs
, TabInfo info
) {
1138 setLayout(new BorderLayout());
1140 final ActionGroup group
= info
.getGroup();
1141 final JComponent side
= info
.getSideComponent();
1143 if (group
!= null && myTabs
.myActionManager
!= null) {
1144 final String place
= info
.getPlace();
1145 ActionToolbar toolbar
=
1146 myTabs
.myActionManager
.createActionToolbar(place
!= null ? place
: ActionPlaces
.UNKNOWN
, group
, myTabs
.myHorizontalSide
);
1147 toolbar
.setTargetComponent(info
.getActionsContextComponent());
1148 final JComponent actionToolbar
= toolbar
.getComponent();
1149 add(actionToolbar
, BorderLayout
.CENTER
);
1153 if (group
!= null) {
1154 add(side
, BorderLayout
.EAST
);
1157 add(side
, BorderLayout
.CENTER
);
1162 public boolean isEmpty() {
1163 return getComponentCount() == 0;
1168 public void doLayout() {
1170 myHeaderFitSize
= computeHeaderFitSize();
1172 final Collection
<TabLabel
> labels
= myInfo2Label
.values();
1173 for (TabLabel each
: labels
) {
1174 each
.setTabActionsAutoHide(myTabLabelActionsAutoHide
);
1178 if (isSingleRow()) {
1179 myLastLayoutPass
= mySingleRowLayout
.layoutSingleRow();
1180 myTableLayout
.myLastTableLayout
= null;
1183 myLastLayoutPass
= myTableLayout
.layoutTable();
1184 mySingleRowLayout
.myLastSingRowLayout
= null;
1187 if (isStealthModeEffective() && !isHideTabs()) {
1188 final TabLabel label
= getSelectedLabel();
1189 final Rectangle bounds
= label
.getBounds();
1190 final Insets insets
= getLayoutInsets();
1191 layout(label
, insets
.left
, bounds
.y
, getWidth() - insets
.right
- insets
.left
, bounds
.height
);
1195 moveDraggedTabLabel();
1197 myTabActionsAutoHideListener
.processMouseOver();
1200 myForcedRelayout
= false;
1203 applyResetComponents();
1206 void moveDraggedTabLabel() {
1207 if (myDragHelper
!= null && myDragHelper
.myDragRec
!= null) {
1208 final TabLabel selectedLabel
= myInfo2Label
.get(getSelectedInfo());
1209 if (selectedLabel
!= null) {
1210 final Rectangle bounds
= selectedLabel
.getBounds();
1211 if (isHorizontalTabs()) {
1212 selectedLabel
.setBounds(myDragHelper
.myDragRec
.x
, bounds
.y
, bounds
.width
, bounds
.height
);
1215 selectedLabel
.setBounds(bounds
.x
, myDragHelper
.myDragRec
.y
, bounds
.width
, bounds
.height
);
1221 private Dimension
computeHeaderFitSize() {
1222 final Max max
= computeMaxSize();
1224 if (myPosition
== JBTabsPosition
.top
|| myPosition
== JBTabsPosition
.bottom
) {
1225 return new Dimension(getSize().width
, myHorizontalSide ? Math
.max(max
.myLabel
.height
, max
.myToolbar
.height
) : max
.myLabel
.height
);
1228 return new Dimension(max
.myLabel
.width
+ (myHorizontalSide ?
0 : max
.myToolbar
.width
), getSize().height
);
1232 public Rectangle
layoutComp(int componentX
, int componentY
, final JComponent comp
, int deltaWidth
, int deltaHeight
) {
1233 final Insets insets
= getLayoutInsets();
1235 final Insets border
= isHideTabs() ?
new Insets(0, 0, 0, 0) : myBorder
.getEffectiveBorder();
1236 final boolean noTabsVisible
= isStealthModeEffective() || isHideTabs();
1238 if (noTabsVisible
) {
1239 border
.top
= getBorder(-1);
1240 border
.bottom
= getBorder(-1);
1241 border
.left
= getBorder(-1);
1242 border
.right
= getBorder(-1);
1245 final Insets inner
= getInnerInsets();
1246 border
.top
+= inner
.top
;
1247 border
.bottom
+= inner
.bottom
;
1248 border
.left
+= inner
.left
;
1249 border
.right
+= inner
.right
;
1252 int x
= insets
.left
+ componentX
+ border
.left
;
1253 int y
= insets
.top
+ componentY
+ border
.top
;
1254 int width
= getWidth() - insets
.left
- insets
.right
- componentX
- border
.left
- border
.right
;
1255 int height
= getHeight() - insets
.top
- insets
.bottom
- componentY
- border
.top
- border
.bottom
;
1257 if (!noTabsVisible
) {
1258 width
+= deltaWidth
;
1259 height
+= deltaHeight
;
1262 return layout(comp
, x
, y
, width
, height
);
1266 public JBTabsPresentation
setInnerInsets(final Insets innerInsets
) {
1267 myInnerInsets
= innerInsets
;
1271 public Insets
getInnerInsets() {
1272 return myInnerInsets
;
1275 public Insets
getLayoutInsets() {
1276 Insets insets
= getInsets();
1277 if (insets
== null) {
1278 insets
= new Insets(0, 0, 0, 0);
1283 private int fixInset(int inset
, int addin
) {
1284 return inset
+ addin
;
1288 public int getToolbarInset() {
1289 return getArcSize() + 1;
1292 public void resetLayout(boolean resetLabels
) {
1294 mySingleRowLayout
.myLeftGhost
.reset();
1295 mySingleRowLayout
.myRightGhost
.reset();
1298 for (TabInfo each
: myVisibleInfos
) {
1299 reset(each
, resetLabels
);
1302 for (TabInfo each
: myHiddenInfos
.keySet()) {
1303 reset(each
, resetLabels
);
1306 for (Component eachDeferred
: myDeferredToRemove
.keySet()) {
1307 resetLayout((JComponent
)eachDeferred
);
1311 private void reset(final TabInfo each
, final boolean resetLabels
) {
1312 final JComponent c
= each
.getComponent();
1317 resetLayout(myInfo2Toolbar
.get(each
));
1320 resetLayout(myInfo2Label
.get(each
));
1325 private int getArcSize() {
1329 public int getGhostTabLength() {
1334 protected void paintComponent(final Graphics g
) {
1335 super.paintComponent(g
);
1337 if (myVisibleInfos
.isEmpty()) return;
1339 Graphics2D g2d
= (Graphics2D
)g
;
1341 final GraphicsConfig config
= new GraphicsConfig(g2d
);
1342 config
.setAntialiasing(true);
1345 g2d
.setColor(getBackground());
1346 final Rectangle clip
= g2d
.getClipBounds();
1347 g2d
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);
1349 final TabInfo selected
= getSelectedInfo();
1351 boolean leftGhostExists
= isSingleRow();
1352 boolean rightGhostExists
= isSingleRow();
1354 if (!isStealthModeEffective() && !isHideTabs()) {
1355 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.lastGhostVisible
) {
1356 paintLastGhost(g2d
);
1360 paintNonSelectedTabs(g2d
, leftGhostExists
);
1362 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.firstGhostVisible
) {
1363 paintFirstGhost(g2d
);
1368 config
.setAntialiasing(false);
1370 if (isSideComponentVertical()) {
1371 Toolbar toolbarComp
= myInfo2Toolbar
.get(mySelectedInfo
);
1372 if (toolbarComp
!= null && !toolbarComp
.isEmpty()) {
1373 Rectangle toolBounds
= toolbarComp
.getBounds();
1374 g2d
.setColor(CaptionPanel
.CNT_ACTIVE_COLOR
);
1375 g2d
.drawLine((int)toolBounds
.getMaxX(), toolBounds
.y
, (int)toolBounds
.getMaxX(), (int)toolBounds
.getMaxY() - 1);
1382 private Color
getActiveTabColor(final Color c
) {
1383 final TabInfo info
= getSelectedInfo();
1388 final Color tabColor
= info
.getTabColor();
1389 return tabColor
== null ? c
: tabColor
;
1392 private void paintSelectionAndBorder(Graphics2D g2d
) {
1393 if (getSelectedLabel() == null) return;
1395 final ShapeInfo shapeInfo
= computeSelectedLabelShape();
1396 if (!isHideTabs()) {
1397 g2d
.setColor(getBackground());
1398 g2d
.fill(shapeInfo
.fillPath
.getShape());
1402 int paintTopY
= shapeInfo
.labelTopY
;
1403 int paintBottomY
= shapeInfo
.labelBottomY
;
1404 final boolean paintFocused
= myPaintFocus
&& (myFocused
|| myActivePopup
!= null);
1405 Color bgPreFill
= null;
1407 final Color bgColor
= getActiveTabColor(getActiveTabFillIn());
1408 if (bgColor
== null) {
1409 shapeInfo
.from
= UIUtil
.getFocusedFillColor();
1410 shapeInfo
.to
= UIUtil
.getFocusedFillColor();
1413 bgPreFill
= bgColor
;
1415 paintBottomY
= shapeInfo
.labelTopY
+ shapeInfo
.labelPath
.deltaY(getArcSize() - 2);
1416 shapeInfo
.from
= UIUtil
.toAlpha(UIUtil
.getFocusedFillColor(), alpha
);
1417 shapeInfo
.to
= UIUtil
.toAlpha(getActiveTabFillIn(), alpha
);
1421 final Color bgColor
= getActiveTabColor(getActiveTabFillIn());
1422 if (isPaintFocus()) {
1423 if (bgColor
== null) {
1425 shapeInfo
.from
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound().brighter(), alpha
);
1426 shapeInfo
.to
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound(), alpha
);
1430 shapeInfo
.from
= UIUtil
.toAlpha(bgColor
, alpha
);
1431 shapeInfo
.to
= UIUtil
.toAlpha(bgColor
, alpha
);
1436 final Color tabColor
= getActiveTabColor(null);
1437 shapeInfo
.from
= UIUtil
.toAlpha(tabColor
== null ? Color
.white
: tabColor
, alpha
);
1438 shapeInfo
.to
= UIUtil
.toAlpha(tabColor
== null ? Color
.white
: tabColor
, alpha
);
1442 if (!isHideTabs()) {
1443 if (bgPreFill
!= null) {
1444 g2d
.setColor(bgPreFill
);
1445 g2d
.fill(shapeInfo
.fillPath
.getShape());
1448 final Line2D
.Float gradientLine
=
1449 shapeInfo
.fillPath
.transformLine(shapeInfo
.fillPath
.getX(), paintTopY
, shapeInfo
.fillPath
.getX(), paintBottomY
);
1452 g2d
.setPaint(new GradientPaint((float)gradientLine
.getX1(), (float)gradientLine
.getY1(),
1453 shapeInfo
.fillPath
.transformY1(shapeInfo
.from
, shapeInfo
.to
), (float)gradientLine
.getX2(),
1454 (float)gradientLine
.getY2(), shapeInfo
.fillPath
.transformY1(shapeInfo
.to
, shapeInfo
.from
)));
1455 g2d
.fill(shapeInfo
.fillPath
.getShape());
1458 final Color tabColor
= getActiveTabColor(null);
1459 Color borderColor
= tabColor
== null ? UIUtil
.getBoundsColor(paintFocused
) : tabColor
.darker();
1460 g2d
.setColor(borderColor
);
1462 if (!isHideTabs()) {
1463 g2d
.draw(shapeInfo
.path
.getShape());
1466 paintBorder(g2d
, shapeInfo
, borderColor
);
1469 private ShapeInfo
computeSelectedLabelShape() {
1470 final ShapeInfo shape
= new ShapeInfo();
1472 shape
.path
= getEffectiveLayout().createShapeTransform(getSize());
1473 shape
.insets
= shape
.path
.transformInsets(getLayoutInsets());
1474 shape
.labelPath
= shape
.path
.createTransform(getSelectedLabel().getBounds());
1476 shape
.labelBottomY
= shape
.labelPath
.getMaxY() + shape
.labelPath
.deltaY(1);
1477 shape
.labelTopY
= shape
.labelPath
.getY();
1478 shape
.labelLeftX
= shape
.labelPath
.getX();
1479 shape
.labelRightX
= shape
.labelPath
.getX() + shape
.labelPath
.deltaX(shape
.labelPath
.getWidth());
1481 shape
.path
.moveTo(shape
.insets
.left
, shape
.labelBottomY
);
1482 shape
.path
.lineTo(shape
.labelLeftX
, shape
.labelBottomY
);
1483 shape
.path
.lineTo(shape
.labelLeftX
, shape
.labelTopY
+ shape
.labelPath
.deltaY(getArcSize()));
1484 shape
.path
.quadTo(shape
.labelLeftX
, shape
.labelTopY
, shape
.labelLeftX
+ shape
.labelPath
.deltaX(getArcSize()), shape
.labelTopY
);
1486 int lastX
= shape
.path
.getWidth() - shape
.path
.deltaX(shape
.insets
.right
+ 1);
1488 if (isStealthModeEffective()) {
1489 shape
.path
.lineTo(lastX
- shape
.path
.deltaX(getArcSize()), shape
.labelTopY
);
1490 shape
.path
.quadTo(lastX
, shape
.labelTopY
, lastX
, shape
.labelTopY
+ shape
.path
.deltaY(getArcSize()));
1491 shape
.path
.lineTo(lastX
, shape
.labelBottomY
);
1494 shape
.path
.lineTo(shape
.labelRightX
- shape
.path
.deltaX(getArcSize()), shape
.labelTopY
);
1495 shape
.path
.quadTo(shape
.labelRightX
, shape
.labelTopY
, shape
.labelRightX
, shape
.labelTopY
+ shape
.path
.deltaY(getArcSize()));
1496 if (myLastLayoutPass
.hasCurveSpaceFor(getSelectedInfo())) {
1497 shape
.path
.lineTo(shape
.labelRightX
, shape
.labelBottomY
- shape
.path
.deltaY(getArcSize()));
1498 shape
.path
.quadTo(shape
.labelRightX
, shape
.labelBottomY
, shape
.labelRightX
+ shape
.path
.deltaX(getArcSize()), shape
.labelBottomY
);
1501 shape
.path
.lineTo(shape
.labelRightX
, shape
.labelBottomY
);
1505 shape
.path
.lineTo(lastX
, shape
.labelBottomY
);
1507 if (isStealthModeEffective()) {
1508 shape
.path
.closePath();
1511 shape
.fillPath
= shape
.path
.copy();
1512 if (!isHideTabs()) {
1513 shape
.fillPath
.lineTo(lastX
, shape
.labelBottomY
+ shape
.fillPath
.deltaY(1));
1514 shape
.fillPath
.lineTo(shape
.labelLeftX
, shape
.labelBottomY
+ shape
.fillPath
.deltaY(1));
1515 shape
.fillPath
.closePath();
1520 private TabLabel
getSelectedLabel() {
1521 return myInfo2Label
.get(getSelectedInfo());
1524 static class ShapeInfo
{
1525 ShapeTransform path
;
1526 ShapeTransform fillPath
;
1527 ShapeTransform labelPath
;
1538 private void paintFirstGhost(Graphics2D g2d
) {
1539 final ShapeTransform path
= getEffectiveLayout().createShapeTransform(mySingleRowLayout
.myLastSingRowLayout
.firstGhost
);
1541 int topX
= path
.getX() + path
.deltaX(getCurveArc());
1542 int topY
= path
.getY() + path
.deltaY(getSelectionTabVShift());
1543 int bottomX
= path
.getMaxX() + path
.deltaX(1);
1544 int bottomY
= path
.getMaxY() + path
.deltaY(1);
1546 path
.moveTo(topX
, topY
);
1548 final boolean isLeftFromSelection
= mySingleRowLayout
.myLastSingRowLayout
.toLayout
.indexOf(getSelectedInfo()) == 0;
1550 if (isLeftFromSelection
) {
1551 path
.lineTo(bottomX
, topY
);
1554 path
.lineTo(bottomX
- getArcSize(), topY
);
1555 path
.quadTo(bottomX
, topY
, bottomX
, topY
+ path
.deltaY(getArcSize()));
1558 path
.lineTo(bottomX
, bottomY
);
1559 path
.lineTo(topX
, bottomY
);
1561 path
.quadTo(topX
- path
.deltaX(getCurveArc() * 2 - 1), bottomY
- path
.deltaY(Math
.abs(bottomY
- topY
) / 4), topX
,
1562 bottomY
- path
.deltaY(Math
.abs(bottomY
- topY
) / 2));
1564 path
.quadTo(topX
+ path
.deltaX(getCurveArc() - 1), topY
+ path
.deltaY(Math
.abs(bottomY
- topY
) / 4), topX
, topY
);
1568 g2d
.setColor(getBackground());
1569 g2d
.fill(path
.getShape());
1571 g2d
.setColor(getBoundsColor());
1572 g2d
.draw(path
.getShape());
1574 g2d
.setColor(getTopBlickColor());
1575 g2d
.drawLine(topX
+ path
.deltaX(1), topY
+ path
.deltaY(1), bottomX
- path
.deltaX(getArcSize()), topY
+ path
.deltaY(1));
1577 g2d
.setColor(getRightBlockColor());
1578 g2d
.drawLine(bottomX
- path
.deltaX(1), topY
+ path
.deltaY(getArcSize()), bottomX
- path
.deltaX(1), bottomY
- path
.deltaY(1));
1581 private void paintLastGhost(Graphics2D g2d
) {
1582 final ShapeTransform path
= getEffectiveLayout().createShapeTransform(mySingleRowLayout
.myLastSingRowLayout
.lastGhost
);
1584 int topX
= path
.getX() - path
.deltaX(getArcSize());
1585 int topY
= path
.getY() + path
.deltaY(getSelectionTabVShift());
1586 int bottomX
= path
.getMaxX() - path
.deltaX(getCurveArc());
1587 int bottomY
= path
.getMaxY() + path
.deltaY(1);
1589 path
.moveTo(topX
, topY
);
1590 path
.lineTo(bottomX
, topY
);
1591 path
.quadTo(bottomX
- getCurveArc(), topY
+ (bottomY
- topY
) / 4, bottomX
, topY
+ (bottomY
- topY
) / 2);
1592 path
.quadTo(bottomX
+ getCurveArc(), bottomY
- (bottomY
- topY
) / 4, bottomX
, bottomY
);
1593 path
.lineTo(topX
, bottomY
);
1597 g2d
.setColor(getBackground());
1598 g2d
.fill(path
.getShape());
1600 g2d
.setColor(getBoundsColor());
1601 g2d
.draw(path
.getShape());
1603 g2d
.setColor(getTopBlickColor());
1604 g2d
.drawLine(topX
, topY
+ path
.deltaY(1), bottomX
- path
.deltaX(getCurveArc()), topY
+ path
.deltaY(1));
1607 private int getCurveArc() {
1611 private Color
getBoundsColor() {
1615 private Color
getRightBlockColor() {
1616 return Color
.lightGray
;
1619 private Color
getTopBlickColor() {
1623 private void paintNonSelectedTabs(final Graphics2D g2d
, final boolean leftGhostExists
) {
1624 TabInfo selected
= getSelectedInfo();
1625 if (myLastPaintedSelection
== null || !myLastPaintedSelection
.equals(selected
)) {
1626 List
<TabInfo
> tabs
= getTabs();
1627 for (TabInfo each
: tabs
) {
1628 myInfo2Label
.get(each
).setInactiveStateImage(null);
1632 for (int eachRow
= 0; eachRow
< myLastLayoutPass
.getRowCount(); eachRow
++) {
1633 for (int eachColumn
= myLastLayoutPass
.getColumnCount(eachRow
) - 1; eachColumn
>= 0; eachColumn
--) {
1634 final TabInfo each
= myLastLayoutPass
.getTabAt(eachRow
, eachColumn
);
1635 if (getSelectedInfo() == each
) {
1638 paintNonSelected(g2d
, each
, leftGhostExists
);
1642 myLastPaintedSelection
= selected
;
1645 private void paintNonSelected(final Graphics2D g2d
, final TabInfo each
, final boolean leftGhostExists
) {
1646 final TabLabel label
= myInfo2Label
.get(each
);
1647 if (label
.getBounds().width
== 0) return;
1649 int imageInsets
= getArcSize() + 1;
1651 Rectangle bounds
= label
.getBounds();
1653 int x
= bounds
.x
- imageInsets
;
1655 int width
= bounds
.width
+ imageInsets
* 2 + 1;
1656 int height
= bounds
.height
+ getArcSize() + 1;
1658 if (isToBufferPainting()) {
1659 BufferedImage img
= label
.getInactiveStateImage(bounds
);
1662 img
= new BufferedImage(width
, height
, BufferedImage
.TYPE_INT_ARGB
);
1663 Graphics2D imgG2d
= img
.createGraphics();
1664 imgG2d
.addRenderingHints(g2d
.getRenderingHints());
1665 doPaintInactictive(imgG2d
, leftGhostExists
, label
, new Rectangle(imageInsets
, 0, label
.getWidth(), label
.getHeight()));
1669 g2d
.drawImage(img
, x
, y
, width
, height
, null);
1671 label
.setInactiveStateImage(img
);
1673 doPaintInactictive(g2d
, leftGhostExists
, label
, label
.getBounds());
1674 label
.setInactiveStateImage(null);
1678 private boolean isToBufferPainting() {
1679 return Registry
.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint
;
1682 private void doPaintInactictive(Graphics2D g2d
, boolean leftGhostExists
, TabLabel label
, Rectangle effectiveBounds
) {
1683 int tabIndex
= myVisibleInfos
.indexOf(label
.getInfo());
1685 final int arc
= getArcSize();
1686 Color topBlickColor
= getTopBlickColor();
1687 Color rightBlockColor
= getRightBlockColor();
1688 Color boundsColor
= getBoundsColor();
1689 Color backgroundColor
= getBackground();
1691 final Color tabColor
= label
.getInfo().getTabColor();
1692 if (tabColor
!= null) {
1693 backgroundColor
= tabColor
;
1694 boundsColor
= tabColor
.darker();
1695 topBlickColor
= tabColor
.brighter().brighter();
1696 rightBlockColor
= tabColor
;
1699 final TabInfo selected
= getSelectedInfo();
1700 final int selectionTabVShift
= getSelectionTabVShift();
1703 final TabInfo prev
= myLastLayoutPass
.getPreviousFor(myVisibleInfos
.get(tabIndex
));
1704 final TabInfo next
= myLastLayoutPass
.getNextFor(myVisibleInfos
.get(tabIndex
));
1707 boolean firstShowing
= prev
== null;
1708 if (!firstShowing
&& !leftGhostExists
) {
1709 firstShowing
= myInfo2Label
.get(prev
).getBounds().width
== 0;
1712 boolean lastShowing
= next
== null;
1714 lastShowing
= myInfo2Label
.get(next
).getBounds().width
== 0;
1717 boolean leftFromSelection
= selected
!= null && tabIndex
== myVisibleInfos
.indexOf(selected
) - 1;
1719 Rectangle originalBounds
= effectiveBounds
;
1720 final ShapeTransform shape
= getEffectiveLayout().createShapeTransform(originalBounds
);
1722 int leftX
= firstShowing ? shape
.getX() : shape
.getX() - shape
.deltaX(arc
+ 1);
1723 int topY
= shape
.getY() + shape
.deltaY(selectionTabVShift
);
1724 int rigthX
= !lastShowing
&& leftFromSelection ? shape
.getMaxX() + shape
.deltaX(arc
+ 1) : shape
.getMaxX();
1725 int bottomY
= shape
.getMaxY() + shape
.deltaY(1);
1727 shape
.moveTo(leftX
, bottomY
);
1728 shape
.lineTo(leftX
, topY
+ shape
.deltaY(arc
));
1729 shape
.quadTo(leftX
, topY
, leftX
+ shape
.deltaX(arc
), topY
);
1730 shape
.lineTo(rigthX
- shape
.deltaX(arc
), topY
);
1731 shape
.quadTo(rigthX
, topY
, rigthX
, topY
+ shape
.deltaY(arc
));
1732 shape
.lineTo(rigthX
, bottomY
);
1734 if (!isSingleRow()) {
1735 final TablePassInfo info
= myTableLayout
.myLastTableLayout
;
1736 if (!info
.isInSelectionRow(label
.getInfo())) {
1737 shape
.lineTo(rigthX
, bottomY
+ shape
.deltaY(getArcSize()));
1738 shape
.lineTo(leftX
, bottomY
+ shape
.deltaY(getArcSize()));
1739 shape
.lineTo(leftX
, bottomY
);
1745 g2d
.setColor(backgroundColor
);
1746 g2d
.fill(shape
.getShape());
1750 final Line2D
.Float gradientLine
=
1751 shape
.transformLine(0, topY
, 0, topY
+ shape
.deltaY((int) (shape
.getHeight() / 1.5 )));
1753 final GradientPaint gp
=
1754 new GradientPaint(gradientLine
.x1
, gradientLine
.y1
,
1755 shape
.transformY1(backgroundColor
.brighter().brighter(), backgroundColor
),
1756 gradientLine
.x2
, gradientLine
.y2
,
1757 shape
.transformY1(backgroundColor
, backgroundColor
.brighter().brighter()));
1758 final Paint old
= g2d
.getPaint();
1760 g2d
.fill(shape
.getShape());
1763 g2d
.setColor(topBlickColor
);
1765 shape
.transformLine(leftX
+ shape
.deltaX(arc
+ 1), topY
+ shape
.deltaY(1), rigthX
- shape
.deltaX(arc
- 1), topY
+ shape
.deltaY(1)));
1767 g2d
.setColor(rightBlockColor
);
1768 g2d
.draw(shape
.transformLine(rigthX
- shape
.deltaX(1), topY
+ shape
.deltaY(arc
- 1), rigthX
- shape
.deltaX(1), bottomY
));
1770 g2d
.setColor(boundsColor
);
1771 g2d
.draw(shape
.getShape());
1774 public int getSelectionTabVShift() {
1778 private void paintBorder(Graphics2D g2d
, ShapeInfo shape
, final Color borderColor
) {
1780 final ShapeTransform shaper
= shape
.path
.copy().reset();
1782 final Insets paintBorder
= shape
.path
.transformInsets(myBorder
.getEffectiveBorder());
1784 int topY
= shape
.labelPath
.getMaxY() + shape
.labelPath
.deltaY(1);
1786 int bottomY
= topY
+ paintBorder
.top
- 2;
1787 int middleY
= topY
+ (bottomY
- topY
) / 2;
1790 final int boundsX
= shape
.path
.getX() + shape
.path
.deltaX(shape
.insets
.left
);
1793 isHideTabs() ? shape
.path
.getY() + shape
.path
.deltaY(shape
.insets
.top
) : shape
.labelPath
.getMaxY() + shape
.path
.deltaY(1);
1795 final int boundsHeight
= Math
.abs(shape
.path
.getMaxY() - boundsY
) - shape
.insets
.bottom
- paintBorder
.bottom
;
1796 final int boundsWidth
= Math
.abs(shape
.path
.getMaxX() - (shape
.insets
.left
+ shape
.insets
.right
));
1798 if (paintBorder
.top
> 0) {
1800 if (isToDrawBorderIfTabsHidden()) {
1801 g2d
.setColor(borderColor
);
1802 g2d
.fill(shaper
.reset().doRect(boundsX
, boundsY
, boundsWidth
, 1).getShape());
1806 Color tabFillColor
= getActiveTabColor(null);
1807 if (tabFillColor
== null) {
1808 tabFillColor
= shape
.path
.transformY1(shape
.to
, shape
.from
);
1811 g2d
.setColor(tabFillColor
);
1812 g2d
.fill(shaper
.reset().doRect(boundsX
, topY
+ shape
.path
.deltaY(1), boundsWidth
, paintBorder
.top
- 1).getShape());
1814 g2d
.setColor(borderColor
);
1815 if (paintBorder
.top
== 2) {
1816 final Line2D
.Float line
= shape
.path
.transformLine(boundsX
, topY
, boundsX
+ shape
.path
.deltaX(boundsWidth
- 1), topY
);
1818 g2d
.drawLine((int)line
.x1
, (int)line
.y1
, (int)line
.x2
, (int)line
.y2
);
1820 else if (paintBorder
.top
> 2) {
1824 if (myPosition
== JBTabsPosition
.bottom
|| myPosition
== JBTabsPosition
.right
) {
1828 final int topLine
= topY
+ shape
.path
.deltaY(paintBorder
.top
- 1);
1829 g2d
.fill(shaper
.reset().doRect(boundsX
, topLine
+ deltaY
, boundsWidth
- 1, 1).getShape());
1834 g2d
.setColor(borderColor
);
1837 g2d
.fill(shaper
.reset().doRect(boundsX
, Math
.abs(shape
.path
.getMaxY() - shape
.insets
.bottom
- paintBorder
.bottom
), boundsWidth
,
1838 paintBorder
.bottom
).getShape());
1841 g2d
.fill(shaper
.reset().doRect(boundsX
, boundsY
, paintBorder
.left
, boundsHeight
).getShape());
1844 g2d
.fill(shaper
.reset()
1845 .doRect(shape
.path
.getMaxX() - shape
.insets
.right
- paintBorder
.right
, boundsY
, paintBorder
.right
, boundsHeight
).getShape());
1849 public boolean isStealthModeEffective() {
1850 return myStealthTabMode
&& getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition
.top
;
1854 private boolean isNavigationVisible() {
1855 if (myStealthTabMode
&& getTabCount() == 1) return false;
1856 return !myVisibleInfos
.isEmpty();
1860 public void paint(final Graphics g
) {
1861 Rectangle clip
= g
.getClipBounds();
1866 if (myPaintBlocked
) {
1867 if (myImage
!= null) {
1868 g
.drawImage(myImage
, 0, 0, getWidth(), getHeight(), null);
1876 protected void paintChildren(final Graphics g
) {
1877 super.paintChildren(g
);
1879 final GraphicsConfig config
= new GraphicsConfig(g
);
1880 config
.setAntialiasing(true);
1881 paintSelectionAndBorder((Graphics2D
)g
);
1884 final TabLabel selected
= getSelectedLabel();
1885 if (selected
!= null) {
1886 selected
.paintImage(g
);
1889 mySingleRowLayout
.myMoreIcon
.paintIcon(this, g
);
1892 private Max
computeMaxSize() {
1893 Max max
= new Max();
1894 for (TabInfo eachInfo
: myVisibleInfos
) {
1895 final TabLabel label
= myInfo2Label
.get(eachInfo
);
1896 max
.myLabel
.height
= Math
.max(max
.myLabel
.height
, label
.getPreferredSize().height
);
1897 max
.myLabel
.width
= Math
.max(max
.myLabel
.width
, label
.getPreferredSize().width
);
1898 final Toolbar toolbar
= myInfo2Toolbar
.get(eachInfo
);
1899 if (myLayout
.isSideComponentOnTabs() && toolbar
!= null && !toolbar
.isEmpty()) {
1900 max
.myToolbar
.height
= Math
.max(max
.myToolbar
.height
, toolbar
.getPreferredSize().height
);
1901 max
.myToolbar
.width
= Math
.max(max
.myToolbar
.width
, toolbar
.getPreferredSize().width
);
1905 max
.myToolbar
.height
++;
1910 public Dimension
getMinimumSize() {
1911 return computeSize(new Transform
<JComponent
, Dimension
>() {
1912 public Dimension
transform(JComponent component
) {
1913 return component
.getMinimumSize();
1918 public Dimension
getPreferredSize() {
1919 return computeSize(new Transform
<JComponent
, Dimension
>() {
1920 public Dimension
transform(JComponent component
) {
1921 return component
.getPreferredSize();
1926 private Dimension
computeSize(Transform
<JComponent
, Dimension
> transform
, int tabCount
) {
1927 Dimension size
= new Dimension();
1928 for (TabInfo each
: myVisibleInfos
) {
1929 final JComponent c
= each
.getComponent();
1931 final Dimension eachSize
= transform
.transform(c
);
1932 size
.width
= Math
.max(eachSize
.width
, size
.width
);
1933 size
.height
= Math
.max(eachSize
.height
, size
.height
);
1937 addHeaderSize(size
, tabCount
);
1941 private void addHeaderSize(Dimension size
, final int tabsCount
) {
1942 Dimension header
= computeHeaderPreferredSize(tabsCount
);
1944 size
.height
+= header
.height
;
1945 size
.width
+= header
.width
;
1947 final Insets insets
= getLayoutInsets();
1948 size
.width
+= insets
.left
+ insets
.right
+ 1;
1949 size
.height
+= insets
.top
+ insets
.bottom
+ 1;
1952 private Dimension
computeHeaderPreferredSize(int tabsCount
) {
1953 final Iterator
<TabInfo
> infos
= myInfo2Label
.keySet().iterator();
1954 Dimension size
= new Dimension();
1957 final boolean horizontal
= getTabsPosition() == JBTabsPosition
.top
|| getTabsPosition() == JBTabsPosition
.bottom
;
1959 while (infos
.hasNext()) {
1960 final boolean canGrow
= currentTab
< tabsCount
;
1962 TabInfo eachInfo
= infos
.next();
1963 final TabLabel eachLabel
= myInfo2Label
.get(eachInfo
);
1964 final Dimension eachPrefSize
= eachLabel
.getPreferredSize();
1967 size
.width
+= eachPrefSize
.width
;
1969 size
.height
= Math
.max(size
.height
, eachPrefSize
.height
);
1972 size
.width
= Math
.max(size
.width
, eachPrefSize
.width
);
1974 size
.height
+= eachPrefSize
.height
;
1981 if (isSingleRow() && isGhostsAlwaysVisible()) {
1983 size
.width
+= getGhostTabLength() * 2;
1986 size
.height
+= getGhostTabLength() * 2;
1991 size
.height
+= myBorder
.getTabBorderSize();
1994 size
.width
+= myBorder
.getTabBorderSize();
2000 public int getTabCount() {
2001 return getTabs().size();
2005 public JBTabsPresentation
getPresentation() {
2009 public ActionCallback
removeTab(final JComponent component
) {
2010 return removeTab(findInfo(component
));
2013 public ActionCallback
removeTab(final TabInfo info
) {
2014 return removeTab(info
, null);
2017 public ActionCallback
removeTab(final TabInfo info
, @Nullable TabInfo forcedSelectionTranfer
) {
2018 return removeTab(info
, forcedSelectionTranfer
, true);
2021 public ActionCallback
removeTab(final TabInfo info
, @Nullable TabInfo forcedSelectionTranfer
, boolean transferFocus
) {
2022 if (info
== null || !getTabs().contains(info
)) return new ActionCallback
.Done();
2024 final ActionCallback result
= new ActionCallback();
2027 if (forcedSelectionTranfer
== null) {
2028 toSelect
= getToSelectOnRemoveOf(info
);
2031 assert myVisibleInfos
.contains(forcedSelectionTranfer
) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer
;
2032 toSelect
= forcedSelectionTranfer
;
2036 if (toSelect
!= null) {
2037 processRemove(info
, false);
2038 _setSelected(toSelect
, transferFocus
).doWhenProcessed(new Runnable() {
2040 removeDeferred().notifyWhenDone(result
);
2045 processRemove(info
, true);
2046 removeDeferred().notifyWhenDone(result
);
2049 if (myVisibleInfos
.isEmpty()) {
2050 removeDeferredNow();
2053 revalidateAndRepaint(true);
2058 private void processRemove(final TabInfo info
, boolean forcedNow
) {
2059 remove(myInfo2Label
.get(info
));
2060 remove(myInfo2Toolbar
.get(info
));
2062 JComponent tabComponent
= info
.getComponent();
2064 if (!isToDeferRemoveForLater(tabComponent
) || forcedNow
) {
2065 remove(tabComponent
);
2068 queueForRemove(tabComponent
);
2071 myVisibleInfos
.remove(info
);
2072 myHiddenInfos
.remove(info
);
2073 myInfo2Label
.remove(info
);
2074 myInfo2Toolbar
.remove(info
);
2077 updateAll(false, false);
2080 public TabInfo
findInfo(Component component
) {
2081 for (TabInfo each
: getTabs()) {
2082 if (each
.getComponent() == component
) return each
;
2088 public TabInfo
findInfo(String text
) {
2089 if (text
== null) return null;
2091 for (TabInfo each
: getTabs()) {
2092 if (text
.equals(each
.getText())) return each
;
2098 public TabInfo
findInfo(MouseEvent event
) {
2099 return findInfo(event
, false);
2102 private TabInfo
findInfo(final MouseEvent event
, final boolean labelsOnly
) {
2103 final Point point
= SwingUtilities
.convertPoint(event
.getComponent(), event
.getPoint(), this);
2104 return _findInfo(point
, labelsOnly
);
2107 public TabInfo
findInfo(final Object object
) {
2108 for (int i
= 0; i
< getTabCount(); i
++) {
2109 final TabInfo each
= getTabAt(i
);
2110 final Object eachObject
= each
.getObject();
2111 if (eachObject
!= null && eachObject
.equals(object
)) return each
;
2116 public TabInfo
findTabLabelBy(final Point point
) {
2117 return _findInfo(point
, true);
2120 private TabInfo
_findInfo(final Point point
, boolean labelsOnly
) {
2121 Component component
= findComponentAt(point
);
2122 if (component
== null) return null;
2123 while (component
!= this || component
!= null) {
2124 if (component
instanceof TabLabel
) {
2125 return ((TabLabel
)component
).getInfo();
2127 else if (!labelsOnly
) {
2128 final TabInfo info
= findInfo(component
);
2129 if (info
!= null) return info
;
2131 if (component
== null) break;
2132 component
= component
.getParent();
2138 public void removeAllTabs() {
2139 for (TabInfo each
: getTabs()) {
2145 private static class Max
{
2146 Dimension myLabel
= new Dimension();
2147 Dimension myToolbar
= new Dimension();
2150 private void updateContainer(boolean forced
, final boolean layoutNow
) {
2151 final TabLabel selectedLabel
= getSelectedLabel();
2153 for (TabInfo each
: myVisibleInfos
) {
2154 final JComponent eachComponent
= each
.getComponent();
2155 if (getSelectedInfo() == each
&& getSelectedInfo() != null) {
2156 unqueueFromRemove(eachComponent
);
2158 final Container parent
= eachComponent
.getParent();
2159 if (parent
!= null && parent
!= this) {
2160 parent
.remove(eachComponent
);
2163 if (eachComponent
.getParent() == null) {
2168 if (eachComponent
.getParent() == null) continue;
2169 if (isToDeferRemoveForLater(eachComponent
)) {
2170 queueForRemove(eachComponent
);
2173 remove(eachComponent
);
2178 relayout(forced
, layoutNow
);
2181 protected void addImpl(final Component comp
, final Object constraints
, final int index
) {
2182 unqueueFromRemove(comp
);
2184 if (comp
instanceof TabLabel
) {
2185 ((TabLabel
)comp
).apply(myUiDecorator
.getDecoration());
2188 super.addImpl(comp
, constraints
, index
);
2192 private boolean isToDeferRemoveForLater(JComponent c
) {
2193 return c
.getRootPane() != null;
2196 void relayout(boolean forced
, final boolean layoutNow
) {
2197 if (!myForcedRelayout
) {
2198 myForcedRelayout
= forced
;
2200 revalidateAndRepaint(layoutNow
);
2203 ActionManager
getActionManager() {
2204 return myActionManager
;
2207 public TabsBorder
getTabsBorder() {
2212 public JBTabs
addTabMouseMotionListener(@NotNull MouseMotionListener listener
) {
2214 myTabMouseListeners
.add(listener
);
2220 public JBTabs
addTabMouseListener(@NotNull MouseListener listener
) {
2222 myTabMouseListeners
.add(listener
);
2228 public JComponent
getComponent() {
2233 public JBTabs
removeTabMouseListener(@NotNull MouseListener listener
) {
2235 myTabMouseListeners
.remove(listener
);
2240 private void addListeners() {
2241 for (TabInfo eachInfo
: myVisibleInfos
) {
2242 final TabLabel label
= myInfo2Label
.get(eachInfo
);
2243 for (EventListener eachListener
: myTabMouseListeners
) {
2244 if (eachListener
instanceof MouseListener
) {
2245 label
.addMouseListener((MouseListener
)eachListener
);
2247 else if (eachListener
instanceof MouseMotionListener
) {
2248 label
.addMouseMotionListener((MouseMotionListener
)eachListener
);
2257 private void removeListeners() {
2258 for (TabInfo eachInfo
: myVisibleInfos
) {
2259 final TabLabel label
= myInfo2Label
.get(eachInfo
);
2260 for (EventListener eachListener
: myTabMouseListeners
) {
2261 if (eachListener
instanceof MouseListener
) {
2262 label
.removeMouseListener((MouseListener
)eachListener
);
2264 else if (eachListener
instanceof MouseMotionListener
) {
2265 label
.removeMouseMotionListener((MouseMotionListener
)eachListener
);
2274 private void updateListeners() {
2279 public JBTabs
addListener(@NotNull TabsListener listener
) {
2280 myTabListeners
.add(listener
);
2284 public JBTabs
removeListener(@NotNull final TabsListener listener
) {
2285 myTabListeners
.remove(listener
);
2289 protected void onPopup(final TabInfo popupInfo
) {
2292 public void setFocused(final boolean focused
) {
2293 if (myFocused
== focused
) return;
2295 myFocused
= focused
;
2302 public int getIndexOf(@Nullable final TabInfo tabInfo
) {
2303 return myVisibleInfos
.indexOf(tabInfo
);
2306 public boolean isHideTabs() {
2310 public void setHideTabs(final boolean hideTabs
) {
2311 if (isHideTabs() == hideTabs
) return;
2313 myHideTabs
= hideTabs
;
2315 relayout(true, false);
2318 public JBTabsPresentation
setPaintBorder(int top
, int left
, int right
, int bottom
) {
2319 return myBorder
.setPaintBorder(top
, left
, right
, bottom
);
2322 public JBTabsPresentation
setTabSidePaintBorder(int size
) {
2323 return myBorder
.setTabSidePaintBorder(size
);
2326 static int getBorder(int size
) {
2327 return size
== -1 ?
1 : size
;
2330 public boolean isPaintFocus() {
2331 return myPaintFocus
;
2335 public JBTabsPresentation
setAdjustBorders(final boolean adjust
) {
2336 myAdjustBorders
= adjust
;
2341 public JBTabsPresentation
setActiveTabFillIn(@Nullable final Color color
) {
2342 if (!isChanged(myActiveTabFillIn
, color
)) return this;
2344 myActiveTabFillIn
= color
;
2345 revalidateAndRepaint(false);
2349 private boolean isChanged(Object oldObject
, Object newObject
) {
2350 if (oldObject
== null && newObject
== null) return false;
2351 return oldObject
!= null && !oldObject
.equals(newObject
) || newObject
!= null && !newObject
.equals(oldObject
);
2355 public JBTabsPresentation
setTabLabelActionsAutoHide(final boolean autoHide
) {
2356 if (myTabLabelActionsAutoHide
!= autoHide
) {
2357 myTabLabelActionsAutoHide
= autoHide
;
2358 revalidateAndRepaint(false);
2364 public Color
getActiveTabFillIn() {
2365 return myActiveTabFillIn
;
2368 public JBTabsPresentation
setFocusCycle(final boolean root
) {
2369 setFocusCycleRoot(root
);
2374 public JBTabsPresentation
setPaintFocus(final boolean paintFocus
) {
2375 myPaintFocus
= paintFocus
;
2379 private abstract static class BaseNavigationAction
extends AnAction
{
2381 private final ShadowAction myShadow
;
2382 private final ActionManager myActionManager
;
2383 private final JBTabsImpl myTabs
;
2385 protected BaseNavigationAction(final String copyFromID
, JBTabsImpl tabs
, ActionManager mgr
) {
2386 myActionManager
= mgr
;
2388 myShadow
= new ShadowAction(this, myActionManager
.getAction(copyFromID
), tabs
);
2389 Disposer
.register(tabs
, myShadow
);
2390 setEnabledInModalContext(true);
2393 public final void update(final AnActionEvent e
) {
2394 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
2395 e
.getPresentation().setVisible(tabs
!= null);
2396 if (tabs
== null) return;
2398 final int selectedIndex
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
2399 final boolean enabled
= tabs
== myTabs
&& myTabs
.isNavigationVisible() && selectedIndex
>= 0 && myTabs
.myNavigationActionsEnabled
;
2400 e
.getPresentation().setEnabled(enabled
);
2402 _update(e
, tabs
, selectedIndex
);
2406 public void reconnect(String actionId
) {
2407 myShadow
.reconnect(myActionManager
.getAction(actionId
));
2410 protected abstract void _update(AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
);
2412 public final void actionPerformed(final AnActionEvent e
) {
2413 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
2414 if (tabs
== null) return;
2416 final int index
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
2417 if (index
== -1) return;
2418 _actionPerformed(e
, tabs
, index
);
2421 protected abstract void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
);
2424 private static class SelectNextAction
extends BaseNavigationAction
{
2426 private SelectNextAction(JBTabsImpl tabs
, ActionManager mgr
) {
2427 super(IdeActions
.ACTION_NEXT_TAB
, tabs
, mgr
);
2430 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
2431 e
.getPresentation().setEnabled(tabs
.findEnabledForward(selectedIndex
+ 1) != null);
2434 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
2435 tabs
.select(tabs
.findEnabledForward(selectedIndex
+ 1), true);
2439 private static class SelectPreviousAction
extends BaseNavigationAction
{
2440 private SelectPreviousAction(JBTabsImpl tabs
, ActionManager mgr
) {
2441 super(IdeActions
.ACTION_PREVIOUS_TAB
, tabs
, mgr
);
2444 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
2445 e
.getPresentation().setEnabled(tabs
.findEnabledBackward(selectedIndex
- 1) != null);
2448 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
2449 tabs
.select(tabs
.findEnabledBackward(selectedIndex
- 1), true);
2453 private void disposePopupListener() {
2454 if (myActivePopup
!= null) {
2455 myActivePopup
.removePopupMenuListener(myPopupListener
);
2456 myActivePopup
= null;
2460 public JBTabsPresentation
setStealthTabMode(final boolean stealthTabMode
) {
2461 myStealthTabMode
= stealthTabMode
;
2463 relayout(true, false);
2468 public boolean isStealthTabMode() {
2469 return myStealthTabMode
;
2472 public JBTabsPresentation
setSideComponentVertical(final boolean vertical
) {
2473 myHorizontalSide
= !vertical
;
2475 for (TabInfo each
: myVisibleInfos
) {
2476 each
.getChangeSupport().firePropertyChange(TabInfo
.ACTION_GROUP
, "new1", "new2");
2480 relayout(true, false);
2485 public JBTabsPresentation
setSingleRow(boolean singleRow
) {
2486 myLayout
= singleRow ? mySingleRowLayout
: myTableLayout
;
2488 relayout(true, false);
2493 public JBTabsPresentation
setGhostsAlwaysVisible(final boolean visible
) {
2494 myGhostsAlwaysVisible
= visible
;
2496 relayout(true, false);
2501 public boolean isGhostsAlwaysVisible() {
2502 return myGhostsAlwaysVisible
;
2505 public boolean isSingleRow() {
2506 return getEffectiveLayout() == mySingleRowLayout
;
2509 public boolean isSideComponentVertical() {
2510 return !myHorizontalSide
;
2513 private TabLayout
getEffectiveLayout() {
2514 if (myLayout
== myTableLayout
&& getTabsPosition() == JBTabsPosition
.top
) return myTableLayout
;
2515 return mySingleRowLayout
;
2518 public JBTabsPresentation
setUiDecorator(UiDecorator decorator
) {
2519 myUiDecorator
= decorator
== null ? ourDefaultDecorator
: decorator
;
2524 protected void setUI(final ComponentUI newUI
) {
2529 public void updateUI() {
2531 SwingUtilities
.invokeLater(new Runnable() {
2535 revalidateAndRepaint(false);
2540 private void applyDecoration() {
2541 if (myUiDecorator
!= null) {
2542 UiDecorator
.UiDecoration uiDecoration
= myUiDecorator
.getDecoration();
2543 for (TabLabel each
: myInfo2Label
.values()) {
2544 each
.apply(uiDecoration
);
2549 for (TabInfo each
: getTabs()) {
2553 relayout(true, false);
2556 private void adjust(final TabInfo each
) {
2557 if (myAdjustBorders
) {
2558 UIUtil
.removeScrollBorder(each
.getComponent());
2562 public void sortTabs(Comparator
<TabInfo
> comparator
) {
2563 Collections
.sort(myVisibleInfos
, comparator
);
2565 relayout(true, false);
2568 public boolean isRequestFocusOnLastFocusedComponent() {
2569 return myRequestFocusOnLastFocusedComponent
;
2572 public JBTabsPresentation
setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent
) {
2573 myRequestFocusOnLastFocusedComponent
= requestFocusOnLastFocusedComponent
;
2579 public Object
getData(@NonNls final String dataId
) {
2580 if (myDataProvider
!= null) {
2581 final Object value
= myDataProvider
.getData(dataId
);
2582 if (value
!= null) return value
;
2585 return NAVIGATION_ACTIONS_KEY
.is(dataId
) ?
this : null;
2589 public DataProvider
getDataProvider() {
2590 return myDataProvider
;
2593 public JBTabsImpl
setDataProvider(@NotNull final DataProvider dataProvider
) {
2594 myDataProvider
= dataProvider
;
2599 public boolean isSelectionClick(final MouseEvent e
, boolean canBeQuick
) {
2600 if (e
.getClickCount() == 1 || canBeQuick
) {
2601 if (!e
.isPopupTrigger()) {
2602 return e
.getButton() == MouseEvent
.BUTTON1
&& !e
.isControlDown() && !e
.isAltDown() && !e
.isMetaDown();
2610 private static class DefautDecorator
implements UiDecorator
{
2612 public UiDecoration
getDecoration() {
2613 return new UiDecoration(null, new Insets(1, 4, 1, 5));
2617 public static Rectangle
layout(JComponent c
, Rectangle bounds
) {
2618 final Rectangle now
= c
.getBounds();
2619 if (!bounds
.equals(now
)) {
2620 c
.setBounds(bounds
);
2622 c
.putClientProperty(LAYOUT_DONE
, Boolean
.TRUE
);
2627 public static Rectangle
layout(JComponent c
, int x
, int y
, int width
, int height
) {
2628 return layout(c
, new Rectangle(x
, y
, width
, height
));
2631 public static void resetLayout(JComponent c
) {
2632 if (c
== null) return;
2633 c
.putClientProperty(LAYOUT_DONE
, null);
2636 private void applyResetComponents() {
2637 for (int i
= 0; i
< getComponentCount(); i
++) {
2638 final Component each
= getComponent(i
);
2639 if (each
instanceof JComponent
) {
2640 final JComponent jc
= (JComponent
)each
;
2641 final Object done
= jc
.getClientProperty(LAYOUT_DONE
);
2642 if (!Boolean
.TRUE
.equals(done
)) {
2643 layout(jc
, new Rectangle(0, 0, 0, 0));
2651 public JBTabsPresentation
setTabLabelActionsMouseDeadzone(final TimedDeadzone
.Length length
) {
2652 myTabActionsMouseDeadzone
= length
;
2653 final List
<TabInfo
> all
= getTabs();
2654 for (TabInfo each
: all
) {
2655 final TabLabel eachLabel
= myInfo2Label
.get(each
);
2656 eachLabel
.updateTabActions();
2662 public JBTabsPresentation
setTabsPosition(final JBTabsPosition position
) {
2663 myPosition
= position
;
2664 relayout(true, false);
2668 public JBTabsPosition
getTabsPosition() {
2672 public TimedDeadzone
.Length
getTabActionsMouseDeadzone() {
2673 return myTabActionsMouseDeadzone
;
2676 public JBTabsPresentation
setTabDraggingEnabled(boolean enabled
) {
2677 myTabDraggingEnabled
= enabled
;
2681 public boolean isTabDraggingEnabled() {
2682 return myTabDraggingEnabled
&& isSingleRow();
2685 void reallocate(TabInfo source
, TabInfo target
, boolean before
) {
2686 if (source
== target
|| source
== null || target
== null) return;
2688 final int targetIndex
= myVisibleInfos
.indexOf(target
);
2689 final int sourceIndex
= myVisibleInfos
.indexOf(source
);
2691 boolean needsValidation
= false;
2693 myVisibleInfos
.remove(source
);
2694 myVisibleInfos
.add(targetIndex
, source
);
2695 needsValidation
= true;
2697 //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
2700 if (needsValidation
) {
2702 relayout(true, true);
2706 boolean isHorizontalTabs() {
2707 return getTabsPosition() == JBTabsPosition
.top
|| getTabsPosition() == JBTabsPosition
.bottom
;
2710 public void putInfo(Map
<String
, String
> info
) {
2711 final TabInfo selected
= getSelectedInfo();
2712 if (selected
!= null) {
2713 selected
.putInfo(info
);
2717 public boolean isUseBufferedPaint() {
2718 return myUseBufferedPaint
;
2721 public void setUseBufferedPaint(boolean useBufferedPaint
) {
2722 myUseBufferedPaint
= useBufferedPaint
;