1 package com
.intellij
.ui
.tabs
.impl
;
3 import com
.intellij
.openapi
.Disposable
;
4 import com
.intellij
.openapi
.actionSystem
.*;
5 import com
.intellij
.openapi
.application
.ModalityState
;
6 import com
.intellij
.openapi
.project
.Project
;
7 import com
.intellij
.openapi
.ui
.ShadowAction
;
8 import com
.intellij
.openapi
.util
.ActionCallback
;
9 import com
.intellij
.openapi
.util
.Disposer
;
10 import com
.intellij
.openapi
.util
.Getter
;
11 import com
.intellij
.openapi
.wm
.FocusCommand
;
12 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
13 import com
.intellij
.openapi
.wm
.impl
.content
.GraphicsConfig
;
14 import com
.intellij
.ui
.CaptionPanel
;
15 import com
.intellij
.ui
.tabs
.*;
16 import com
.intellij
.ui
.tabs
.impl
.singleRow
.SingleRowLayout
;
17 import com
.intellij
.ui
.tabs
.impl
.table
.TableLayout
;
18 import com
.intellij
.ui
.tabs
.impl
.table
.TablePassInfo
;
19 import com
.intellij
.util
.ui
.Animator
;
20 import com
.intellij
.util
.ui
.UIUtil
;
21 import org
.jetbrains
.annotations
.NonNls
;
22 import org
.jetbrains
.annotations
.NotNull
;
23 import org
.jetbrains
.annotations
.Nullable
;
26 import javax
.swing
.event
.PopupMenuEvent
;
27 import javax
.swing
.event
.PopupMenuListener
;
28 import javax
.swing
.plaf
.ComponentUI
;
30 import java
.awt
.event
.*;
31 import java
.awt
.geom
.GeneralPath
;
32 import java
.awt
.image
.BufferedImage
;
33 import java
.beans
.PropertyChangeEvent
;
34 import java
.beans
.PropertyChangeListener
;
35 import java
.lang
.ref
.WeakReference
;
37 import java
.util
.List
;
39 public class JBTabsImpl
extends JComponent
40 implements JBTabs
, PropertyChangeListener
, TimerListener
, DataProvider
, PopupMenuListener
, Disposable
, JBTabsPresentation
{
42 static DataKey
<JBTabsImpl
> NAVIGATION_ACTIONS_KEY
= DataKey
.create("JBTabs");
44 ActionManager myActionManager
;
45 public final List
<TabInfo
> myVisibleInfos
= new ArrayList
<TabInfo
>();
46 final Set
<TabInfo
> myHiddenInfos
= new HashSet
<TabInfo
>();
48 TabInfo mySelectedInfo
;
49 public final Map
<TabInfo
, TabLabel
> myInfo2Label
= new HashMap
<TabInfo
, TabLabel
>();
50 public final Map
<TabInfo
, JComponent
> myInfo2Toolbar
= new HashMap
<TabInfo
, JComponent
>();
51 public Dimension myHeaderFitSize
;
53 Insets myInnerInsets
= new Insets(0, 0, 0, 0);
55 final List
<MouseListener
> myTabMouseListeners
= new ArrayList
<MouseListener
>();
56 final List
<TabsListener
> myTabListeners
= new ArrayList
<TabsListener
>();
57 public boolean myFocused
;
59 Getter
<ActionGroup
> myPopupGroup
;
63 DefaultActionGroup myNavigationActions
;
65 PopupMenuListener myPopupListener
;
66 JPopupMenu myActivePopup
;
68 public boolean myHorizontalSide
= true;
70 boolean myStealthTabMode
= false;
72 DataProvider myDataProvider
;
74 WeakReference
<Component
> myDeferredToRemove
= new WeakReference
<Component
>(null);
76 SingleRowLayout mySingleRowLayout
= new SingleRowLayout(this);
77 TableLayout myTableLayout
= new TableLayout(this);
80 private TabLayout myLayout
= mySingleRowLayout
;
81 private LayoutPassInfo myLastLayoutPass
;
83 public boolean myForcedRelayout
;
85 private UiDecorator myUiDecorator
;
86 static final UiDecorator ourDefaultDecorator
= new DefautDecorator();
88 private boolean myPaintFocus
= true;
90 private boolean myHideTabs
= false;
91 private @Nullable Project myProject
;
93 private boolean myRequestFocusOnLastFocusedComponent
= false;
94 private boolean myListenerAdded
;
95 final Set
<TabInfo
> myAttractions
= new HashSet
<TabInfo
>();
97 static final String DEFERRED_REMOVE_FLAG
= "JBTabs.deferredRemove";
98 List
<TabInfo
> myAllTabs
;
99 boolean myPaintBlocked
;
100 BufferedImage myImage
;
101 IdeFocusManager myFocusManager
;
102 boolean myAdjustBorders
= true;
105 private Insets myBorderSize
= new Insets(0, 0, 0, 0);
106 boolean myAddNavigationGroup
= true;
108 boolean myGhostsAlwaysVisible
= false;
109 private boolean myDisposed
;
110 private boolean myToDrawBorderIfTabsHidden
= true;
111 private Color myActiveTabFillIn
;
114 public JBTabsImpl(@Nullable Project project
, ActionManager actionManager
, IdeFocusManager focusManager
, Disposable parent
) {
116 myActionManager
= actionManager
;
117 myFocusManager
= focusManager
;
120 setPaintBorder(-1, -1, -1, -1);
122 Disposer
.register(parent
, this);
124 myNavigationActions
= new DefaultActionGroup();
126 if (myActionManager
!= null) {
127 myNavigationActions
.add(new SelectNextAction(this));
128 myNavigationActions
.add(new SelectPreviousAction(this));
131 setUiDecorator(null);
133 UIUtil
.addAwtListener(new AWTEventListener() {
134 public void eventDispatched(final AWTEvent event
) {
135 if (mySingleRowLayout
.myMorePopup
!= null) return;
136 processFocusChange();
138 }, FocusEvent
.FOCUS_EVENT_MASK
, this);
140 myPopupListener
= new PopupMenuListener() {
141 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
144 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
145 disposePopupListener();
148 public void popupMenuCanceled(final PopupMenuEvent e
) {
149 disposePopupListener();
153 addMouseListener(new MouseAdapter() {
154 public void mousePressed(final MouseEvent e
) {
155 if (mySingleRowLayout
.myLastSingRowLayout
!= null && mySingleRowLayout
.myLastSingRowLayout
.moreRect
!= null && mySingleRowLayout
.myLastSingRowLayout
.moreRect
.contains(e
.getPoint())) {
161 Disposer
.register(this, new Disposable() {
162 public void dispose() {
167 myAnimator
= new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
168 public void paintNow(final float frame
, final float totalFrames
, final float cycle
) {
169 repaintAttractions();
172 myAnimator
.setTakInitialDelay(false);
174 Disposer
.register(this, myAnimator
);
176 setFocusCycleRoot(true);
177 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
178 public Component
getDefaultComponent(final Container aContainer
) {
183 add(mySingleRowLayout
.myLeftGhost
);
184 add(mySingleRowLayout
.myRightGhost
);
187 public void dispose() {
189 mySelectedInfo
= null;
191 myAttractions
.clear();
192 myVisibleInfos
.clear();
193 myUiDecorator
= null;
195 myActivePopup
= null;
196 myInfo2Label
.clear();
197 myInfo2Toolbar
.clear();
198 myTabListeners
.clear();
201 private void processFocusChange() {
202 Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
208 if (owner
== JBTabsImpl
.this || SwingUtilities
.isDescendingFrom(owner
, JBTabsImpl
.this)) {
216 private void repaintAttractions() {
217 boolean needsUpdate
= false;
218 for (TabInfo each
: myVisibleInfos
) {
219 TabLabel eachLabel
= myInfo2Label
.get(each
);
220 needsUpdate
|= eachLabel
.repaintAttraction();
224 relayout(true, false);
228 public void addNotify() {
231 if (myActionManager
!= null && !myListenerAdded
) {
232 myActionManager
.addTimerListener(500, this);
233 myListenerAdded
= true;
237 public void removeNotify() {
238 super.removeNotify();
245 private void removeTimerUpdate() {
246 if (myActionManager
!= null && myListenerAdded
) {
247 myActionManager
.removeTimerListener(this);
248 myListenerAdded
= false;
252 public ModalityState
getModalityState() {
253 return ModalityState
.stateForComponent(this);
257 updateTabActions(false);
260 public void updateTabActions(final boolean validateNow
) {
261 boolean changed
= false;
262 for (TabLabel label
: myInfo2Label
.values()) {
263 changed
|= label
.updateTabActions();
269 paintImmediately(0, 0, getWidth(), getHeight());
272 revalidateAndRepaint(false);
277 private void showMorePopup(final MouseEvent e
) {
278 mySingleRowLayout
.myMorePopup
= new JPopupMenu();
279 for (final TabInfo each
: myVisibleInfos
) {
280 final JCheckBoxMenuItem item
= new JCheckBoxMenuItem(each
.getText());
281 mySingleRowLayout
.myMorePopup
.add(item
);
282 if (getSelectedInfo() == each
) {
283 item
.setSelected(true);
285 item
.addActionListener(new ActionListener() {
286 public void actionPerformed(final ActionEvent e
) {
292 mySingleRowLayout
.myMorePopup
.addPopupMenuListener(new PopupMenuListener() {
293 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
296 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
297 mySingleRowLayout
.myMorePopup
= null;
300 public void popupMenuCanceled(final PopupMenuEvent e
) {
301 mySingleRowLayout
.myMorePopup
= null;
305 mySingleRowLayout
.myMorePopup
.show(this, e
.getX(), e
.getY());
309 private JComponent
getToFocus() {
310 final TabInfo info
= getSelectedInfo();
312 if (info
== null) return null;
314 JComponent toFocus
= null;
316 if (isRequestFocusOnLastFocusedComponent() && info
.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
317 toFocus
= info
.getLastFocusOwner();
320 if (toFocus
== null && (info
== null || info
.getPreferredFocusableComponent() == null)) {
325 if (toFocus
== null) {
326 toFocus
= info
.getPreferredFocusableComponent();
327 final JComponent policyToFocus
= myFocusManager
!= null ? myFocusManager
.getFocusTargetFor(toFocus
) : null;
328 if (policyToFocus
!= null) {
329 toFocus
= policyToFocus
;
336 public void requestFocus() {
337 final JComponent toFocus
= getToFocus();
338 if (toFocus
!= null) {
339 toFocus
.requestFocus();
342 super.requestFocus();
346 public boolean requestFocusInWindow() {
347 final JComponent toFocus
= getToFocus();
348 if (toFocus
!= null) {
349 return toFocus
.requestFocusInWindow();
352 return super.requestFocusInWindow();
356 private JBTabsImpl
findTabs(Component c
) {
357 Component eachParent
= c
;
358 while (eachParent
!= null) {
359 if (eachParent
instanceof JBTabsImpl
) {
360 return (JBTabsImpl
)eachParent
;
362 eachParent
= eachParent
.getParent();
370 public TabInfo
addTab(TabInfo info
, int index
) {
371 if (getTabs().contains(info
)) {
372 return getTabs().get(getTabs().indexOf(info
));
375 info
.getChangeSupport().addPropertyChangeListener(this);
376 final TabLabel label
= new TabLabel(this, info
);
377 myInfo2Label
.put(info
, label
);
380 myVisibleInfos
.add(info
);
382 else if (index
> myVisibleInfos
.size() - 1) {
383 myVisibleInfos
.add(info
);
386 myVisibleInfos
.add(index
, info
);
395 updateSideComponent(info
);
396 updateTabActions(info
);
398 updateAll(false, true);
400 if (info
.isHidden()) {
407 revalidateAndRepaint(false);
414 public TabInfo
addTab(TabInfo info
) {
415 return addTab(info
, -1);
418 public ActionGroup
getPopupGroup() {
419 return myPopupGroup
.get();
422 public String
getPopupPlace() {
426 public JBTabs
setPopupGroup(@NotNull final ActionGroup popupGroup
, @NotNull String place
, final boolean addNavigationGroup
) {
427 return setPopupGroup(new Getter
<ActionGroup
>() {
428 public ActionGroup
get() {
431 }, place
, addNavigationGroup
);
434 public JBTabs
setPopupGroup(@NotNull final Getter
<ActionGroup
> popupGroup
,
435 @NotNull final String place
,
436 final boolean addNavigationGroup
) {
437 myPopupGroup
= popupGroup
;
438 myPopupPlace
= place
;
439 myAddNavigationGroup
= addNavigationGroup
;
443 private void updateAll(final boolean forcedRelayout
, final boolean now
) {
444 mySelectedInfo
= getSelectedInfo();
445 removeDeferred(updateContainer(forcedRelayout
, now
));
447 updateTabActions(false);
450 private boolean isMyChildIsFocusedNow() {
451 final Component owner
= getFocusOwner();
452 if (owner
== null) return false;
455 if (mySelectedInfo
!= null) {
456 if (!SwingUtilities
.isDescendingFrom(owner
, mySelectedInfo
.getComponent())) return false;
459 return SwingUtilities
.isDescendingFrom(owner
, this);
463 private JComponent
getFocusOwner() {
464 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
465 return (JComponent
)(owner
instanceof JComponent ? owner
: null);
468 public ActionCallback
select(@NotNull TabInfo info
, boolean requestFocus
) {
469 return _setSelected(info
, requestFocus
);
472 private ActionCallback
_setSelected(final TabInfo info
, final boolean requestFocus
) {
473 if (mySelectedInfo
!= null && mySelectedInfo
.equals(info
)) {
475 return new ActionCallback
.Done();
478 requestFocus(getToFocus());
483 if (myRequestFocusOnLastFocusedComponent
&& mySelectedInfo
!= null) {
484 if (isMyChildIsFocusedNow()) {
485 mySelectedInfo
.setLastFocusOwner(getFocusOwner());
489 TabInfo oldInfo
= mySelectedInfo
;
490 mySelectedInfo
= info
;
491 final TabInfo newInfo
= getSelectedInfo();
493 final Component deferredRemove
= updateContainer(false, true);
495 if (oldInfo
!= newInfo
) {
496 for (TabsListener eachListener
: myTabListeners
) {
497 if (eachListener
!= null) {
498 eachListener
.selectionChanged(oldInfo
, newInfo
);
504 final JComponent toFocus
= getToFocus();
505 if (myProject
!= null && toFocus
!= null) {
506 final ActionCallback result
= new ActionCallback();
507 requestFocus(toFocus
).doWhenProcessed(new Runnable() {
510 result
.setRejected();
512 removeDeferred(deferredRemove
).notifyWhenDone(result
);
520 return removeDeferred(deferredRemove
);
524 return removeDeferred(deferredRemove
);
528 private ActionCallback
requestFocus(final JComponent toFocus
) {
529 if (toFocus
== null) return new ActionCallback
.Done();
531 return myFocusManager
.requestFocus(new FocusCommand(toFocus
) {
532 public ActionCallback
run() {
533 toFocus
.requestFocus();
534 return new ActionCallback
.Done();
539 private ActionCallback
removeDeferred(final Component deferredRemove
) {
540 final ActionCallback callback
= new ActionCallback();
541 if (deferredRemove
!= null) {
542 SwingUtilities
.invokeLater(new Runnable() {
544 if (isForDeferredRemove(deferredRemove
)) {
545 remove(deferredRemove
);
558 private boolean isForDeferredRemove(Component c
) {
559 if (c
instanceof JComponent
) {
560 if (((JComponent
)c
).getClientProperty(DEFERRED_REMOVE_FLAG
) == null) return false;
562 if (mySelectedInfo
!= null && mySelectedInfo
.getComponent() == c
) {
574 private void setForDeferredRemove(Component c
, boolean toRemove
) {
575 if (c
instanceof JComponent
) {
576 ((JComponent
)c
).putClientProperty(DEFERRED_REMOVE_FLAG
, toRemove ? Boolean
.TRUE
: null);
577 c
.setBounds(0, 0, 0, 0);
579 removeCurrentDeferred();
580 setDeferredToRemove(c
);
582 else if (getDeferredToRemove() != null && getDeferredToRemove() == c
) {
583 setDeferredToRemove(null);
588 private void removeCurrentDeferred() {
589 if (getDeferredToRemove() != null) {
590 remove(getDeferredToRemove());
591 setDeferredToRemove(null);
596 public void propertyChange(final PropertyChangeEvent evt
) {
597 final TabInfo tabInfo
= (TabInfo
)evt
.getSource();
598 if (TabInfo
.ACTION_GROUP
.equals(evt
.getPropertyName())) {
599 updateSideComponent(tabInfo
);
601 else if (TabInfo
.TEXT
.equals(evt
.getPropertyName())) {
604 else if (TabInfo
.ICON
.equals(evt
.getPropertyName())) {
607 else if (TabInfo
.ALERT_STATUS
.equals(evt
.getPropertyName())) {
608 boolean start
= ((Boolean
)evt
.getNewValue()).booleanValue();
609 updateAttraction(tabInfo
, start
);
611 else if (TabInfo
.TAB_ACTION_GROUP
.equals(evt
.getPropertyName())) {
612 updateTabActions(tabInfo
);
614 else if (TabInfo
.HIDDEN
.equals(evt
.getPropertyName())) {
618 relayout(false, false);
621 private void updateHiding() {
622 boolean update
= false;
624 Iterator
<TabInfo
> visible
= myVisibleInfos
.iterator();
625 while (visible
.hasNext()) {
626 TabInfo each
= visible
.next();
627 if (each
.isHidden() && !myHiddenInfos
.contains(each
)) {
628 myHiddenInfos
.add(each
);
635 Iterator
<TabInfo
> hidden
= myHiddenInfos
.iterator();
636 while (hidden
.hasNext()) {
637 TabInfo each
= hidden
.next();
638 if (!each
.isHidden() && myHiddenInfos
.contains(each
)) {
639 myVisibleInfos
.add(each
);
648 if (mySelectedInfo
!= null && myHiddenInfos
.contains(mySelectedInfo
)) {
649 mySelectedInfo
= getToSelectOnRemoveOf(mySelectedInfo
);
651 updateAll(true, false);
655 private void updateIcon(final TabInfo tabInfo
) {
656 myInfo2Label
.get(tabInfo
).setIcon(tabInfo
.getIcon());
657 revalidateAndRepaint(false);
660 void revalidateAndRepaint(final boolean layoutNow
) {
661 if (myVisibleInfos
.size() == 0) {
663 final Component nonOpaque
= UIUtil
.findUltimateParent(this);
664 if (nonOpaque
!= null && getParent() != null) {
665 final Rectangle toRepaint
= SwingUtilities
.convertRectangle(getParent(), getBounds(), nonOpaque
);
666 nonOpaque
.repaint(toRepaint
.x
, toRepaint
.y
, toRepaint
.width
, toRepaint
.height
);
684 private void updateAttraction(final TabInfo tabInfo
, boolean start
) {
686 myAttractions
.add(tabInfo
);
689 myAttractions
.remove(tabInfo
);
690 tabInfo
.setBlinkCount(0);
693 if (start
&& !myAnimator
.isRunning()) {
696 else if (!start
&& myAttractions
.size() == 0) {
697 myAnimator
.suspend();
698 repaintAttractions();
702 private void updateText(final TabInfo tabInfo
) {
703 final TabLabel label
= myInfo2Label
.get(tabInfo
);
704 label
.setText(tabInfo
.getColoredText());
705 label
.setToolTipText(tabInfo
.getTooltipText());
706 revalidateAndRepaint(false);
709 private void updateSideComponent(final TabInfo tabInfo
) {
710 final JComponent old
= myInfo2Toolbar
.get(tabInfo
);
714 final JComponent toolbar
= createToolbarComponent(tabInfo
);
715 if (toolbar
!= null) {
716 myInfo2Toolbar
.put(tabInfo
, toolbar
);
721 private void updateTabActions(final TabInfo info
) {
722 myInfo2Label
.get(info
).setTabActions(info
.getTabLabelActions());
726 public TabInfo
getSelectedInfo() {
727 if (!myVisibleInfos
.contains(mySelectedInfo
)) {
728 mySelectedInfo
= null;
730 return mySelectedInfo
!= null ? mySelectedInfo
: (myVisibleInfos
.size() > 0 ? myVisibleInfos
.get(0) : null);
734 private TabInfo
getToSelectOnRemoveOf(TabInfo info
) {
735 if (!myVisibleInfos
.contains(info
)) return null;
736 if (mySelectedInfo
!= info
) return null;
738 if (myVisibleInfos
.size() == 1) return null;
740 int index
= myVisibleInfos
.indexOf(info
);
741 if (index
> 0) return myVisibleInfos
.get(index
- 1);
742 if (index
< myVisibleInfos
.size() - 1) return myVisibleInfos
.get(index
+ 1);
747 protected JComponent
createToolbarComponent(final TabInfo tabInfo
) {
748 return new Toolbar(this, tabInfo
);
752 public TabInfo
getTabAt(final int tabIndex
) {
753 return getTabs().get(tabIndex
);
757 public List
<TabInfo
> getTabs() {
758 if (myAllTabs
!= null) return myAllTabs
;
760 ArrayList
<TabInfo
> result
= new ArrayList
<TabInfo
>();
761 result
.addAll(myVisibleInfos
);
762 result
.addAll(myHiddenInfos
);
769 public TabInfo
getTargetInfo() {
770 return myPopupInfo
!= null ? myPopupInfo
: getSelectedInfo();
773 public void popupMenuWillBecomeVisible(final PopupMenuEvent e
) {
776 public void popupMenuWillBecomeInvisible(final PopupMenuEvent e
) {
780 public void popupMenuCanceled(final PopupMenuEvent e
) {
784 private void resetPopup() {
785 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
786 SwingUtilities
.invokeLater(new Runnable() {
793 public void setPaintBlocked(boolean blocked
) {
794 if (blocked
&& !myPaintBlocked
) {
795 myImage
= new BufferedImage(getWidth(), getHeight(), BufferedImage
.TYPE_INT_ARGB
);
796 final Graphics2D g
= myImage
.createGraphics();
801 myPaintBlocked
= blocked
;
803 if (!myPaintBlocked
) {
804 if (myImage
!= null) {
814 private Component
getDeferredToRemove() {
815 return myDeferredToRemove
!= null ? myDeferredToRemove
.get() : null;
818 private void setDeferredToRemove(final Component c
) {
819 myDeferredToRemove
= new WeakReference
<Component
>(c
);
822 public boolean isToDrawBorderIfTabsHidden() {
823 return myToDrawBorderIfTabsHidden
;
827 public JBTabsPresentation
setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden
) {
828 myToDrawBorderIfTabsHidden
= toDrawBorderIfTabsHidden
;
833 public JBTabs
getJBTabs() {
837 public static class Toolbar
extends JPanel
{
838 private JBTabsImpl myTabs
;
840 public Toolbar(JBTabsImpl tabs
, TabInfo info
) {
843 setLayout(new BorderLayout());
845 final ActionGroup group
= info
.getGroup();
846 final JComponent side
= info
.getSideComponent();
848 if (group
!= null && myTabs
.myActionManager
!= null) {
849 final String place
= info
.getPlace();
850 ActionToolbar toolbar
= myTabs
.myActionManager
.createActionToolbar(place
!= null ? place
: ActionPlaces
.UNKNOWN
, group
, myTabs
.myHorizontalSide
);
851 toolbar
.setTargetComponent(info
.getActionsContextComponent());
852 final JComponent actionToolbar
= toolbar
.getComponent();
853 add(actionToolbar
, BorderLayout
.CENTER
);
858 add(side
, BorderLayout
.EAST
);
861 add(side
, BorderLayout
.CENTER
);
870 public void doLayout() {
872 final Max max
= computeMaxSize();
874 new Dimension(getSize().width
, myHorizontalSide ? Math
.max(max
.myLabel
.height
, max
.myToolbar
.height
) : max
.myLabel
.height
);
877 myLastLayoutPass
= mySingleRowLayout
.layoutSingleRow();
878 myTableLayout
.myLastTableLayout
= null;
881 myLastLayoutPass
= myTableLayout
.layoutTable();
882 mySingleRowLayout
.myLastSingRowLayout
= null;
885 if (isStealthModeEffective()) {
886 final TabLabel label
= myInfo2Label
.get(getSelectedInfo());
887 final Rectangle bounds
= label
.getBounds();
888 final Insets insets
= getLayoutInsets();
889 label
.setBounds(bounds
.x
, bounds
.y
, getWidth() - insets
.right
- insets
.left
, bounds
.height
);
894 myForcedRelayout
= false;
899 public void layoutComp(int xAddin
, int yComp
, final JComponent comp
) {
900 final Insets insets
= getLayoutInsets();
902 final Insets border
=
903 isHideTabs() ?
new Insets(0, 0, 0, 0) : (Insets
)myBorderSize
.clone();
904 if (isStealthModeEffective() || isHideTabs()) {
905 border
.top
= getBorder(-1);
906 border
.bottom
= getBorder(-1);
907 border
.left
= getBorder(-1);
908 border
.right
= getBorder(-1);
911 final Insets inner
= getInnerInsets();
912 border
.top
+= inner
.top
;
913 border
.bottom
+= inner
.bottom
;
914 border
.left
+= inner
.left
;
915 border
.right
+= inner
.right
;
917 comp
.setBounds(insets
.left
+ xAddin
+ border
.left
, yComp
+ border
.top
,
918 getWidth() - insets
.left
- insets
.right
- xAddin
- border
.left
- border
.right
,
919 getHeight() - insets
.bottom
- yComp
- border
.top
- border
.bottom
);
923 public JBTabsPresentation
setInnerInsets(final Insets innerInsets
) {
924 myInnerInsets
= innerInsets
;
928 public Insets
getInnerInsets() {
929 return myInnerInsets
;
932 public Insets
getLayoutInsets() {
933 Insets insets
= getInsets();
934 if (insets
== null) {
935 insets
= new Insets(0, 0, 0, 0);
940 private int fixInset(int inset
, int addin
) {
941 return inset
+ addin
;
946 public int getToolbarInset() {
947 return getArcSize() + 1;
950 public void resetLayout(boolean resetLabels
) {
952 mySingleRowLayout
.myLeftGhost
.reset();
953 mySingleRowLayout
.myRightGhost
.reset();
956 for (TabInfo each
: myVisibleInfos
) {
957 reset(each
, resetLabels
);
960 for (TabInfo each
: myHiddenInfos
) {
961 reset(each
, resetLabels
);
965 private void reset(final TabInfo each
, final boolean resetLabels
) {
966 final JComponent c
= each
.getComponent();
968 c
.setBounds(0, 0, 0, 0);
971 final JComponent toolbar
= myInfo2Toolbar
.get(each
);
972 if (toolbar
!= null) {
973 toolbar
.setBounds(0, 0, 0, 0);
977 myInfo2Label
.get(each
).setBounds(0, 0, 0, 0);
982 private int getArcSize() {
986 public int getGhostTabWidth() {
991 protected void paintComponent(final Graphics g
) {
992 super.paintComponent(g
);
994 if (myVisibleInfos
.size() == 0) return;
996 final GraphicsConfig config
= new GraphicsConfig(g
);
997 config
.setAntialiasing(true);
999 Graphics2D g2d
= (Graphics2D
)g
;
1002 g
.setColor(getBackground());
1003 g
.fillRect(0, 0, getWidth(), getHeight());
1005 int arc
= getArcSize();
1007 final Color topBlickColor
= getTopBlickColor();
1008 final Color rightBlockColor
= getRightBlockColor();
1009 final Color boundsColor
= getBoundsColor();
1011 Insets insets
= getLayoutInsets();
1013 final TabInfo selected
= getSelectedInfo();
1015 final int selectionTabVShift
= getSelectionTabVShift();
1018 boolean leftGhostExists
= isSingleRow();
1019 boolean rightGhostExists
= isSingleRow();
1021 if (!isStealthModeEffective() && !isHideTabs()) {
1022 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.rightGhostVisible
) {
1023 int topX
= mySingleRowLayout
.myLastSingRowLayout
.rightGhost
.x
- arc
;
1024 int topY
= mySingleRowLayout
.myLastSingRowLayout
.rightGhost
.y
+ selectionTabVShift
;
1025 int bottomX
= (int)(mySingleRowLayout
.myLastSingRowLayout
.rightGhost
.getMaxX() - curveArc
);
1026 int bottomY
= (int)mySingleRowLayout
.myLastSingRowLayout
.rightGhost
.getMaxY() + 1;
1028 final GeneralPath path
= new GeneralPath();
1029 path
.moveTo(topX
, topY
);
1030 path
.lineTo(bottomX
, topY
);
1031 path
.quadTo(bottomX
- curveArc
, topY
+ (bottomY
- topY
) / 4, bottomX
, topY
+ (bottomY
- topY
) / 2);
1032 path
.quadTo(bottomX
+ curveArc
, bottomY
- (bottomY
- topY
) / 4, bottomX
, bottomY
);
1033 path
.lineTo(topX
, bottomY
);
1037 g2d
.setColor(getBackground());
1040 g2d
.setColor(boundsColor
);
1043 g2d
.setColor(topBlickColor
);
1044 g2d
.drawLine(topX
, topY
+ 1, bottomX
- curveArc
, topY
+ 1);
1048 paintNonSelectedTabs(g2d
, leftGhostExists
);
1050 if (isSingleRow() && mySingleRowLayout
.myLastSingRowLayout
.leftGhostVisible
) {
1051 final GeneralPath path
= new GeneralPath();
1053 int topX
= mySingleRowLayout
.myLastSingRowLayout
.leftGhost
.x
+ curveArc
;
1054 int topY
= mySingleRowLayout
.myLastSingRowLayout
.leftGhost
.y
+ selectionTabVShift
;
1055 int bottomX
= (int)mySingleRowLayout
.myLastSingRowLayout
.leftGhost
.getMaxX() + 1;
1056 int bottomY
= (int)(mySingleRowLayout
.myLastSingRowLayout
.leftGhost
.getMaxY() + 1);
1058 path
.moveTo(topX
, topY
);
1060 final boolean isLeftFromSelection
= mySingleRowLayout
.myLastSingRowLayout
.toLayout
.indexOf(getSelectedInfo()) == 0;
1062 if (isLeftFromSelection
) {
1063 path
.lineTo(bottomX
, topY
);
1066 path
.lineTo(bottomX
- arc
, topY
);
1067 path
.quadTo(bottomX
, topY
, bottomX
, topY
+ arc
);
1070 path
.lineTo(bottomX
, bottomY
);
1071 path
.lineTo(topX
, bottomY
);
1073 path
.quadTo(topX
- curveArc
* 2 + 1, bottomY
- (bottomY
- topY
) / 4, topX
, (bottomY
- topY
) / 2);
1075 path
.quadTo(topX
+ curveArc
- 1, topY
+ (bottomY
- topY
) / 4, topX
, topY
);
1079 g2d
.setColor(getBackground());
1082 g
.setColor(boundsColor
);
1085 g
.setColor(topBlickColor
);
1086 g
.drawLine(topX
+ 1, topY
+ 1, bottomX
- arc
, topY
+ 1);
1088 g
.setColor(rightBlockColor
);
1089 g2d
.drawLine(bottomX
- 1, topY
+ arc
, bottomX
- 1, bottomY
- 1);
1094 if (selected
== null) return;
1097 final TabLabel selectedLabel
= myInfo2Label
.get(selected
);
1098 if (selectedLabel
== null) return;
1100 Rectangle selectedTabBounds
= selectedLabel
.getBounds();
1103 final GeneralPath path
= new GeneralPath();
1104 int bottomY
= (int)selectedTabBounds
.getMaxY() + 1;
1105 final int topY
= selectedTabBounds
.y
;
1106 int leftX
= selectedTabBounds
.x
;
1108 int rightX
= selectedTabBounds
.x
+ selectedTabBounds
.width
;
1110 path
.moveTo(insets
.left
, bottomY
);
1111 path
.lineTo(leftX
, bottomY
);
1112 path
.lineTo(leftX
, topY
+ arc
);
1113 path
.quadTo(leftX
, topY
, leftX
+ arc
, topY
);
1115 int lastX
= getWidth() - insets
.right
- 1;
1117 if (isStealthModeEffective()) {
1118 path
.lineTo(lastX
- arc
, topY
);
1119 path
.quadTo(lastX
, topY
, lastX
, topY
+ arc
);
1120 path
.lineTo(lastX
, bottomY
);
1123 path
.lineTo(rightX
- arc
, topY
);
1124 path
.quadTo(rightX
, topY
, rightX
, topY
+ arc
);
1125 if (myLastLayoutPass
.hasCurveSpaceFor(selected
)) {
1126 path
.lineTo(rightX
, bottomY
- arc
);
1127 path
.quadTo(rightX
, bottomY
, rightX
+ arc
, bottomY
);
1129 path
.lineTo(rightX
, bottomY
);
1133 path
.lineTo(lastX
, bottomY
);
1135 if (isStealthModeEffective()) {
1139 final GeneralPath fillPath
= (GeneralPath
)path
.clone();
1140 if (!isHideTabs()) {
1141 fillPath
.lineTo(lastX
, bottomY
+ 1);
1142 fillPath
.lineTo(leftX
, bottomY
+ 1);
1143 fillPath
.closePath();
1144 g2d
.setColor(getBackground());
1152 int paintTopY
= topY
;
1153 int paintBottomY
= bottomY
;
1154 final boolean paintFocused
= myPaintFocus
&& (myFocused
|| myActivePopup
!= null);
1155 Color bgPreFill
= null;
1157 if (getActiveTabFillIn() == null) {
1158 from
= UIUtil
.getFocusedFillColor();
1159 to
= UIUtil
.getFocusedFillColor();
1161 bgPreFill
= getActiveTabFillIn();
1163 paintBottomY
= topY
+ getArcSize() - 2;
1164 from
= UIUtil
.toAlpha(UIUtil
.getFocusedFillColor(), alpha
);
1165 to
= UIUtil
.toAlpha(getActiveTabFillIn(), alpha
);
1169 if (isPaintFocus()) {
1170 if (getActiveTabFillIn() == null) {
1172 from
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound().brighter(), alpha
);
1173 to
= UIUtil
.toAlpha(UIUtil
.getPanelBackgound(), alpha
);
1176 from
= UIUtil
.toAlpha(getActiveTabFillIn(), alpha
);
1177 to
= UIUtil
.toAlpha(getActiveTabFillIn(), alpha
);
1182 from
= UIUtil
.toAlpha(Color
.white
, alpha
);
1183 to
= UIUtil
.toAlpha(Color
.white
, alpha
);
1187 if (!isHideTabs()) {
1188 if (bgPreFill
!= null) {
1189 g2d
.setColor(bgPreFill
);
1192 g2d
.setPaint(new GradientPaint(selectedTabBounds
.x
, paintTopY
, from
, selectedTabBounds
.x
, paintBottomY
, to
));
1196 Color borderColor
= UIUtil
.getBoundsColor(paintFocused
);
1197 g2d
.setColor(borderColor
);
1199 if (!isHideTabs()) {
1204 paintBorder(g2d
, insets
.left
, insets
.top
, getWidth() - insets
.left
- insets
.right
, getHeight() - insets
.bottom
- insets
.top
,
1205 borderColor
, from
, to
, paintFocused
);
1208 paintBorder(g2d
, insets
.left
, bottomY
, getWidth() - insets
.left
- insets
.right
, getHeight() - bottomY
- insets
.bottom
, borderColor
,
1209 from
, to
, paintFocused
);
1212 config
.setAntialiasing(false);
1213 if (isSideComponentVertical()) {
1214 JComponent toolbarComp
= myInfo2Toolbar
.get(mySelectedInfo
);
1215 if (toolbarComp
!= null) {
1216 Rectangle toolBounds
= toolbarComp
.getBounds();
1217 g2d
.setColor(CaptionPanel
.CNT_ACTIVE_COLOR
);
1218 g
.drawLine((int)toolBounds
.getMaxX(), toolBounds
.y
, (int)toolBounds
.getMaxX(), (int)toolBounds
.getMaxY() - 1);
1225 private Color
getBoundsColor() {
1229 private Color
getRightBlockColor() {
1230 return Color
.lightGray
;
1233 private Color
getTopBlickColor() {
1237 private void paintNonSelectedTabs(final Graphics2D g2d
, final boolean leftGhostExists
) {
1238 for (int eachRow
= 0; eachRow
< myLastLayoutPass
.getRowCount(); eachRow
++) {
1239 for (int eachColumn
= myLastLayoutPass
.getColumnCount(eachRow
) - 1; eachColumn
>= 0; eachColumn
--) {
1240 final TabInfo each
= myLastLayoutPass
.getTabAt(eachRow
, eachColumn
);
1241 if (getSelectedInfo() == each
) continue;
1242 paintTab(g2d
, each
, leftGhostExists
);
1247 private void paintTab(final Graphics2D g2d
, final TabInfo each
, final boolean leftGhostExists
) {
1248 int tabIndex
= myVisibleInfos
.indexOf(each
);
1250 final int arc
= getArcSize();
1251 final Color topBlickColor
= getTopBlickColor();
1252 final Color rightBlockColor
= getRightBlockColor();
1253 final Color boundsColor
= getBoundsColor();
1254 final TabInfo selected
= getSelectedInfo();
1255 final int selectionTabVShift
= getSelectionTabVShift();
1258 final TabLabel eachLabel
= myInfo2Label
.get(each
);
1259 if (eachLabel
.getBounds().width
== 0) return;
1262 final TabInfo prev
= myLastLayoutPass
.getPreviousFor(myVisibleInfos
.get(tabIndex
));
1263 final TabInfo next
= myLastLayoutPass
.getNextFor(myVisibleInfos
.get(tabIndex
));
1265 final Rectangle eachBounds
= eachLabel
.getBounds();
1266 final GeneralPath path
= new GeneralPath();
1268 boolean firstShowing
= prev
== null;
1269 if (!firstShowing
&& !leftGhostExists
) {
1270 firstShowing
= myInfo2Label
.get(prev
).getBounds().width
== 0;
1273 boolean lastShowing
= next
== null;
1275 lastShowing
= myInfo2Label
.get(next
).getBounds().width
== 0;
1278 boolean leftFromSelection
= selected
!= null && tabIndex
== myVisibleInfos
.indexOf(selected
) - 1;
1281 int leftX
= firstShowing ? eachBounds
.x
: eachBounds
.x
- arc
- 1;
1282 int topY
= eachBounds
.y
+ selectionTabVShift
;
1283 int rigthX
= !lastShowing
&& leftFromSelection ?
(int)eachBounds
.getMaxX() + arc
+ 1 : (int)eachBounds
.getMaxX();
1284 int bottomY
= (int)eachBounds
.getMaxY() + 1;
1286 path
.moveTo(leftX
, bottomY
);
1287 path
.lineTo(leftX
, topY
+ arc
);
1288 path
.quadTo(leftX
, topY
, leftX
+ arc
, topY
);
1289 path
.lineTo(rigthX
- arc
, topY
);
1290 path
.quadTo(rigthX
, topY
, rigthX
, topY
+ arc
);
1291 path
.lineTo(rigthX
, bottomY
);
1293 if (!isSingleRow()) {
1294 final TablePassInfo info
= myTableLayout
.myLastTableLayout
;
1295 if (!info
.isInSelectionRow(each
)) {
1296 path
.lineTo(rigthX
, bottomY
+ getArcSize());
1297 path
.lineTo(leftX
, bottomY
+ getArcSize());
1298 path
.lineTo(leftX
, bottomY
);
1304 g2d
.setColor(getBackground());
1307 g2d
.setColor(topBlickColor
);
1308 g2d
.drawLine(leftX
+ arc
, topY
+ 1, rigthX
- arc
, topY
+ 1);
1310 g2d
.setColor(rightBlockColor
);
1311 g2d
.drawLine(rigthX
- 1, topY
+ arc
- 1, rigthX
- 1, bottomY
);
1313 g2d
.setColor(boundsColor
);
1317 public int getSelectionTabVShift() {
1321 private void paintBorder(Graphics2D g2d
,
1326 final Color borderColor
,
1327 final Color fillFrom
,
1329 boolean isFocused
) {
1331 int bottomY
= y
+ myBorderSize
.top
- 2;
1332 int middleY
= topY
+ (bottomY
- topY
) / 2;
1334 if (myBorderSize
.top
> 0) {
1336 if (isToDrawBorderIfTabsHidden()) {
1337 g2d
.setColor(borderColor
);
1338 g2d
.drawLine(x
, y
, x
+ width
- 1, y
);
1341 else if (isStealthModeEffective()) {
1342 g2d
.setColor(borderColor
);
1343 g2d
.drawLine(x
, y
- 1, x
+ width
- 1, y
- 1);
1345 else if (getActiveTabFillIn() == null) {
1346 if (myBorderSize
.top
> 1) {
1347 g2d
.setColor(Color
.white
);
1348 g2d
.fillRect(x
, topY
, width
, bottomY
- topY
);
1350 g2d
.setColor(fillTo
);
1351 g2d
.fillRect(x
, topY
, width
, middleY
- topY
);
1353 final Color relfectionStartColor
=
1354 isFocused ? UIUtil
.toAlpha(UIUtil
.getListSelectionBackground().darker(), 125) : UIUtil
.toAlpha(borderColor
, 75);
1355 g2d
.setPaint(new GradientPaint(x
, middleY
, relfectionStartColor
, x
, bottomY
, UIUtil
.toAlpha(Color
.white
, 255)));
1356 g2d
.fillRect(x
, middleY
, width
, bottomY
- middleY
);
1358 g2d
.setColor(UIUtil
.toAlpha(Color
.white
, 100));
1359 g2d
.drawLine(x
, topY
, x
+ width
- 1, topY
);
1362 g2d
.setColor(Color
.lightGray
);
1363 g2d
.drawLine(x
, bottomY
, x
+ width
- 1, bottomY
);
1365 else if (myBorderSize
.top
== 1) {
1366 g2d
.setColor(borderColor
);
1367 g2d
.drawLine(x
, y
, x
+ width
- 1, y
);
1372 g2d
.setColor(borderColor
);
1373 g2d
.fillRect(x
, y
+ height
- myBorderSize
.bottom
, width
, myBorderSize
.bottom
);
1375 g2d
.fillRect(x
, y
, myBorderSize
.left
, height
);
1376 g2d
.fillRect(x
+ width
- myBorderSize
.right
, y
, myBorderSize
.right
, height
);
1379 public boolean isStealthModeEffective() {
1380 return myStealthTabMode
&& getTabCount() == 1 && isSideComponentVertical();
1384 private boolean isNavigationVisible() {
1385 if (myStealthTabMode
&& getTabCount() == 1) return false;
1386 return myVisibleInfos
.size() > 0;
1390 public void paint(final Graphics g
) {
1391 if (myPaintBlocked
) {
1392 g
.drawImage(myImage
, 0, 0, getWidth(), getHeight(), null);
1399 protected void paintChildren(final Graphics g
) {
1400 super.paintChildren(g
);
1402 //if (isSingleRow() && myLastSingRowLayout != null) {
1403 // final List<TabInfo> infos = myLastSingRowLayout.toLayout;
1404 // for (int i = 1; i < infos.size(); i++) {
1405 // final TabInfo each = infos.get(i);
1406 // if (getSelectedInfo() != each && getSelectedInfo() != infos.get(i - 1)) {
1407 // drawSeparator(g, each);
1411 //else if (!isSingleRow() && myLastTableLayout != null) {
1412 // final List<TableRow> table = myLastTableLayout.table;
1413 // for (TableRow eachRow : table) {
1414 // final List<TabInfo> infos = eachRow.myColumns;
1415 // for (int i = 1; i < infos.size(); i++) {
1416 // final TabInfo each = infos.get(i);
1417 // if (getSelectedInfo() != each && getSelectedInfo() != infos.get(i - 1)) {
1418 // drawSeparator(g, each);
1424 mySingleRowLayout
.myMoreIcon
.paintIcon(this, g
);
1427 private void drawSeparator(Graphics g
, TabInfo info
) {
1428 final TabLabel label
= myInfo2Label
.get(info
);
1429 if (label
== null) return;
1430 final Rectangle bounds
= label
.getBounds();
1432 final double height
= bounds
.height
* 0.85d
;
1433 final double delta
= bounds
.height
- height
;
1435 final int y1
= (int)(bounds
.y
+ delta
) + 1;
1436 final int x1
= bounds
.x
;
1437 final int y2
= (int)(bounds
.y
+ bounds
.height
- delta
);
1438 UIUtil
.drawVDottedLine((Graphics2D
)g
, x1
, y1
, y2
, getBackground(), Color
.gray
);
1441 private Max
computeMaxSize() {
1442 Max max
= new Max();
1443 for (TabInfo eachInfo
: myVisibleInfos
) {
1444 final TabLabel label
= myInfo2Label
.get(eachInfo
);
1445 max
.myLabel
.height
= Math
.max(max
.myLabel
.height
, label
.getPreferredSize().height
);
1446 max
.myLabel
.width
= Math
.max(max
.myLabel
.width
, label
.getPreferredSize().width
);
1447 final JComponent toolbar
= myInfo2Toolbar
.get(eachInfo
);
1448 if (toolbar
!= null) {
1449 max
.myToolbar
.height
= Math
.max(max
.myToolbar
.height
, toolbar
.getPreferredSize().height
);
1450 max
.myToolbar
.width
= Math
.max(max
.myToolbar
.width
, toolbar
.getPreferredSize().width
);
1454 max
.myToolbar
.height
++;
1460 private JComponent
getSelectedComponent() {
1461 final TabInfo selection
= getSelectedInfo();
1462 if (selection
!= null) {
1463 final JComponent c
= selection
.getComponent();
1464 if (c
!= null && c
.getParent() == this) return c
;
1470 public Dimension
getMinimumSize() {
1471 final JComponent c
= getSelectedComponent();
1472 return c
!= null ? c
.getMinimumSize() : new Dimension(0, 0);
1475 public Dimension
getMaximumSize() {
1476 final JComponent c
= getSelectedComponent();
1477 return c
!= null ? c
.getMaximumSize() : super.getPreferredSize();
1480 public Dimension
getPreferredSize() {
1481 final JComponent c
= getSelectedComponent();
1482 return c
!= null ? c
.getPreferredSize() : super.getPreferredSize();
1485 public int getTabCount() {
1486 return getTabs().size();
1490 public JBTabsPresentation
getPresentation() {
1494 public ActionCallback
removeTab(final JComponent component
) {
1495 return removeTab(findInfo(component
));
1498 public ActionCallback
removeTab(final TabInfo info
) {
1499 return removeTab(info
, true);
1502 public ActionCallback
removeTab(final TabInfo info
, boolean transferFocus
) {
1503 if (info
== null || !getTabs().contains(info
)) return new ActionCallback
.Done();
1505 final ActionCallback result
= new ActionCallback();
1507 TabInfo toSelect
= transferFocus ?
getToSelectOnRemoveOf(info
) : null;
1510 if (toSelect
!= null) {
1511 final JComponent deferred
= processRemove(info
, false);
1512 _setSelected(toSelect
, true).doWhenProcessed(new Runnable() {
1514 removeDeferred(deferred
);
1516 }).notifyWhenDone(result
);
1519 removeDeferred(processRemove(info
, true)).notifyWhenDone(result
);
1522 if (myVisibleInfos
.size() == 0) {
1523 removeCurrentDeferred();
1526 revalidateAndRepaint(true);
1532 private JComponent
processRemove(final TabInfo info
, boolean forcedNow
) {
1533 remove(myInfo2Label
.get(info
));
1534 final JComponent tb
= myInfo2Toolbar
.get(info
);
1539 JComponent tabComponent
= info
.getComponent();
1541 if (!isFocused(tabComponent
) || forcedNow
) {
1542 remove(tabComponent
);
1543 tabComponent
= null;
1546 setForDeferredRemove(tabComponent
, true);
1549 myVisibleInfos
.remove(info
);
1550 myHiddenInfos
.remove(info
);
1551 myInfo2Label
.remove(info
);
1552 myInfo2Toolbar
.remove(info
);
1555 updateAll(false, false);
1557 return tabComponent
;
1560 public TabInfo
findInfo(Component component
) {
1561 for (TabInfo each
: getTabs()) {
1562 if (each
.getComponent() == component
) return each
;
1568 public TabInfo
findInfo(String text
) {
1569 if (text
== null) return null;
1571 for (TabInfo each
: getTabs()) {
1572 if (text
.equals(each
.getText())) return each
;
1578 public TabInfo
findInfo(MouseEvent event
) {
1579 final Point point
= SwingUtilities
.convertPoint(event
.getComponent(), event
.getPoint(), this);
1580 return _findInfo(point
, false);
1583 public TabInfo
findInfo(final Object object
) {
1584 for (int i
= 0; i
< getTabCount(); i
++) {
1585 final TabInfo each
= getTabAt(i
);
1586 final Object eachObject
= each
.getObject();
1587 if (eachObject
!= null && eachObject
.equals(object
)) return each
;
1592 public TabInfo
findTabLabelBy(final Point point
) {
1593 return _findInfo(point
, true);
1596 private TabInfo
_findInfo(final Point point
, boolean labelsOnly
) {
1597 Component component
= findComponentAt(point
);
1598 if (component
== null) return null;
1599 while (component
!= this || component
!= null) {
1600 if (component
instanceof TabLabel
) {
1601 return ((TabLabel
)component
).getInfo();
1603 else if (!labelsOnly
) {
1604 final TabInfo info
= findInfo(component
);
1605 if (info
!= null) return info
;
1607 if (component
== null) break;
1608 component
= component
.getParent();
1614 public void removeAllTabs() {
1615 for (TabInfo each
: getTabs()) {
1622 Dimension myLabel
= new Dimension();
1623 Dimension myToolbar
= new Dimension();
1627 private Component
updateContainer(boolean forced
, final boolean layoutNow
) {
1628 Component deferredRemove
= null;
1630 for (TabInfo each
: myVisibleInfos
) {
1631 final JComponent eachComponent
= each
.getComponent();
1632 if (getSelectedInfo() == each
&& getSelectedInfo() != null) {
1633 final Container parent
= eachComponent
.getParent();
1634 if (parent
!= null && parent
!= this) {
1635 parent
.remove(eachComponent
);
1638 if (eachComponent
.getParent() == null) {
1643 if (eachComponent
.getParent() == null) continue;
1644 if (isFocused(eachComponent
)) {
1645 deferredRemove
= eachComponent
;
1648 remove(eachComponent
);
1653 if (deferredRemove
!= null) {
1654 setForDeferredRemove(deferredRemove
, true);
1658 relayout(forced
, layoutNow
);
1660 return deferredRemove
;
1663 protected void addImpl(final Component comp
, final Object constraints
, final int index
) {
1664 setForDeferredRemove(comp
, false);
1666 if (comp
instanceof TabLabel
) {
1667 ((TabLabel
)comp
).apply(myUiDecorator
.getDecoration());
1670 super.addImpl(comp
, constraints
, index
);
1673 private boolean isFocused(JComponent c
) {
1674 Component focusOwner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
1675 return focusOwner
!= null && (focusOwner
== c
|| SwingUtilities
.isDescendingFrom(focusOwner
, c
));
1678 private void relayout(boolean forced
, final boolean layoutNow
) {
1679 if (!myForcedRelayout
) {
1680 myForcedRelayout
= forced
;
1682 revalidateAndRepaint(layoutNow
);
1685 ActionManager
getActionManager() {
1686 return myActionManager
;
1690 public JBTabs
addTabMouseListener(@NotNull MouseListener listener
) {
1692 myTabMouseListeners
.add(listener
);
1698 public JComponent
getComponent() {
1703 public JBTabs
removeTabMouseListener(@NotNull MouseListener listener
) {
1705 myTabMouseListeners
.remove(listener
);
1710 private void addListeners() {
1711 for (TabInfo eachInfo
: myVisibleInfos
) {
1712 final TabLabel label
= myInfo2Label
.get(eachInfo
);
1713 for (MouseListener eachListener
: myTabMouseListeners
) {
1714 label
.addMouseListener(eachListener
);
1719 private void removeListeners() {
1720 for (TabInfo eachInfo
: myVisibleInfos
) {
1721 final TabLabel label
= myInfo2Label
.get(eachInfo
);
1722 for (MouseListener eachListener
: myTabMouseListeners
) {
1723 label
.removeMouseListener(eachListener
);
1728 private void updateListeners() {
1733 public JBTabs
addListener(@NotNull TabsListener listener
) {
1734 myTabListeners
.add(listener
);
1738 public JBTabs
removeListener(@NotNull final TabsListener listener
) {
1739 myTabListeners
.remove(listener
);
1743 protected void onPopup(final TabInfo popupInfo
) {
1746 public void setFocused(final boolean focused
) {
1747 myFocused
= focused
;
1751 public int getIndexOf(@Nullable final TabInfo tabInfo
) {
1752 return myVisibleInfos
.indexOf(tabInfo
);
1755 public boolean isHideTabs() {
1759 public void setHideTabs(final boolean hideTabs
) {
1760 if (isHideTabs() == hideTabs
) return;
1762 myHideTabs
= hideTabs
;
1764 relayout(true, false);
1767 public JBTabsPresentation
setPaintBorder(int top
, int left
, int right
, int bottom
) {
1768 if (myBorderSize
.top
== top
&& myBorderSize
.left
== left
&& myBorderSize
.right
== right
&& myBorderSize
.bottom
== bottom
) return this;
1770 myBorderSize
= new Insets(getBorder(top
), getBorder(left
), getBorder(bottom
), getBorder(right
));
1772 revalidateAndRepaint(false);
1777 private static int getBorder(int size
) {
1778 return size
== -1 ?
1 : size
;
1781 public boolean isPaintFocus() {
1782 return myPaintFocus
;
1786 public JBTabsPresentation
setAdjustBorders(final boolean adjust
) {
1787 myAdjustBorders
= adjust
;
1792 public JBTabsPresentation
setActiveTabFillIn(@Nullable final Color color
) {
1793 myActiveTabFillIn
= color
;
1794 revalidateAndRepaint(false);
1799 public Color
getActiveTabFillIn() {
1800 return myActiveTabFillIn
;
1803 public JBTabsPresentation
setFocusCycle(final boolean root
) {
1804 setFocusCycleRoot(root
);
1809 public JBTabsPresentation
setPaintFocus(final boolean paintFocus
) {
1810 myPaintFocus
= paintFocus
;
1814 private static abstract class BaseNavigationAction
extends AnAction
{
1816 private ShadowAction myShadow
;
1818 protected BaseNavigationAction(final String copyFromID
, JComponent c
) {
1819 myShadow
= new ShadowAction(this, ActionManager
.getInstance().getAction(copyFromID
), c
);
1822 public final void update(final AnActionEvent e
) {
1823 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
1824 e
.getPresentation().setVisible(tabs
!= null);
1825 if (tabs
== null) return;
1827 final int selectedIndex
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
1828 final boolean enabled
= tabs
.myVisibleInfos
.size() > 0 && selectedIndex
>= 0;
1829 e
.getPresentation().setEnabled(enabled
);
1831 _update(e
, tabs
, selectedIndex
);
1835 protected abstract void _update(AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
);
1837 public final void actionPerformed(final AnActionEvent e
) {
1838 JBTabsImpl tabs
= e
.getData(NAVIGATION_ACTIONS_KEY
);
1839 if (tabs
== null) return;
1841 final int index
= tabs
.myVisibleInfos
.indexOf(tabs
.getSelectedInfo());
1842 if (index
== -1) return;
1843 _actionPerformed(e
, tabs
, index
);
1846 protected abstract void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
);
1849 private static class SelectNextAction
extends BaseNavigationAction
{
1851 public SelectNextAction(JComponent c
) {
1852 super(IdeActions
.ACTION_NEXT_TAB
, c
);
1855 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
1856 e
.getPresentation().setEnabled(tabs
.myVisibleInfos
.size() > 0 && selectedIndex
< tabs
.myVisibleInfos
.size() - 1);
1859 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
1860 tabs
.select(tabs
.myVisibleInfos
.get(selectedIndex
+ 1), true);
1864 private static class SelectPreviousAction
extends BaseNavigationAction
{
1865 public SelectPreviousAction(JComponent c
) {
1866 super(IdeActions
.ACTION_PREVIOUS_TAB
, c
);
1869 protected void _update(final AnActionEvent e
, final JBTabsImpl tabs
, int selectedIndex
) {
1870 e
.getPresentation().setEnabled(tabs
.myVisibleInfos
.size() > 0 && selectedIndex
> 0);
1873 protected void _actionPerformed(final AnActionEvent e
, final JBTabsImpl tabs
, final int selectedIndex
) {
1874 tabs
.select(tabs
.myVisibleInfos
.get(selectedIndex
- 1), true);
1878 private void disposePopupListener() {
1879 if (myActivePopup
!= null) {
1880 myActivePopup
.removePopupMenuListener(myPopupListener
);
1881 myActivePopup
= null;
1885 public JBTabsPresentation
setStealthTabMode(final boolean stealthTabMode
) {
1886 myStealthTabMode
= stealthTabMode
;
1888 relayout(true, false);
1893 public boolean isStealthTabMode() {
1894 return myStealthTabMode
;
1897 public JBTabsPresentation
setSideComponentVertical(final boolean vertical
) {
1898 myHorizontalSide
= !vertical
;
1900 for (TabInfo each
: myVisibleInfos
) {
1901 each
.getChangeSupport().firePropertyChange(TabInfo
.ACTION_GROUP
, "new1", "new2");
1905 relayout(true, false);
1910 public JBTabsPresentation
setSingleRow(boolean singleRow
) {
1911 myLayout
= singleRow ? mySingleRowLayout
: myTableLayout
;
1913 relayout(true, false);
1918 public JBTabsPresentation
setGhostsAlwaysVisible(final boolean visible
) {
1919 myGhostsAlwaysVisible
= visible
;
1921 relayout(true, false);
1926 public boolean isGhostsAlwaysVisible() {
1927 return myGhostsAlwaysVisible
;
1930 public boolean isSingleRow() {
1931 return myLayout
== mySingleRowLayout
;
1934 public boolean isSideComponentVertical() {
1935 return !myHorizontalSide
;
1938 public JBTabsPresentation
setUiDecorator(UiDecorator decorator
) {
1939 myUiDecorator
= decorator
== null ? ourDefaultDecorator
: decorator
;
1944 protected void setUI(final ComponentUI newUI
) {
1949 public void updateUI() {
1951 SwingUtilities
.invokeLater(new Runnable() {
1955 revalidateAndRepaint(false);
1960 private void applyDecoration() {
1961 if (myUiDecorator
!= null) {
1962 UiDecorator
.UiDecoration uiDecoration
= myUiDecorator
.getDecoration();
1963 for (TabLabel each
: myInfo2Label
.values()) {
1964 each
.apply(uiDecoration
);
1969 for (TabInfo each
: getTabs()) {
1973 relayout(true, false);
1976 private void adjust(final TabInfo each
) {
1977 if (myAdjustBorders
) {
1978 UIUtil
.removeScrollBorder(each
.getComponent());
1982 public void sortTabs(Comparator
<TabInfo
> comparator
) {
1983 Collections
.sort(myVisibleInfos
, comparator
);
1985 relayout(true, false);
1988 public boolean isRequestFocusOnLastFocusedComponent() {
1989 return myRequestFocusOnLastFocusedComponent
;
1992 public JBTabsPresentation
setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent
) {
1993 myRequestFocusOnLastFocusedComponent
= requestFocusOnLastFocusedComponent
;
1999 public Object
getData(@NonNls final String dataId
) {
2000 if (myDataProvider
!= null) {
2001 final Object value
= myDataProvider
.getData(dataId
);
2002 if (value
!= null) return value
;
2005 if (!NAVIGATION_ACTIONS_KEY
.getName().equals(dataId
)) return null;
2006 return isNavigationVisible() ?
this : null;
2010 public DataProvider
getDataProvider() {
2011 return myDataProvider
;
2014 public JBTabsImpl
setDataProvider(@NotNull final DataProvider dataProvider
) {
2015 myDataProvider
= dataProvider
;
2020 public boolean isSelectionClick(final MouseEvent e
, boolean canBeQuick
) {
2021 if (e
.getClickCount() == 1 || canBeQuick
) {
2022 if (!e
.isPopupTrigger()) {
2023 return e
.getButton() == MouseEvent
.BUTTON1
&& !e
.isControlDown() && !e
.isAltDown() && !e
.isMetaDown();
2031 private static class DefautDecorator
implements UiDecorator
{
2033 public UiDecoration
getDecoration() {
2034 return new UiDecoration(null, new Insets(2, 8, 2, 8));