switcher - auto selection + fixes
[idea/community.git] / platform / platform-api / src / com / intellij / ui / tabs / impl / JBTabsImpl.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.ui.tabs.impl;
17
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.Queryable;
23 import com.intellij.openapi.ui.ShadowAction;
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.awt.RelativeRectangle;
33 import com.intellij.ui.switcher.QuickActionProvider;
34 import com.intellij.ui.switcher.SwitchProvider;
35 import com.intellij.ui.switcher.SwitchTarget;
36 import com.intellij.ui.tabs.*;
37 import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
38 import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo;
39 import com.intellij.ui.tabs.impl.table.TableLayout;
40 import com.intellij.ui.tabs.impl.table.TablePassInfo;
41 import com.intellij.util.ui.Animator;
42 import com.intellij.util.ui.TimedDeadzone;
43 import com.intellij.util.ui.UIUtil;
44 import com.intellij.util.ui.update.ComparableObject;
45 import com.intellij.util.ui.update.LazyUiDisposable;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import javax.swing.event.PopupMenuEvent;
52 import javax.swing.event.PopupMenuListener;
53 import javax.swing.plaf.ComponentUI;
54 import java.awt.*;
55 import java.awt.event.*;
56 import java.awt.geom.Line2D;
57 import java.awt.image.BufferedImage;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
60 import java.util.*;
61 import java.util.List;
62
63 public class JBTabsImpl extends JComponent
64   implements JBTabs, PropertyChangeListener, TimerListener, DataProvider, PopupMenuListener, Disposable, JBTabsPresentation, Queryable, QuickActionProvider {
65
66   static DataKey<JBTabsImpl> NAVIGATION_ACTIONS_KEY = DataKey.create("JBTabs");
67
68   ActionManager myActionManager;
69   public final List<TabInfo> myVisibleInfos = new ArrayList<TabInfo>();
70   private final Map<TabInfo, Integer> myHiddenInfos = new HashMap<TabInfo, Integer>();
71
72   private TabInfo mySelectedInfo;
73   public final Map<TabInfo, TabLabel> myInfo2Label = new HashMap<TabInfo, TabLabel>();
74   public final Map<TabInfo, Toolbar> myInfo2Toolbar = new HashMap<TabInfo, Toolbar>();
75   public Dimension myHeaderFitSize;
76
77   private Insets myInnerInsets = new Insets(0, 0, 0, 0);
78
79   private final List<EventListener> myTabMouseListeners = new ArrayList<EventListener>();
80   private final List<TabsListener> myTabListeners = new ArrayList<TabsListener>();
81   public boolean myFocused;
82
83   private Getter<ActionGroup> myPopupGroup;
84   private String myPopupPlace;
85
86   TabInfo myPopupInfo;
87   DefaultActionGroup myNavigationActions;
88
89   PopupMenuListener myPopupListener;
90   JPopupMenu myActivePopup;
91
92   public boolean myHorizontalSide = true;
93
94   private boolean myStealthTabMode = false;
95
96   private DataProvider myDataProvider;
97
98   private final WeakHashMap<Component, Component> myDeferredToRemove = new WeakHashMap<Component, Component>();
99
100   private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
101   private final TableLayout myTableLayout = new TableLayout(this);
102
103
104   private TabLayout myLayout = mySingleRowLayout;
105   LayoutPassInfo myLastLayoutPass;
106   private TabInfo myLastPaintedSelection;
107
108   public boolean myForcedRelayout;
109
110   private UiDecorator myUiDecorator;
111   static final UiDecorator ourDefaultDecorator = new DefautDecorator();
112
113   private boolean myPaintFocus;
114
115   private boolean myHideTabs = false;
116   @Nullable private Project myProject;
117
118   private boolean myRequestFocusOnLastFocusedComponent = false;
119   private boolean myListenerAdded;
120   final Set<TabInfo> myAttractions = new HashSet<TabInfo>();
121   private Animator myAnimator;
122   private List<TabInfo> myAllTabs;
123   private boolean myPaintBlocked;
124   private BufferedImage myImage;
125   private IdeFocusManager myFocusManager;
126   private boolean myAdjustBorders = true;
127
128   boolean myAddNavigationGroup = true;
129
130   private boolean myGhostsAlwaysVisible = false;
131   private boolean myDisposed;
132   private boolean myToDrawBorderIfTabsHidden = true;
133   private Color myActiveTabFillIn;
134
135   private boolean myTabLabelActionsAutoHide;
136
137   private final TabActionsAutoHideListener myTabActionsAutoHideListener = new TabActionsAutoHideListener();
138   private IdeGlassPane myGlassPane;
139   @NonNls private static final String LAYOUT_DONE = "Layout.done";
140
141   private TimedDeadzone.Length myTabActionsMouseDeadzone = TimedDeadzone.DEFAULT;
142
143   private long myRemoveDefferredRequest;
144   private boolean myTestMode;
145
146   private JBTabsPosition myPosition = JBTabsPosition.top;
147
148   private final TabsBorder myBorder = new TabsBorder(this);
149   private BaseNavigationAction myNextAction;
150   private BaseNavigationAction myPrevAction;
151
152   private boolean myWasEverShown;
153
154   private boolean myTabDraggingEnabled;
155   private DragHelper myDragHelper;
156   private boolean myNavigationActionsEnabled = true;
157   private boolean myUseBufferedPaint = true;
158
159   private boolean myOwnSwitchProvider = true;
160   private SwitchProvider mySwitchDelegate;
161
162   public JBTabsImpl(@NotNull Project project) {
163     this(project, project);
164   }
165
166   public JBTabsImpl(@NotNull Project project, @NotNull Disposable parent) {
167     this(project, ActionManager.getInstance(), IdeFocusManager.getInstance(project), parent);
168   }
169
170   public JBTabsImpl(@Nullable Project project, IdeFocusManager focusManager, @NotNull Disposable parent) {
171     this(project, ActionManager.getInstance(), focusManager, parent);
172   }
173
174   public JBTabsImpl(@Nullable Project project, ActionManager actionManager, IdeFocusManager focusManager, @NotNull Disposable parent) {
175     myProject = project;
176     myActionManager = actionManager;
177     myFocusManager = focusManager != null ? focusManager : IdeFocusManager.getGlobalInstance();
178
179     setOpaque(true);
180     setPaintBorder(-1, -1, -1, -1);
181
182     Disposer.register(parent, this);
183
184     myNavigationActions = new DefaultActionGroup();
185
186     if (myActionManager != null) {
187       myNextAction = new SelectNextAction(this, myActionManager);
188       myPrevAction = new SelectPreviousAction(this, myActionManager);
189
190       myNavigationActions.add(myNextAction);
191       myNavigationActions.add(myPrevAction);
192     }
193
194     setUiDecorator(null);
195
196     myPopupListener = new PopupMenuListener() {
197       public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
198       }
199
200       public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
201         disposePopupListener();
202       }
203
204       public void popupMenuCanceled(final PopupMenuEvent e) {
205         disposePopupListener();
206       }
207     };
208
209     addMouseListener(new MouseAdapter() {
210       public void mousePressed(final MouseEvent e) {
211         if (mySingleRowLayout.myLastSingRowLayout != null &&
212             mySingleRowLayout.myLastSingRowLayout.moreRect != null &&
213             mySingleRowLayout.myLastSingRowLayout.moreRect.contains(e.getPoint())) {
214           showMorePopup(e);
215         }
216       }
217     });
218
219     myAnimator = new Animator("JBTabs Attractions", 2, 500, true, 0, -1) {
220       public void paintNow(final float frame, final float totalFrames, final float cycle) {
221         repaintAttractions();
222       }
223     };
224     myAnimator.setTakInitialDelay(false);
225
226     setFocusCycleRoot(true);
227     setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
228       public Component getDefaultComponent(final Container aContainer) {
229         return getToFocus();
230       }
231     });
232
233     add(mySingleRowLayout.myLeftGhost);
234     add(mySingleRowLayout.myRightGhost);
235
236
237     new LazyUiDisposable<JBTabsImpl>(parent, this, this, project) {
238       protected void initialize(@NotNull Disposable parent, @NotNull JBTabsImpl child, @Nullable Project project) {
239         myProject = project;
240
241         Disposer.register(child, myAnimator);
242         Disposer.register(child, new Disposable() {
243           public void dispose() {
244             removeTimerUpdate();
245           }
246         });
247
248         if (!myTestMode) {
249           final IdeGlassPane gp = IdeGlassPaneUtil.find(child);
250           if (gp != null) {
251             gp.addMouseMotionPreprocessor(myTabActionsAutoHideListener, child);
252             myGlassPane = gp;
253           }
254
255           UIUtil.addAwtListener(new AWTEventListener() {
256             public void eventDispatched(final AWTEvent event) {
257               if (mySingleRowLayout.myMorePopup != null) return;
258               processFocusChange();
259             }
260           }, AWTEvent.FOCUS_EVENT_MASK, child);
261
262           myDragHelper = new DragHelper(child);
263           myDragHelper.start();
264         }
265
266         if (myProject != null && myFocusManager == IdeFocusManager.getGlobalInstance()) {
267           myFocusManager = IdeFocusManager.getInstance(myProject);
268         }
269       }
270     };
271   }
272
273
274   public JBTabs setNavigationActiondBinding(String prevActionId, String nextActionId) {
275     if (myNextAction != null) {
276       myNextAction.reconnect(nextActionId);
277     }
278     if (myPrevAction != null) {
279       myPrevAction.reconnect(prevActionId);
280     }
281
282     return this;
283   }
284
285   public JBTabs setNavigationActionsEnabled(boolean enabled) {
286     myNavigationActionsEnabled = enabled;
287     return this;
288   }
289
290   public final boolean isDisposed() {
291     return myDisposed;
292   }
293
294   public JBTabs setAdditinalSwitchProviderWhenOriginal(SwitchProvider delegate) {
295     mySwitchDelegate = delegate;
296     return this;
297   }
298
299   public void dispose() {
300     myDisposed = true;
301     mySelectedInfo = null;
302     resetTabsCache();
303     myAttractions.clear();
304     myVisibleInfos.clear();
305     myUiDecorator = null;
306     myImage = null;
307     myActivePopup = null;
308     myInfo2Label.clear();
309     myInfo2Toolbar.clear();
310     myTabListeners.clear();
311   }
312
313   void resetTabsCache() {
314     myAllTabs = null;
315   }
316
317   private void processFocusChange() {
318     Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
319     if (owner == null) {
320       setFocused(false);
321       return;
322     }
323
324     if (owner == this || SwingUtilities.isDescendingFrom(owner, this)) {
325       setFocused(true);
326     }
327     else {
328       setFocused(false);
329     }
330   }
331
332   private void repaintAttractions() {
333     boolean needsUpdate = false;
334     for (TabInfo each : myVisibleInfos) {
335       TabLabel eachLabel = myInfo2Label.get(each);
336       needsUpdate |= eachLabel.repaintAttraction();
337     }
338
339     if (needsUpdate) {
340       relayout(true, false);
341     }
342   }
343
344   public void addNotify() {
345     super.addNotify();
346     addTimerUpdate();
347   }
348
349   public void removeNotify() {
350     super.removeNotify();
351
352     setFocused(false);
353
354     removeTimerUpdate();
355
356     if (myGlassPane != null) {
357       myGlassPane.removeMouseMotionPreprocessor(myTabActionsAutoHideListener);
358       myGlassPane = null;
359     }
360   }
361
362   private void addTimerUpdate() {
363     if (myActionManager != null && !myListenerAdded) {
364       myActionManager.addTimerListener(500, this);
365       myListenerAdded = true;
366     }
367   }
368
369   private void removeTimerUpdate() {
370     if (myActionManager != null && myListenerAdded) {
371       myActionManager.removeTimerListener(this);
372       myListenerAdded = false;
373     }
374   }
375
376   void setTestMode(final boolean testMode) {
377     myTestMode = testMode;
378   }
379
380   public void layoutComp(SingleRowPassInfo data, int deltaX, int deltaY, int deltaWidth, int deltaHeight) {
381     if (data.hToolbar != null) {
382       final int toolbarHeight = data.hToolbar.getPreferredSize().height;
383       final Rectangle compRect = layoutComp(deltaX, toolbarHeight + deltaY, data.comp, deltaWidth, deltaHeight);
384       layout(data.hToolbar, compRect.x, compRect.y - toolbarHeight, compRect.width, toolbarHeight);
385     }
386     else if (data.vToolbar != null) {
387       final int toolbarWidth = data.vToolbar.getPreferredSize().width;
388       final Rectangle compRect = layoutComp(toolbarWidth + deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
389       layout(data.vToolbar, compRect.x - toolbarWidth, compRect.y, toolbarWidth, compRect.height);
390     }
391     else {
392       layoutComp(deltaX, deltaY, data.comp, deltaWidth, deltaHeight);
393     }
394   }
395
396   class TabActionsAutoHideListener extends MouseMotionAdapter {
397
398     private TabLabel myCurrentOverLabel;
399     private Point myLastOverPoint;
400
401     @Override
402     public void mouseMoved(final MouseEvent e) {
403       if (!myTabLabelActionsAutoHide) return;
404
405       final Point point = SwingUtilities.convertPoint(e.getComponent(), e.getX(), e.getY(), JBTabsImpl.this);
406       myLastOverPoint = point;
407       processMouseOver();
408     }
409
410     void processMouseOver() {
411       if (!myTabLabelActionsAutoHide) return;
412
413       if (myLastOverPoint == null) return;
414
415       if (myLastOverPoint.x >= 0 && myLastOverPoint.x < getWidth() && myLastOverPoint.y > 0 && myLastOverPoint.y < getHeight()) {
416         final TabLabel label = myInfo2Label.get(_findInfo(myLastOverPoint, true));
417         if (label != null) {
418           if (myCurrentOverLabel != null) {
419             myCurrentOverLabel.toggleShowActions(false);
420           }
421           label.toggleShowActions(true);
422           myCurrentOverLabel = label;
423           return;
424         }
425       }
426
427       if (myCurrentOverLabel != null) {
428         myCurrentOverLabel.toggleShowActions(false);
429         myCurrentOverLabel = null;
430       }
431     }
432   }
433
434
435   public ModalityState getModalityState() {
436     return ModalityState.stateForComponent(this);
437   }
438
439   public void run() {
440     updateTabActions(false);
441   }
442
443   public void updateTabActions(final boolean validateNow) {
444     final Ref<Boolean> changed = new Ref<Boolean>(Boolean.FALSE);
445     for (final TabInfo eachInfo : myInfo2Label.keySet()) {
446       updateTab(new Runnable() {
447         public void run() {
448           final boolean changes = myInfo2Label.get(eachInfo).updateTabActions();
449           changed.set(changed.get().booleanValue() || changes);
450         }
451       }, eachInfo);
452     }
453
454     if (changed.get().booleanValue()) {
455       if (validateNow) {
456         validate();
457         paintImmediately(0, 0, getWidth(), getHeight());
458       }
459     }
460   }
461
462   private void showMorePopup(final MouseEvent e) {
463     mySingleRowLayout.myMorePopup = new JPopupMenu();
464     for (final TabInfo each : myVisibleInfos) {
465       final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText());
466       mySingleRowLayout.myMorePopup.add(item);
467       if (getSelectedInfo() == each) {
468         item.setSelected(true);
469       }
470       item.addActionListener(new ActionListener() {
471         public void actionPerformed(final ActionEvent e) {
472           select(each, true);
473         }
474       });
475     }
476
477     mySingleRowLayout.myMorePopup.addPopupMenuListener(new PopupMenuListener() {
478       public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
479       }
480
481       public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
482         mySingleRowLayout.myMorePopup = null;
483       }
484
485       public void popupMenuCanceled(final PopupMenuEvent e) {
486         mySingleRowLayout.myMorePopup = null;
487       }
488     });
489
490     mySingleRowLayout.myMorePopup.show(this, e.getX(), e.getY());
491   }
492
493
494   private JComponent getToFocus() {
495     final TabInfo info = getSelectedInfo();
496
497     if (info == null) return null;
498
499     JComponent toFocus = null;
500
501     if (isRequestFocusOnLastFocusedComponent() && info.getLastFocusOwner() != null && !isMyChildIsFocusedNow()) {
502       toFocus = info.getLastFocusOwner();
503     }
504
505     if (toFocus == null && info.getPreferredFocusableComponent() == null) {
506       return null;
507     }
508
509
510     if (toFocus == null) {
511       toFocus = info.getPreferredFocusableComponent();
512       final JComponent policyToFocus = myFocusManager.getFocusTargetFor(toFocus);
513       if (policyToFocus != null) {
514         toFocus = policyToFocus;
515       }
516     }
517
518     return toFocus;
519   }
520
521   public void requestFocus() {
522     final JComponent toFocus = getToFocus();
523     if (toFocus != null) {
524       toFocus.requestFocus();
525     }
526     else {
527       super.requestFocus();
528     }
529   }
530
531   public boolean requestFocusInWindow() {
532     final JComponent toFocus = getToFocus();
533     if (toFocus != null) {
534       return toFocus.requestFocusInWindow();
535     }
536     else {
537       return super.requestFocusInWindow();
538     }
539   }
540
541   private JBTabsImpl findTabs(Component c) {
542     Component eachParent = c;
543     while (eachParent != null) {
544       if (eachParent instanceof JBTabsImpl) {
545         return (JBTabsImpl)eachParent;
546       }
547       eachParent = eachParent.getParent();
548     }
549
550     return null;
551   }
552
553
554   @NotNull
555   public TabInfo addTab(TabInfo info, int index) {
556     if (getTabs().contains(info)) {
557       return getTabs().get(getTabs().indexOf(info));
558     }
559
560     info.getChangeSupport().addPropertyChangeListener(this);
561     final TabLabel label = new TabLabel(this, info);
562     myInfo2Label.put(info, label);
563
564     if (index < 0) {
565       myVisibleInfos.add(info);
566     }
567     else if (index > myVisibleInfos.size() - 1) {
568       myVisibleInfos.add(info);
569     }
570     else {
571       myVisibleInfos.add(index, info);
572     }
573
574     resetTabsCache();
575
576
577     updateText(info);
578     updateIcon(info);
579     updateSideComponent(info);
580     updateTabActions(info);
581
582     add(label);
583
584     adjust(info);
585
586     updateAll(false, false);
587
588     if (info.isHidden()) {
589       updateHiding();
590     }
591
592     if (getTabCount() == 1) {
593       fireBeforeSelectionChanged(null, info);
594       fireSelectionChanged(null, info);
595     }
596
597     return info;
598   }
599
600
601   @NotNull
602   public TabInfo addTab(TabInfo info) {
603     return addTab(info, -1);
604   }
605
606   public ActionGroup getPopupGroup() {
607     return myPopupGroup != null ? myPopupGroup.get() : null;
608   }
609
610   public String getPopupPlace() {
611     return myPopupPlace;
612   }
613
614   public JBTabs setPopupGroup(@NotNull final ActionGroup popupGroup, @NotNull String place, final boolean addNavigationGroup) {
615     return setPopupGroup(new Getter<ActionGroup>() {
616       public ActionGroup get() {
617         return popupGroup;
618       }
619     }, place, addNavigationGroup);
620   }
621
622   public JBTabs setPopupGroup(@NotNull final Getter<ActionGroup> popupGroup,
623                               @NotNull final String place,
624                               final boolean addNavigationGroup) {
625     myPopupGroup = popupGroup;
626     myPopupPlace = place;
627     myAddNavigationGroup = addNavigationGroup;
628     return this;
629   }
630
631   private void updateAll(final boolean forcedRelayout, final boolean now) {
632     mySelectedInfo = getSelectedInfo();
633     updateContainer(forcedRelayout, now);
634     removeDeferred();
635     updateListeners();
636     updateTabActions(false);
637     updateEnabling();
638   }
639
640   private boolean isMyChildIsFocusedNow() {
641     final Component owner = getFocusOwner();
642     if (owner == null) return false;
643
644
645     if (mySelectedInfo != null) {
646       if (!SwingUtilities.isDescendingFrom(owner, mySelectedInfo.getComponent())) return false;
647     }
648
649     return SwingUtilities.isDescendingFrom(owner, this);
650   }
651
652   @Nullable
653   private static JComponent getFocusOwner() {
654     final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
655     return (JComponent)(owner instanceof JComponent ? owner : null);
656   }
657
658   public ActionCallback select(@NotNull TabInfo info, boolean requestFocus) {
659     return _setSelected(info, requestFocus);
660   }
661
662   private ActionCallback _setSelected(final TabInfo info, final boolean requestFocus) {
663     if (mySelectedInfo != null && mySelectedInfo.equals(info)) {
664       if (!requestFocus) {
665         return new ActionCallback.Done();
666       }
667       else {
668         return requestFocus(getToFocus());
669       }
670     }
671
672     if (myRequestFocusOnLastFocusedComponent && mySelectedInfo != null) {
673       if (isMyChildIsFocusedNow()) {
674         mySelectedInfo.setLastFocusOwner(getFocusOwner());
675       }
676     }
677
678     TabInfo oldInfo = mySelectedInfo;
679     mySelectedInfo = info;
680     final TabInfo newInfo = getSelectedInfo();
681
682     fireBeforeSelectionChanged(oldInfo, newInfo);
683
684     updateContainer(false, true);
685
686     fireSelectionChanged(oldInfo, newInfo);
687
688     if (requestFocus) {
689       final JComponent toFocus = getToFocus();
690       if (myProject != null && toFocus != null) {
691         final ActionCallback result = new ActionCallback();
692         requestFocus(toFocus).doWhenProcessed(new Runnable() {
693           public void run() {
694             if (myDisposed) {
695               result.setRejected();
696             }
697             else {
698               removeDeferred().notifyWhenDone(result);
699             }
700           }
701         });
702         return result;
703       }
704       else {
705         requestFocus();
706         return removeDeferred();
707       }
708     }
709     else {
710       return removeDeferred();
711     }
712   }
713
714   private void fireBeforeSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
715     if (oldInfo != newInfo) {
716       for (TabsListener eachListener : myTabListeners) {
717         eachListener.beforeSelectionChanged(oldInfo, newInfo);
718       }
719     }
720   }
721
722   private void fireSelectionChanged(TabInfo oldInfo, TabInfo newInfo) {
723     if (oldInfo != newInfo) {
724       for (TabsListener eachListener : myTabListeners) {
725         if (eachListener != null) {
726           eachListener.selectionChanged(oldInfo, newInfo);
727         }
728       }
729     }
730   }
731
732   private ActionCallback requestFocus(final JComponent toFocus) {
733     if (toFocus == null) return new ActionCallback.Done();
734
735     if (myTestMode) {
736       toFocus.requestFocus();
737       return new ActionCallback.Done();
738     }
739
740     return myFocusManager.requestFocus(new FocusCommand.ByComponent(toFocus), true);
741   }
742
743   private ActionCallback removeDeferred() {
744     final ActionCallback callback = new ActionCallback();
745
746     final long executionRequest = ++myRemoveDefferredRequest;
747
748     final Runnable onDone = new Runnable() {
749       public void run() {
750         if (myRemoveDefferredRequest == executionRequest) {
751           removeDeferredNow();
752         }
753
754         callback.setDone();
755       }
756     };
757
758     myFocusManager.doWhenFocusSettlesDown(onDone);
759
760     return callback;
761   }
762
763   private void queueForRemove(Component c) {
764     if (c instanceof JComponent) {
765       addToDeferredRemove(c);
766     }
767     else {
768       remove(c);
769     }
770   }
771
772   private void unqueueFromRemove(Component c) {
773     myDeferredToRemove.remove(c);
774   }
775
776   private void removeDeferredNow() {
777     for (Component each : myDeferredToRemove.keySet()) {
778       if (each != null && each.getParent() == this) {
779         remove(each);
780       }
781     }
782     myDeferredToRemove.clear();
783   }
784
785   private void printRemoveInfo(final Component each) {
786     TabInfo removingInfo = null;
787     final List<TabInfo> all = getTabs();
788     for (TabInfo eachInfo : all) {
789       if (eachInfo.getComponent() == each) {
790         removingInfo = eachInfo;
791         break;
792       }
793     }
794
795     //System.out.println(" - removing " + (removingInfo != null ? " component for " + removingInfo : each));
796   }
797
798   public void propertyChange(final PropertyChangeEvent evt) {
799     final TabInfo tabInfo = (TabInfo)evt.getSource();
800     if (TabInfo.ACTION_GROUP.equals(evt.getPropertyName())) {
801       updateSideComponent(tabInfo);
802       relayout(false, false);
803     }
804     else if (TabInfo.COMPONENT.equals(evt.getPropertyName())) {
805       relayout(true, false);
806     }
807     else if (TabInfo.TEXT.equals(evt.getPropertyName())) {
808       updateText(tabInfo);
809     }
810     else if (TabInfo.ICON.equals(evt.getPropertyName())) {
811       updateIcon(tabInfo);
812     }
813     else if (TabInfo.TAB_COLOR.equals(evt.getPropertyName())) {
814       updateColor(tabInfo);
815     }
816     else if (TabInfo.ALERT_STATUS.equals(evt.getPropertyName())) {
817       boolean start = ((Boolean)evt.getNewValue()).booleanValue();
818       updateAttraction(tabInfo, start);
819     }
820     else if (TabInfo.TAB_ACTION_GROUP.equals(evt.getPropertyName())) {
821       updateTabActions(tabInfo);
822       relayout(false, false);
823     }
824     else if (TabInfo.HIDDEN.equals(evt.getPropertyName())) {
825       updateHiding();
826       relayout(false, false);
827     }
828     else if (TabInfo.ENABLED.equals(evt.getPropertyName())) {
829       updateEnabling();
830     }
831   }
832
833   private void updateEnabling() {
834     final List<TabInfo> all = getTabs();
835     for (TabInfo each : all) {
836       final TabLabel eachLabel = myInfo2Label.get(each);
837       eachLabel.setTabEnabled(each.isEnabled());
838     }
839
840     final TabInfo selected = getSelectedInfo();
841     if (selected != null && !selected.isEnabled()) {
842       final TabInfo toSelect = getToSelectOnRemoveOf(selected);
843       if (toSelect != null) {
844         select(toSelect, myFocusManager.getFocusedDescendantFor(this) != null);
845       }
846     }
847   }
848
849   private void updateHiding() {
850     boolean update = false;
851
852     Iterator<TabInfo> visible = myVisibleInfos.iterator();
853     while (visible.hasNext()) {
854       TabInfo each = visible.next();
855       if (each.isHidden() && !myHiddenInfos.containsKey(each)) {
856         myHiddenInfos.put(each, myVisibleInfos.indexOf(each));
857         visible.remove();
858         update = true;
859       }
860     }
861
862
863     Iterator<TabInfo> hidden = myHiddenInfos.keySet().iterator();
864     while (hidden.hasNext()) {
865       TabInfo each = hidden.next();
866       if (!each.isHidden() && myHiddenInfos.containsKey(each)) {
867         myVisibleInfos.add(getIndexInVisibleArray(each), each);
868         hidden.remove();
869         update = true;
870       }
871     }
872
873
874     if (update) {
875       resetTabsCache();
876       if (mySelectedInfo != null && myHiddenInfos.containsKey(mySelectedInfo)) {
877         mySelectedInfo = getToSelectOnRemoveOf(mySelectedInfo);
878       }
879       updateAll(true, false);
880     }
881   }
882
883   private int getIndexInVisibleArray(TabInfo each) {
884     Integer index = myHiddenInfos.get(each);
885     if (index == null) {
886       index = Integer.valueOf(myVisibleInfos.size());
887     }
888
889     if (index > myVisibleInfos.size()) {
890       index = myVisibleInfos.size();
891     }
892
893     if (index.intValue() < 0) {
894       index = 0;
895     }
896
897     return index.intValue();
898   }
899
900   private void updateIcon(final TabInfo tabInfo) {
901     updateTab(new Runnable() {
902       public void run() {
903         myInfo2Label.get(tabInfo).setIcon(tabInfo.getIcon());
904       }
905     }, tabInfo);
906   }
907
908   private void updateColor(final TabInfo tabInfo) {
909     myInfo2Label.get(tabInfo).setInactiveStateImage(null);
910
911     updateTab(new Runnable() {
912       public void run() {
913         repaint();
914       }
915     }, tabInfo);
916   }
917
918   private void updateTab(Runnable update, TabInfo info) {
919     final TabLabel label = myInfo2Label.get(info);
920     update.run();
921     if (label.getRootPane() != null) {
922       if (label.isValid()) {
923         label.repaint();
924       }
925       else {
926         revalidateAndRepaint(false);
927       }
928     }
929   }
930
931   void revalidateAndRepaint(final boolean layoutNow) {
932
933     if (myVisibleInfos.isEmpty()) {
934       setOpaque(false);
935       final Component nonOpaque = UIUtil.findUltimateParent(this);
936       if (nonOpaque != null && getParent() != null) {
937         final Rectangle toRepaint = SwingUtilities.convertRectangle(getParent(), getBounds(), nonOpaque);
938         nonOpaque.repaint(toRepaint.x, toRepaint.y, toRepaint.width, toRepaint.height);
939       }
940     }
941     else {
942       setOpaque(true);
943     }
944
945     if (layoutNow) {
946       validate();
947     }
948     else {
949       revalidate();
950     }
951
952     repaint();
953   }
954
955
956   private void updateAttraction(final TabInfo tabInfo, boolean start) {
957     if (start) {
958       myAttractions.add(tabInfo);
959     }
960     else {
961       myAttractions.remove(tabInfo);
962       tabInfo.setBlinkCount(0);
963     }
964
965     if (start && !myAnimator.isRunning()) {
966       myAnimator.resume();
967     }
968     else if (!start && myAttractions.isEmpty()) {
969       myAnimator.suspend();
970       repaintAttractions();
971     }
972   }
973
974   private void updateText(final TabInfo tabInfo) {
975     updateTab(new Runnable() {
976       public void run() {
977         final TabLabel label = myInfo2Label.get(tabInfo);
978         label.setText(tabInfo.getColoredText());
979         label.setToolTipText(tabInfo.getTooltipText());
980       }
981     }, tabInfo);
982   }
983
984   private void updateSideComponent(final TabInfo tabInfo) {
985     final Toolbar old = myInfo2Toolbar.get(tabInfo);
986     if (old != null) {
987       remove(old);
988     }
989
990     final Toolbar toolbar = createToolbarComponent(tabInfo);
991     myInfo2Toolbar.put(tabInfo, toolbar);
992     add(toolbar);
993   }
994
995   private void updateTabActions(final TabInfo info) {
996     myInfo2Label.get(info).setTabActions(info.getTabLabelActions());
997   }
998
999   @Nullable
1000   public TabInfo getSelectedInfo() {
1001     if (!myVisibleInfos.contains(mySelectedInfo)) {
1002       mySelectedInfo = null;
1003     }
1004     return mySelectedInfo != null ? mySelectedInfo : !myVisibleInfos.isEmpty() ? myVisibleInfos.get(0) : null;
1005   }
1006
1007   @Nullable
1008   private TabInfo getToSelectOnRemoveOf(TabInfo info) {
1009     if (!myVisibleInfos.contains(info)) return null;
1010     if (mySelectedInfo != info) return null;
1011
1012     if (myVisibleInfos.size() == 1) return null;
1013
1014     int index = myVisibleInfos.indexOf(info);
1015
1016     TabInfo result = null;
1017     if (index > 0) {
1018       result = findEnabledBackward(index - 1);
1019     }
1020
1021     if (result == null) {
1022       result = findEnabledForward(index + 1);
1023     }
1024
1025     return result;
1026   }
1027
1028   private TabInfo findEnabledForward(int from) {
1029     int index = from;
1030     while (index < myVisibleInfos.size() && index >= 0) {
1031       final TabInfo each = myVisibleInfos.get(index);
1032       if (each.isEnabled()) return each;
1033       index++;
1034     }
1035
1036     return null;
1037   }
1038
1039   private TabInfo findEnabledBackward(int from) {
1040     int index = from;
1041     while (index >= 0 && from < myVisibleInfos.size()) {
1042       final TabInfo each = myVisibleInfos.get(index);
1043       if (each.isEnabled()) return each;
1044       index--;
1045     }
1046
1047     return null;
1048   }
1049
1050   protected Toolbar createToolbarComponent(final TabInfo tabInfo) {
1051     return new Toolbar(this, tabInfo);
1052   }
1053
1054   @NotNull
1055   public TabInfo getTabAt(final int tabIndex) {
1056     return getTabs().get(tabIndex);
1057   }
1058
1059   @NotNull
1060   public List<TabInfo> getTabs() {
1061     if (myAllTabs != null) return myAllTabs;
1062
1063     ArrayList<TabInfo> result = new ArrayList<TabInfo>();
1064     result.addAll(myVisibleInfos);
1065
1066     for (TabInfo each : myHiddenInfos.keySet()) {
1067       result.add(getIndexInVisibleArray(each), each);
1068     }
1069
1070     myAllTabs = result;
1071
1072     return result;
1073   }
1074
1075   public TabInfo getTargetInfo() {
1076     return myPopupInfo != null ? myPopupInfo : getSelectedInfo();
1077   }
1078
1079   public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
1080   }
1081
1082   public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
1083     resetPopup();
1084   }
1085
1086   public void popupMenuCanceled(final PopupMenuEvent e) {
1087     resetPopup();
1088   }
1089
1090   private void resetPopup() {
1091 //todo [kirillk] dirty hack, should rely on ActionManager to understand that menu item was either chosen on or cancelled
1092     SwingUtilities.invokeLater(new Runnable() {
1093       public void run() {
1094         myPopupInfo = null;
1095       }
1096     });
1097   }
1098
1099   public void setPaintBlocked(boolean blocked, final boolean takeSnapshot) {
1100     if (blocked && !myPaintBlocked) {
1101       if (takeSnapshot) {
1102         myImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
1103         final Graphics2D g = myImage.createGraphics();
1104         super.paint(g);
1105         g.dispose();
1106       }
1107     }
1108
1109     myPaintBlocked = blocked;
1110
1111     if (!myPaintBlocked) {
1112       if (myImage != null) {
1113         myImage.flush();
1114       }
1115
1116       myImage = null;
1117       repaint();
1118     }
1119   }
1120
1121
1122   private void addToDeferredRemove(final Component c) {
1123     if (!myDeferredToRemove.containsKey(c)) {
1124       myDeferredToRemove.put(c, c);
1125     }
1126   }
1127
1128   public boolean isToDrawBorderIfTabsHidden() {
1129     return myToDrawBorderIfTabsHidden;
1130   }
1131
1132   @NotNull
1133   public JBTabsPresentation setToDrawBorderIfTabsHidden(final boolean toDrawBorderIfTabsHidden) {
1134     myToDrawBorderIfTabsHidden = toDrawBorderIfTabsHidden;
1135     return this;
1136   }
1137
1138   @NotNull
1139   public JBTabs getJBTabs() {
1140     return this;
1141   }
1142
1143   public static class Toolbar extends JPanel {
1144     private final JBTabsImpl myTabs;
1145     private final TabInfo myInfo;
1146
1147     public Toolbar(JBTabsImpl tabs, TabInfo info) {
1148       myTabs = tabs;
1149       myInfo = info;
1150
1151       setLayout(new BorderLayout());
1152
1153       final ActionGroup group = info.getGroup();
1154       final JComponent side = info.getSideComponent();
1155
1156       if (group != null && myTabs.myActionManager != null) {
1157         final String place = info.getPlace();
1158         ActionToolbar toolbar =
1159           myTabs.myActionManager.createActionToolbar(place != null ? place : ActionPlaces.UNKNOWN, group, myTabs.myHorizontalSide);
1160         toolbar.setTargetComponent(info.getActionsContextComponent());
1161         final JComponent actionToolbar = toolbar.getComponent();
1162         add(actionToolbar, BorderLayout.CENTER);
1163       }
1164
1165       if (side != null) {
1166         if (group != null) {
1167           add(side, BorderLayout.EAST);
1168         }
1169         else {
1170           add(side, BorderLayout.CENTER);
1171         }
1172       }
1173     }
1174
1175     public boolean isEmpty() {
1176       return getComponentCount() == 0;
1177     }
1178   }
1179
1180
1181   public void doLayout() {
1182     try {
1183       myHeaderFitSize = computeHeaderFitSize();
1184
1185       final Collection<TabLabel> labels = myInfo2Label.values();
1186       for (TabLabel each : labels) {
1187         each.setTabActionsAutoHide(myTabLabelActionsAutoHide);
1188       }
1189
1190
1191       if (isSingleRow()) {
1192         myLastLayoutPass = mySingleRowLayout.layoutSingleRow();
1193         myTableLayout.myLastTableLayout = null;
1194       }
1195       else {
1196         myLastLayoutPass = myTableLayout.layoutTable();
1197         mySingleRowLayout.myLastSingRowLayout = null;
1198       }
1199
1200       if (isStealthModeEffective() && !isHideTabs()) {
1201         final TabLabel label = getSelectedLabel();
1202         final Rectangle bounds = label.getBounds();
1203         final Insets insets = getLayoutInsets();
1204         layout(label, insets.left, bounds.y, getWidth() - insets.right - insets.left, bounds.height);
1205       }
1206
1207
1208       moveDraggedTabLabel();
1209
1210       myTabActionsAutoHideListener.processMouseOver();
1211     }
1212     finally {
1213       myForcedRelayout = false;
1214     }
1215
1216     applyResetComponents();
1217   }
1218
1219   void moveDraggedTabLabel() {
1220     if (myDragHelper != null && myDragHelper.myDragRec != null) {
1221       final TabLabel selectedLabel = myInfo2Label.get(getSelectedInfo());
1222       if (selectedLabel != null) {
1223         final Rectangle bounds = selectedLabel.getBounds();
1224         if (isHorizontalTabs()) {
1225           selectedLabel.setBounds(myDragHelper.myDragRec.x, bounds.y, bounds.width, bounds.height);
1226         }
1227         else {
1228           selectedLabel.setBounds(bounds.x, myDragHelper.myDragRec.y, bounds.width, bounds.height);
1229         }
1230       }
1231     }
1232   }
1233
1234   private Dimension computeHeaderFitSize() {
1235     final Max max = computeMaxSize();
1236
1237     if (myPosition == JBTabsPosition.top || myPosition == JBTabsPosition.bottom) {
1238       return new Dimension(getSize().width, myHorizontalSide ? Math.max(max.myLabel.height, max.myToolbar.height) : max.myLabel.height);
1239     }
1240     else {
1241       return new Dimension(max.myLabel.width + (myHorizontalSide ? 0 : max.myToolbar.width), getSize().height);
1242     }
1243   }
1244
1245   public Rectangle layoutComp(int componentX, int componentY, final JComponent comp, int deltaWidth, int deltaHeight) {
1246     final Insets insets = getLayoutInsets();
1247
1248     final Insets border = isHideTabs() ? new Insets(0, 0, 0, 0) : myBorder.getEffectiveBorder();
1249     final boolean noTabsVisible = isStealthModeEffective() || isHideTabs();
1250
1251     if (noTabsVisible) {
1252       border.top = getBorder(-1);
1253       border.bottom = getBorder(-1);
1254       border.left = getBorder(-1);
1255       border.right = getBorder(-1);
1256     }
1257
1258     final Insets inner = getInnerInsets();
1259     border.top += inner.top;
1260     border.bottom += inner.bottom;
1261     border.left += inner.left;
1262     border.right += inner.right;
1263
1264
1265     int x = insets.left + componentX + border.left;
1266     int y = insets.top + componentY + border.top;
1267     int width = getWidth() - insets.left - insets.right - componentX - border.left - border.right;
1268     int height = getHeight() - insets.top - insets.bottom - componentY - border.top - border.bottom;
1269
1270     if (!noTabsVisible) {
1271       width += deltaWidth;
1272       height += deltaHeight;
1273     }
1274
1275     return layout(comp, x, y, width, height);
1276   }
1277
1278
1279   public JBTabsPresentation setInnerInsets(final Insets innerInsets) {
1280     myInnerInsets = innerInsets;
1281     return this;
1282   }
1283
1284   public Insets getInnerInsets() {
1285     return myInnerInsets;
1286   }
1287
1288   public Insets getLayoutInsets() {
1289     Insets insets = getInsets();
1290     if (insets == null) {
1291       insets = new Insets(0, 0, 0, 0);
1292     }
1293     return insets;
1294   }
1295
1296   private int fixInset(int inset, int addin) {
1297     return inset + addin;
1298   }
1299
1300
1301   public int getToolbarInset() {
1302     return getArcSize() + 1;
1303   }
1304
1305   public void resetLayout(boolean resetLabels) {
1306     if (resetLabels) {
1307       mySingleRowLayout.myLeftGhost.reset();
1308       mySingleRowLayout.myRightGhost.reset();
1309     }
1310
1311     for (TabInfo each : myVisibleInfos) {
1312       reset(each, resetLabels);
1313     }
1314
1315     for (TabInfo each : myHiddenInfos.keySet()) {
1316       reset(each, resetLabels);
1317     }
1318
1319     for (Component eachDeferred : myDeferredToRemove.keySet()) {
1320       resetLayout((JComponent)eachDeferred);
1321     }
1322   }
1323
1324   private void reset(final TabInfo each, final boolean resetLabels) {
1325     final JComponent c = each.getComponent();
1326     if (c != null) {
1327       resetLayout(c);
1328     }
1329
1330     resetLayout(myInfo2Toolbar.get(each));
1331
1332     if (resetLabels) {
1333       resetLayout(myInfo2Label.get(each));
1334     }
1335   }
1336
1337
1338   private int getArcSize() {
1339     return 4;
1340   }
1341
1342   public int getGhostTabLength() {
1343     return 15;
1344   }
1345
1346
1347   protected void paintComponent(final Graphics g) {
1348     super.paintComponent(g);
1349
1350     if (myVisibleInfos.isEmpty()) return;
1351
1352     Graphics2D g2d = (Graphics2D)g;
1353
1354     final GraphicsConfig config = new GraphicsConfig(g2d);
1355     config.setAntialiasing(true);
1356
1357
1358     g2d.setColor(getBackground());
1359     final Rectangle clip = g2d.getClipBounds();
1360     g2d.fillRect(clip.x, clip.y, clip.width, clip.height);
1361
1362     final TabInfo selected = getSelectedInfo();
1363
1364     boolean leftGhostExists = isSingleRow();
1365     boolean rightGhostExists = isSingleRow();
1366
1367     if (!isStealthModeEffective() && !isHideTabs()) {
1368       if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.lastGhostVisible) {
1369         paintLastGhost(g2d);
1370       }
1371
1372
1373       paintNonSelectedTabs(g2d, leftGhostExists);
1374
1375       if (isSingleRow() && mySingleRowLayout.myLastSingRowLayout.firstGhostVisible) {
1376         paintFirstGhost(g2d);
1377       }
1378
1379     }
1380
1381     config.setAntialiasing(false);
1382
1383     if (isSideComponentVertical()) {
1384       Toolbar toolbarComp = myInfo2Toolbar.get(mySelectedInfo);
1385       if (toolbarComp != null && !toolbarComp.isEmpty()) {
1386         Rectangle toolBounds = toolbarComp.getBounds();
1387         g2d.setColor(CaptionPanel.CNT_ACTIVE_COLOR);
1388         g2d.drawLine((int)toolBounds.getMaxX(), toolBounds.y, (int)toolBounds.getMaxX(), (int)toolBounds.getMaxY() - 1);
1389       }
1390     }
1391
1392     config.restore();
1393   }
1394
1395   private Color getActiveTabColor(final Color c) {
1396     final TabInfo info = getSelectedInfo();
1397     if (info == null) {
1398       return c;
1399     }
1400
1401     final Color tabColor = info.getTabColor();
1402     return tabColor == null ? c : tabColor;
1403   }
1404
1405   private void paintSelectionAndBorder(Graphics2D g2d) {
1406     if (getSelectedLabel() == null) return;
1407
1408     final ShapeInfo shapeInfo = computeSelectedLabelShape();
1409     if (!isHideTabs()) {
1410       g2d.setColor(getBackground());
1411       g2d.fill(shapeInfo.fillPath.getShape());
1412     }
1413
1414     final int alpha;
1415     int paintTopY = shapeInfo.labelTopY;
1416     int paintBottomY = shapeInfo.labelBottomY;
1417     final boolean paintFocused = myPaintFocus && (myFocused || myActivePopup != null);
1418     Color bgPreFill = null;
1419     if (paintFocused) {
1420       final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1421       if (bgColor == null) {
1422         shapeInfo.from = UIUtil.getFocusedFillColor();
1423         shapeInfo.to = UIUtil.getFocusedFillColor();
1424       }
1425       else {
1426         bgPreFill = bgColor;
1427         alpha = 255;
1428         paintBottomY = shapeInfo.labelTopY + shapeInfo.labelPath.deltaY(getArcSize() - 2);
1429         shapeInfo.from = UIUtil.toAlpha(UIUtil.getFocusedFillColor(), alpha);
1430         shapeInfo.to = UIUtil.toAlpha(getActiveTabFillIn(), alpha);
1431       }
1432     }
1433     else {
1434       final Color bgColor = getActiveTabColor(getActiveTabFillIn());
1435       if (isPaintFocus()) {
1436         if (bgColor == null) {
1437           alpha = 150;
1438           shapeInfo.from = UIUtil.toAlpha(UIUtil.getPanelBackgound().brighter(), alpha);
1439           shapeInfo.to = UIUtil.toAlpha(UIUtil.getPanelBackgound(), alpha);
1440         }
1441         else {
1442           alpha = 255;
1443           shapeInfo.from = UIUtil.toAlpha(bgColor, alpha);
1444           shapeInfo.to = UIUtil.toAlpha(bgColor, alpha);
1445         }
1446       }
1447       else {
1448         alpha = 255;
1449         final Color tabColor = getActiveTabColor(null);
1450         shapeInfo.from = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1451         shapeInfo.to = UIUtil.toAlpha(tabColor == null ? Color.white : tabColor, alpha);
1452       }
1453     }
1454
1455     if (!isHideTabs()) {
1456       if (bgPreFill != null) {
1457         g2d.setColor(bgPreFill);
1458         g2d.fill(shapeInfo.fillPath.getShape());
1459       }
1460
1461       final Line2D.Float gradientLine =
1462         shapeInfo.fillPath.transformLine(shapeInfo.fillPath.getX(), paintTopY, shapeInfo.fillPath.getX(), paintBottomY);
1463
1464
1465       g2d.setPaint(new GradientPaint((float)gradientLine.getX1(), (float)gradientLine.getY1(),
1466                                      shapeInfo.fillPath.transformY1(shapeInfo.from, shapeInfo.to), (float)gradientLine.getX2(),
1467                                      (float)gradientLine.getY2(), shapeInfo.fillPath.transformY1(shapeInfo.to, shapeInfo.from)));
1468       g2d.fill(shapeInfo.fillPath.getShape());
1469     }
1470
1471     final Color tabColor = getActiveTabColor(null);
1472     Color borderColor = tabColor == null ? UIUtil.getBoundsColor(paintFocused) : tabColor.darker();
1473     g2d.setColor(borderColor);
1474
1475     if (!isHideTabs()) {
1476       g2d.draw(shapeInfo.path.getShape());
1477     }
1478
1479     paintBorder(g2d, shapeInfo, borderColor);
1480   }
1481
1482   private ShapeInfo computeSelectedLabelShape() {
1483     final ShapeInfo shape = new ShapeInfo();
1484
1485     shape.path = getEffectiveLayout().createShapeTransform(getSize());
1486     shape.insets = shape.path.transformInsets(getLayoutInsets());
1487     shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds());
1488
1489     shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1490     shape.labelTopY = shape.labelPath.getY();
1491     shape.labelLeftX = shape.labelPath.getX();
1492     shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth());
1493
1494     shape.path.moveTo(shape.insets.left, shape.labelBottomY);
1495     shape.path.lineTo(shape.labelLeftX, shape.labelBottomY);
1496     shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize()));
1497     shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY);
1498
1499     int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1);
1500
1501     if (isStealthModeEffective()) {
1502       shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1503       shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1504       shape.path.lineTo(lastX, shape.labelBottomY);
1505     }
1506     else {
1507       shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1508       shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1509       if (myLastLayoutPass.hasCurveSpaceFor(getSelectedInfo())) {
1510         shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize()));
1511         shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY);
1512       }
1513       else {
1514         shape.path.lineTo(shape.labelRightX, shape.labelBottomY);
1515       }
1516     }
1517
1518     shape.path.lineTo(lastX, shape.labelBottomY);
1519
1520     if (isStealthModeEffective()) {
1521       shape.path.closePath();
1522     }
1523
1524     shape.fillPath = shape.path.copy();
1525     if (!isHideTabs()) {
1526       shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1));
1527       shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1));
1528       shape.fillPath.closePath();
1529     }
1530     return shape;
1531   }
1532
1533   private TabLabel getSelectedLabel() {
1534     return myInfo2Label.get(getSelectedInfo());
1535   }
1536
1537   static class ShapeInfo {
1538     ShapeTransform path;
1539     ShapeTransform fillPath;
1540     ShapeTransform labelPath;
1541     int labelBottomY;
1542     int labelTopY;
1543     int labelLeftX;
1544     int labelRightX;
1545     Insets insets;
1546     Color from;
1547     Color to;
1548   }
1549
1550
1551   private void paintFirstGhost(Graphics2D g2d) {
1552     final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost);
1553
1554     int topX = path.getX() + path.deltaX(getCurveArc());
1555     int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1556     int bottomX = path.getMaxX() + path.deltaX(1);
1557     int bottomY = path.getMaxY() + path.deltaY(1);
1558
1559     path.moveTo(topX, topY);
1560
1561     final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0;
1562
1563     if (isLeftFromSelection) {
1564       path.lineTo(bottomX, topY);
1565     }
1566     else {
1567       path.lineTo(bottomX - getArcSize(), topY);
1568       path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize()));
1569     }
1570
1571     path.lineTo(bottomX, bottomY);
1572     path.lineTo(topX, bottomY);
1573
1574     path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX,
1575                 bottomY - path.deltaY(Math.abs(bottomY - topY) / 2));
1576
1577     path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY);
1578
1579     path.closePath();
1580
1581     g2d.setColor(getBackground());
1582     g2d.fill(path.getShape());
1583
1584     g2d.setColor(getBoundsColor());
1585     g2d.draw(path.getShape());
1586
1587     g2d.setColor(getTopBlickColor());
1588     g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1));
1589
1590     g2d.setColor(getRightBlockColor());
1591     g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1));
1592   }
1593
1594   private void paintLastGhost(Graphics2D g2d) {
1595     final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost);
1596
1597     int topX = path.getX() - path.deltaX(getArcSize());
1598     int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1599     int bottomX = path.getMaxX() - path.deltaX(getCurveArc());
1600     int bottomY = path.getMaxY() + path.deltaY(1);
1601
1602     path.moveTo(topX, topY);
1603     path.lineTo(bottomX, topY);
1604     path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2);
1605     path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY);
1606     path.lineTo(topX, bottomY);
1607
1608     path.closePath();
1609
1610     g2d.setColor(getBackground());
1611     g2d.fill(path.getShape());
1612
1613     g2d.setColor(getBoundsColor());
1614     g2d.draw(path.getShape());
1615
1616     g2d.setColor(getTopBlickColor());
1617     g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1));
1618   }
1619
1620   private int getCurveArc() {
1621     return 2;
1622   }
1623
1624   private Color getBoundsColor() {
1625     return Color.gray;
1626   }
1627
1628   private Color getRightBlockColor() {
1629     return Color.lightGray;
1630   }
1631
1632   private Color getTopBlickColor() {
1633     return Color.white;
1634   }
1635
1636   private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists) {
1637     TabInfo selected = getSelectedInfo();
1638     if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) {
1639       List<TabInfo> tabs = getTabs();
1640       for (TabInfo each : tabs) {
1641         myInfo2Label.get(each).setInactiveStateImage(null);
1642       }
1643     }
1644
1645     for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) {
1646       for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) {
1647         final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn);
1648         if (getSelectedInfo() == each) {
1649           continue;
1650         }
1651         paintNonSelected(g2d, each, leftGhostExists);
1652       }
1653     }
1654
1655     myLastPaintedSelection = selected;
1656   }
1657
1658   private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists) {
1659     final TabLabel label = myInfo2Label.get(each);
1660     if (label.getBounds().width == 0) return;
1661
1662     int imageInsets = getArcSize() + 1;
1663
1664     Rectangle bounds = label.getBounds();
1665
1666     int x = bounds.x - imageInsets;
1667     int y = bounds.y;
1668     int width = bounds.width + imageInsets * 2 + 1;
1669     int height = bounds.height + getArcSize() + 1;
1670
1671     if (isToBufferPainting()) {
1672       BufferedImage img = label.getInactiveStateImage(bounds);
1673
1674       if (img == null) {
1675         img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
1676         Graphics2D imgG2d = img.createGraphics();
1677         imgG2d.addRenderingHints(g2d.getRenderingHints());
1678         doPaintInactictive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()));
1679         imgG2d.dispose();
1680       }
1681
1682       g2d.drawImage(img, x, y, width, height, null);
1683
1684       label.setInactiveStateImage(img);
1685     } else {
1686       doPaintInactictive(g2d, leftGhostExists, label, label.getBounds());
1687       label.setInactiveStateImage(null);
1688     }
1689   }
1690
1691   private boolean isToBufferPainting() {
1692     return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint;
1693   }
1694
1695   private void doPaintInactictive(Graphics2D g2d, boolean leftGhostExists, TabLabel label, Rectangle effectiveBounds) {
1696     int tabIndex = myVisibleInfos.indexOf(label.getInfo());
1697
1698     final int arc = getArcSize();
1699     Color topBlickColor = getTopBlickColor();
1700     Color rightBlockColor = getRightBlockColor();
1701     Color boundsColor = getBoundsColor();
1702     Color backgroundColor = getBackground();
1703
1704     final Color tabColor = label.getInfo().getTabColor();
1705     if (tabColor != null) {
1706       backgroundColor = tabColor;
1707       boundsColor = tabColor.darker();
1708       topBlickColor = tabColor.brighter().brighter();
1709       rightBlockColor = tabColor;
1710     }
1711
1712     final TabInfo selected = getSelectedInfo();
1713     final int selectionTabVShift = getSelectionTabVShift();
1714
1715
1716     final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex));
1717     final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex));
1718
1719
1720     boolean firstShowing = prev == null;
1721     if (!firstShowing && !leftGhostExists) {
1722       firstShowing = myInfo2Label.get(prev).getBounds().width == 0;
1723     }
1724
1725     boolean lastShowing = next == null;
1726     if (!lastShowing) {
1727       lastShowing = myInfo2Label.get(next).getBounds().width == 0;
1728     }
1729
1730     boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1;
1731
1732     Rectangle originalBounds = effectiveBounds;
1733     final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds);
1734
1735     int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1);
1736     int topY = shape.getY() + shape.deltaY(selectionTabVShift);
1737     int rigthX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX();
1738     int bottomY = shape.getMaxY() + shape.deltaY(1);
1739
1740     shape.moveTo(leftX, bottomY);
1741     shape.lineTo(leftX, topY + shape.deltaY(arc));
1742     shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY);
1743     shape.lineTo(rigthX - shape.deltaX(arc), topY);
1744     shape.quadTo(rigthX, topY, rigthX, topY + shape.deltaY(arc));
1745     shape.lineTo(rigthX, bottomY);
1746
1747     if (!isSingleRow()) {
1748       final TablePassInfo info = myTableLayout.myLastTableLayout;
1749       if (!info.isInSelectionRow(label.getInfo())) {
1750         shape.lineTo(rigthX, bottomY + shape.deltaY(getArcSize()));
1751         shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize()));
1752         shape.lineTo(leftX, bottomY);
1753       }
1754     }
1755
1756     shape.closePath();
1757
1758     g2d.setColor(backgroundColor);
1759     g2d.fill(shape.getShape());
1760
1761     // TODO
1762
1763     final Line2D.Float gradientLine =
1764       shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 )));
1765
1766     final GradientPaint gp =
1767       new GradientPaint(gradientLine.x1, gradientLine.y1,
1768                         shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor),
1769                         gradientLine.x2, gradientLine.y2,
1770                         shape.transformY1(backgroundColor, backgroundColor.brighter().brighter()));
1771     final Paint old = g2d.getPaint();
1772     g2d.setPaint(gp);
1773     g2d.fill(shape.getShape());
1774     g2d.setPaint(old);
1775
1776     g2d.setColor(topBlickColor);
1777     g2d.draw(
1778       shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rigthX - shape.deltaX(arc - 1), topY + shape.deltaY(1)));
1779
1780     g2d.setColor(rightBlockColor);
1781     g2d.draw(shape.transformLine(rigthX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rigthX - shape.deltaX(1), bottomY));
1782
1783     g2d.setColor(boundsColor);
1784     g2d.draw(shape.getShape());
1785   }
1786
1787   public int getSelectionTabVShift() {
1788     return 2;
1789   }
1790
1791   private void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) {
1792
1793     final ShapeTransform shaper = shape.path.copy().reset();
1794
1795     final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder());
1796
1797     int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1798
1799     int bottomY = topY + paintBorder.top - 2;
1800     int middleY = topY + (bottomY - topY) / 2;
1801
1802
1803     final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left);
1804
1805     final int boundsY =
1806       isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1);
1807
1808     final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom;
1809     final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right));
1810
1811     if (paintBorder.top > 0) {
1812       if (isHideTabs()) {
1813         if (isToDrawBorderIfTabsHidden()) {
1814           g2d.setColor(borderColor);
1815           g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape());
1816         }
1817       }
1818       else {
1819         Color tabFillColor = getActiveTabColor(null);
1820         if (tabFillColor == null) {
1821           tabFillColor = shape.path.transformY1(shape.to, shape.from);
1822         }
1823
1824         g2d.setColor(tabFillColor);
1825         g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape());
1826
1827         g2d.setColor(borderColor);
1828         if (paintBorder.top == 2) {
1829           final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY);
1830
1831           g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2);
1832         }
1833         else if (paintBorder.top > 2) {
1834 //todo kirillk
1835 //start hack
1836           int deltaY = 0;
1837           if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) {
1838             deltaY = 1;
1839           }
1840 //end hack
1841           final int topLine = topY + shape.path.deltaY(paintBorder.top - 1);
1842           g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape());
1843         }
1844       }
1845     }
1846
1847     g2d.setColor(borderColor);
1848
1849     //bottom
1850     g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth,
1851                                    paintBorder.bottom).getShape());
1852
1853     //left
1854     g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape());
1855
1856     //right
1857     g2d.fill(shaper.reset()
1858       .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape());
1859
1860   }
1861
1862   public boolean isStealthModeEffective() {
1863     return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top;
1864   }
1865
1866
1867   private boolean isNavigationVisible() {
1868     if (myStealthTabMode && getTabCount() == 1) return false;
1869     return !myVisibleInfos.isEmpty();
1870   }
1871
1872
1873   public void paint(final Graphics g) {
1874     Rectangle clip = g.getClipBounds();
1875     if (clip == null) {
1876       return;
1877     }
1878
1879     if (myPaintBlocked) {
1880       if (myImage != null) {
1881         g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
1882       }
1883       return;
1884     }
1885
1886     super.paint(g);
1887   }
1888
1889   protected void paintChildren(final Graphics g) {
1890     super.paintChildren(g);
1891
1892     final GraphicsConfig config = new GraphicsConfig(g);
1893     config.setAntialiasing(true);
1894     paintSelectionAndBorder((Graphics2D)g);
1895     config.restore();
1896
1897     final TabLabel selected = getSelectedLabel();
1898     if (selected != null) {
1899       selected.paintImage(g);
1900     }
1901
1902     mySingleRowLayout.myMoreIcon.paintIcon(this, g);
1903   }
1904
1905   private Max computeMaxSize() {
1906     Max max = new Max();
1907     for (TabInfo eachInfo : myVisibleInfos) {
1908       final TabLabel label = myInfo2Label.get(eachInfo);
1909       max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height);
1910       max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width);
1911       final Toolbar toolbar = myInfo2Toolbar.get(eachInfo);
1912       if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) {
1913         max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height);
1914         max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width);
1915       }
1916     }
1917
1918     max.myToolbar.height++;
1919
1920     return max;
1921   }
1922
1923   public Dimension getMinimumSize() {
1924     return computeSize(new Transform<JComponent, Dimension>() {
1925       public Dimension transform(JComponent component) {
1926         return component.getMinimumSize();
1927       }
1928     }, 1);
1929   }
1930
1931   public Dimension getPreferredSize() {
1932     return computeSize(new Transform<JComponent, Dimension>() {
1933       public Dimension transform(JComponent component) {
1934         return component.getPreferredSize();
1935       }
1936     }, 3);
1937   }
1938
1939   private Dimension computeSize(Transform<JComponent, Dimension> transform, int tabCount) {
1940     Dimension size = new Dimension();
1941     for (TabInfo each : myVisibleInfos) {
1942       final JComponent c = each.getComponent();
1943       if (c != null) {
1944         final Dimension eachSize = transform.transform(c);
1945         size.width = Math.max(eachSize.width, size.width);
1946         size.height = Math.max(eachSize.height, size.height);
1947       }
1948     }
1949
1950     addHeaderSize(size, tabCount);
1951     return size;
1952   }
1953
1954   private void addHeaderSize(Dimension size, final int tabsCount) {
1955     Dimension header = computeHeaderPreferredSize(tabsCount);
1956
1957     size.height += header.height;
1958     size.width += header.width;
1959
1960     final Insets insets = getLayoutInsets();
1961     size.width += insets.left + insets.right + 1;
1962     size.height += insets.top + insets.bottom + 1;
1963   }
1964
1965   private Dimension computeHeaderPreferredSize(int tabsCount) {
1966     final Iterator<TabInfo> infos = myInfo2Label.keySet().iterator();
1967     Dimension size = new Dimension();
1968     int currentTab = 0;
1969
1970     final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
1971
1972     while (infos.hasNext()) {
1973       final boolean canGrow = currentTab < tabsCount;
1974
1975       TabInfo eachInfo = infos.next();
1976       final TabLabel eachLabel = myInfo2Label.get(eachInfo);
1977       final Dimension eachPrefSize = eachLabel.getPreferredSize();
1978       if (horizontal) {
1979         if (canGrow) {
1980           size.width += eachPrefSize.width;
1981         }
1982         size.height = Math.max(size.height, eachPrefSize.height);
1983       }
1984       else {
1985         size.width = Math.max(size.width, eachPrefSize.width);
1986         if (canGrow) {
1987           size.height += eachPrefSize.height;
1988         }
1989       }
1990
1991       currentTab++;
1992     }
1993
1994     if (isSingleRow() && isGhostsAlwaysVisible()) {
1995       if (horizontal) {
1996         size.width += getGhostTabLength() * 2;
1997       }
1998       else {
1999         size.height += getGhostTabLength() * 2;
2000       }
2001     }
2002
2003     if (horizontal) {
2004       size.height += myBorder.getTabBorderSize();
2005     }
2006     else {
2007       size.width += myBorder.getTabBorderSize();
2008     }
2009
2010     return size;
2011   }
2012
2013   public int getTabCount() {
2014     return getTabs().size();
2015   }
2016
2017   @NotNull
2018   public JBTabsPresentation getPresentation() {
2019     return this;
2020   }
2021
2022   public ActionCallback removeTab(final JComponent component) {
2023     return removeTab(findInfo(component));
2024   }
2025
2026   public ActionCallback removeTab(final TabInfo info) {
2027     return removeTab(info, null);
2028   }
2029
2030   public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) {
2031     return removeTab(info, forcedSelectionTranfer, true);
2032   }
2033
2034   public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) {
2035     if (info == null || !getTabs().contains(info)) return new ActionCallback.Done();
2036
2037     final ActionCallback result = new ActionCallback();
2038
2039     TabInfo toSelect;
2040     if (forcedSelectionTranfer == null) {
2041       toSelect = getToSelectOnRemoveOf(info);
2042     }
2043     else {
2044       assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer;
2045       toSelect = forcedSelectionTranfer;
2046     }
2047
2048
2049     if (toSelect != null) {
2050       processRemove(info, false);
2051       _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() {
2052         public void run() {
2053           removeDeferred().notifyWhenDone(result);
2054         }
2055       });
2056     }
2057     else {
2058       processRemove(info, true);
2059       removeDeferred().notifyWhenDone(result);
2060     }
2061
2062     if (myVisibleInfos.isEmpty()) {
2063       removeDeferredNow();
2064     }
2065
2066     revalidateAndRepaint(true);
2067
2068     return result;
2069   }
2070
2071   private void processRemove(final TabInfo info, boolean forcedNow) {
2072     remove(myInfo2Label.get(info));
2073     remove(myInfo2Toolbar.get(info));
2074
2075     JComponent tabComponent = info.getComponent();
2076
2077     if (!isToDeferRemoveForLater(tabComponent) || forcedNow) {
2078       remove(tabComponent);
2079     }
2080     else {
2081       queueForRemove(tabComponent);
2082     }
2083
2084     myVisibleInfos.remove(info);
2085     myHiddenInfos.remove(info);
2086     myInfo2Label.remove(info);
2087     myInfo2Toolbar.remove(info);
2088     resetTabsCache();
2089
2090     updateAll(false, false);
2091   }
2092
2093   public TabInfo findInfo(Component component) {
2094     for (TabInfo each : getTabs()) {
2095       if (each.getComponent() == component) return each;
2096     }
2097
2098     return null;
2099   }
2100
2101   public TabInfo findInfo(String text) {
2102     if (text == null) return null;
2103
2104     for (TabInfo each : getTabs()) {
2105       if (text.equals(each.getText())) return each;
2106     }
2107
2108     return null;
2109   }
2110
2111   public TabInfo findInfo(MouseEvent event) {
2112     return findInfo(event, false);
2113   }
2114
2115   private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) {
2116     final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this);
2117     return _findInfo(point, labelsOnly);
2118   }
2119
2120   public TabInfo findInfo(final Object object) {
2121     for (int i = 0; i < getTabCount(); i++) {
2122       final TabInfo each = getTabAt(i);
2123       final Object eachObject = each.getObject();
2124       if (eachObject != null && eachObject.equals(object)) return each;
2125     }
2126     return null;
2127   }
2128
2129   public TabInfo findTabLabelBy(final Point point) {
2130     return _findInfo(point, true);
2131   }
2132
2133   private TabInfo _findInfo(final Point point, boolean labelsOnly) {
2134     Component component = findComponentAt(point);
2135     if (component == null) return null;
2136     while (component != this || component != null) {
2137       if (component instanceof TabLabel) {
2138         return ((TabLabel)component).getInfo();
2139       }
2140       else if (!labelsOnly) {
2141         final TabInfo info = findInfo(component);
2142         if (info != null) return info;
2143       }
2144       if (component == null) break;
2145       component = component.getParent();
2146     }
2147
2148     return null;
2149   }
2150
2151   public void removeAllTabs() {
2152     for (TabInfo each : getTabs()) {
2153       removeTab(each);
2154     }
2155   }
2156
2157
2158   private static class Max {
2159     Dimension myLabel = new Dimension();
2160     Dimension myToolbar = new Dimension();
2161   }
2162
2163   private void updateContainer(boolean forced, final boolean layoutNow) {
2164     final TabLabel selectedLabel = getSelectedLabel();
2165
2166     for (TabInfo each : myVisibleInfos) {
2167       final JComponent eachComponent = each.getComponent();
2168       if (getSelectedInfo() == each && getSelectedInfo() != null) {
2169         unqueueFromRemove(eachComponent);
2170
2171         final Container parent = eachComponent.getParent();
2172         if (parent != null && parent != this) {
2173           parent.remove(eachComponent);
2174         }
2175
2176         if (eachComponent.getParent() == null) {
2177           add(eachComponent);
2178         }
2179       }
2180       else {
2181         if (eachComponent.getParent() == null) continue;
2182         if (isToDeferRemoveForLater(eachComponent)) {
2183           queueForRemove(eachComponent);
2184         }
2185         else {
2186           remove(eachComponent);
2187         }
2188       }
2189     }
2190
2191     relayout(forced, layoutNow);
2192   }
2193
2194   protected void addImpl(final Component comp, final Object constraints, final int index) {
2195     unqueueFromRemove(comp);
2196
2197     if (comp instanceof TabLabel) {
2198       ((TabLabel)comp).apply(myUiDecorator.getDecoration());
2199     }
2200
2201     super.addImpl(comp, constraints, index);
2202   }
2203
2204
2205   private boolean isToDeferRemoveForLater(JComponent c) {
2206     return c.getRootPane() != null;
2207   }
2208
2209   void relayout(boolean forced, final boolean layoutNow) {
2210     if (!myForcedRelayout) {
2211       myForcedRelayout = forced;
2212     }
2213     revalidateAndRepaint(layoutNow);
2214   }
2215
2216   ActionManager getActionManager() {
2217     return myActionManager;
2218   }
2219
2220   public TabsBorder getTabsBorder() {
2221     return myBorder;
2222   }
2223
2224   @NotNull
2225   public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) {
2226     removeListeners();
2227     myTabMouseListeners.add(listener);
2228     addListeners();
2229     return this;
2230   }
2231
2232   @NotNull
2233   public JBTabs addTabMouseListener(@NotNull MouseListener listener) {
2234     removeListeners();
2235     myTabMouseListeners.add(listener);
2236     addListeners();
2237     return this;
2238   }
2239
2240   @NotNull
2241   public JComponent getComponent() {
2242     return this;
2243   }
2244
2245   public boolean isCycleRoot() {
2246     return false;
2247   }
2248
2249   @NotNull
2250   public JBTabs removeTabMouseListener(@NotNull MouseListener listener) {
2251     removeListeners();
2252     myTabMouseListeners.remove(listener);
2253     addListeners();
2254     return this;
2255   }
2256
2257   private void addListeners() {
2258     for (TabInfo eachInfo : myVisibleInfos) {
2259       final TabLabel label = myInfo2Label.get(eachInfo);
2260       for (EventListener eachListener : myTabMouseListeners) {
2261         if (eachListener instanceof MouseListener) {
2262           label.addMouseListener((MouseListener)eachListener);
2263         }
2264         else if (eachListener instanceof MouseMotionListener) {
2265           label.addMouseMotionListener((MouseMotionListener)eachListener);
2266         }
2267         else {
2268           assert false;
2269         }
2270       }
2271     }
2272   }
2273
2274   private void removeListeners() {
2275     for (TabInfo eachInfo : myVisibleInfos) {
2276       final TabLabel label = myInfo2Label.get(eachInfo);
2277       for (EventListener eachListener : myTabMouseListeners) {
2278         if (eachListener instanceof MouseListener) {
2279           label.removeMouseListener((MouseListener)eachListener);
2280         }
2281         else if (eachListener instanceof MouseMotionListener) {
2282           label.removeMouseMotionListener((MouseMotionListener)eachListener);
2283         }
2284         else {
2285           assert false;
2286         }
2287       }
2288     }
2289   }
2290
2291   private void updateListeners() {
2292     removeListeners();
2293     addListeners();
2294   }
2295
2296   public JBTabs addListener(@NotNull TabsListener listener) {
2297     myTabListeners.add(listener);
2298     return this;
2299   }
2300
2301   public JBTabs removeListener(@NotNull final TabsListener listener) {
2302     myTabListeners.remove(listener);
2303     return this;
2304   }
2305
2306   protected void onPopup(final TabInfo popupInfo) {
2307   }
2308
2309   public void setFocused(final boolean focused) {
2310     if (myFocused == focused) return;
2311
2312     myFocused = focused;
2313
2314     if (myPaintFocus) {
2315       repaint();
2316     }
2317   }
2318
2319   public int getIndexOf(@Nullable final TabInfo tabInfo) {
2320     return myVisibleInfos.indexOf(tabInfo);
2321   }
2322
2323   public boolean isHideTabs() {
2324     return myHideTabs;
2325   }
2326
2327   public void setHideTabs(final boolean hideTabs) {
2328     if (isHideTabs() == hideTabs) return;
2329
2330     myHideTabs = hideTabs;
2331
2332     relayout(true, false);
2333   }
2334
2335   public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
2336     return myBorder.setPaintBorder(top, left, right, bottom);
2337   }
2338
2339   public JBTabsPresentation setTabSidePaintBorder(int size) {
2340     return myBorder.setTabSidePaintBorder(size);
2341   }
2342
2343   static int getBorder(int size) {
2344     return size == -1 ? 1 : size;
2345   }
2346
2347   public boolean isPaintFocus() {
2348     return myPaintFocus;
2349   }
2350
2351   @NotNull
2352   public JBTabsPresentation setAdjustBorders(final boolean adjust) {
2353     myAdjustBorders = adjust;
2354     return this;
2355   }
2356
2357   @NotNull
2358   public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) {
2359     if (!isChanged(myActiveTabFillIn, color)) return this;
2360
2361     myActiveTabFillIn = color;
2362     revalidateAndRepaint(false);
2363     return this;
2364   }
2365
2366   private boolean isChanged(Object oldObject, Object newObject) {
2367     if (oldObject == null && newObject == null) return false;
2368     return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject);
2369   }
2370
2371   @NotNull
2372   public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) {
2373     if (myTabLabelActionsAutoHide != autoHide) {
2374       myTabLabelActionsAutoHide = autoHide;
2375       revalidateAndRepaint(false);
2376     }
2377     return this;
2378   }
2379
2380   @Nullable
2381   public Color getActiveTabFillIn() {
2382     return myActiveTabFillIn;
2383   }
2384
2385   public JBTabsPresentation setFocusCycle(final boolean root) {
2386     setFocusCycleRoot(root);
2387     return this;
2388   }
2389
2390
2391   public JBTabsPresentation setPaintFocus(final boolean paintFocus) {
2392     myPaintFocus = paintFocus;
2393     return this;
2394   }
2395
2396   private abstract static class BaseNavigationAction extends AnAction {
2397
2398     private final ShadowAction myShadow;
2399     private final ActionManager myActionManager;
2400     private final JBTabsImpl myTabs;
2401
2402     protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) {
2403       myActionManager = mgr;
2404       myTabs = tabs;
2405       myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs);
2406       Disposer.register(tabs, myShadow);
2407       setEnabledInModalContext(true);
2408     }
2409
2410     public final void update(final AnActionEvent e) {
2411       JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2412       e.getPresentation().setVisible(tabs != null);
2413       if (tabs == null) return;
2414
2415       final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2416       final boolean enabled = tabs == myTabs && myTabs.isNavigationVisible() && selectedIndex >= 0 && myTabs.myNavigationActionsEnabled;
2417       e.getPresentation().setEnabled(enabled);
2418       if (enabled) {
2419         _update(e, tabs, selectedIndex);
2420       }
2421     }
2422
2423     public void reconnect(String actionId) {
2424       myShadow.reconnect(myActionManager.getAction(actionId));
2425     }
2426
2427     protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex);
2428
2429     public final void actionPerformed(final AnActionEvent e) {
2430       JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2431       if (tabs == null) return;
2432
2433       final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2434       if (index == -1) return;
2435       _actionPerformed(e, tabs, index);
2436     }
2437
2438     protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex);
2439   }
2440
2441   private static class SelectNextAction extends BaseNavigationAction {
2442
2443     private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) {
2444       super(IdeActions.ACTION_NEXT_TAB, tabs, mgr);
2445     }
2446
2447     protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2448       e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex + 1) != null);
2449     }
2450
2451     protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2452       tabs.select(tabs.findEnabledForward(selectedIndex + 1), true);
2453     }
2454   }
2455
2456   private static class SelectPreviousAction extends BaseNavigationAction {
2457     private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) {
2458       super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr);
2459     }
2460
2461     protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2462       e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex - 1) != null);
2463     }
2464
2465     protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2466       tabs.select(tabs.findEnabledBackward(selectedIndex - 1), true);
2467     }
2468   }
2469
2470   private void disposePopupListener() {
2471     if (myActivePopup != null) {
2472       myActivePopup.removePopupMenuListener(myPopupListener);
2473       myActivePopup = null;
2474     }
2475   }
2476
2477   public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) {
2478     myStealthTabMode = stealthTabMode;
2479
2480     relayout(true, false);
2481
2482     return this;
2483   }
2484
2485   public boolean isStealthTabMode() {
2486     return myStealthTabMode;
2487   }
2488
2489   public JBTabsPresentation setSideComponentVertical(final boolean vertical) {
2490     myHorizontalSide = !vertical;
2491
2492     for (TabInfo each : myVisibleInfos) {
2493       each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2");
2494     }
2495
2496
2497     relayout(true, false);
2498
2499     return this;
2500   }
2501
2502   public JBTabsPresentation setSingleRow(boolean singleRow) {
2503     myLayout = singleRow ? mySingleRowLayout : myTableLayout;
2504
2505     relayout(true, false);
2506
2507     return this;
2508   }
2509
2510   public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) {
2511     myGhostsAlwaysVisible = visible;
2512
2513     relayout(true, false);
2514
2515     return this;
2516   }
2517
2518   public boolean isGhostsAlwaysVisible() {
2519     return myGhostsAlwaysVisible;
2520   }
2521
2522   public boolean isSingleRow() {
2523     return getEffectiveLayout() == mySingleRowLayout;
2524   }
2525
2526   public boolean isSideComponentVertical() {
2527     return !myHorizontalSide;
2528   }
2529
2530   private TabLayout getEffectiveLayout() {
2531     if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout;
2532     return mySingleRowLayout;
2533   }
2534
2535   public JBTabsPresentation setUiDecorator(UiDecorator decorator) {
2536     myUiDecorator = decorator == null ? ourDefaultDecorator : decorator;
2537     applyDecoration();
2538     return this;
2539   }
2540
2541   protected void setUI(final ComponentUI newUI) {
2542     super.setUI(newUI);
2543     applyDecoration();
2544   }
2545
2546   public void updateUI() {
2547     super.updateUI();
2548     SwingUtilities.invokeLater(new Runnable() {
2549       public void run() {
2550         applyDecoration();
2551
2552         revalidateAndRepaint(false);
2553       }
2554     });
2555   }
2556
2557   private void applyDecoration() {
2558     if (myUiDecorator != null) {
2559       UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration();
2560       for (TabLabel each : myInfo2Label.values()) {
2561         each.apply(uiDecoration);
2562       }
2563     }
2564
2565
2566     for (TabInfo each : getTabs()) {
2567       adjust(each);
2568     }
2569
2570     relayout(true, false);
2571   }
2572
2573   private void adjust(final TabInfo each) {
2574     if (myAdjustBorders) {
2575       UIUtil.removeScrollBorder(each.getComponent());
2576     }
2577   }
2578
2579   public void sortTabs(Comparator<TabInfo> comparator) {
2580     Collections.sort(myVisibleInfos, comparator);
2581
2582     relayout(true, false);
2583   }
2584
2585   public boolean isRequestFocusOnLastFocusedComponent() {
2586     return myRequestFocusOnLastFocusedComponent;
2587   }
2588
2589   public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) {
2590     myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent;
2591     return this;
2592   }
2593
2594
2595   @Nullable
2596   public Object getData(@NonNls final String dataId) {
2597     if (myDataProvider != null) {
2598       final Object value = myDataProvider.getData(dataId);
2599       if (value != null) return value;
2600     }
2601
2602     if (SwitchProvider.KEY.getName().equals(dataId) && myOwnSwitchProvider) {
2603       return this;
2604     }
2605
2606     if (QuickActionProvider.KEY.getName().equals(dataId)) {
2607       return this;
2608     }
2609
2610     return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null;
2611   }
2612
2613   public List<AnAction> getActions(boolean originalProvider) {
2614     ArrayList<AnAction> result = new ArrayList<AnAction>();
2615
2616     TabInfo selection = getSelectedInfo();
2617     if (selection != null) {
2618       ActionGroup group = selection.getGroup();
2619       if (group != null) {
2620         AnAction[] children = group.getChildren(null);
2621         for (int i = 0; i < children.length; i++) {
2622           result.add(children[i]);
2623         }
2624       }
2625     }
2626
2627     return result;
2628   }
2629
2630   public DataProvider getDataProvider() {
2631     return myDataProvider;
2632   }
2633
2634   public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) {
2635     myDataProvider = dataProvider;
2636     return this;
2637   }
2638
2639
2640   public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) {
2641     if (e.getClickCount() == 1 || canBeQuick) {
2642       if (!e.isPopupTrigger()) {
2643         return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
2644       }
2645     }
2646
2647     return false;
2648   }
2649
2650
2651   private static class DefautDecorator implements UiDecorator {
2652     @NotNull
2653     public UiDecoration getDecoration() {
2654       return new UiDecoration(null, new Insets(1, 4, 1, 5));
2655     }
2656   }
2657
2658   public static Rectangle layout(JComponent c, Rectangle bounds) {
2659     final Rectangle now = c.getBounds();
2660     if (!bounds.equals(now)) {
2661       c.setBounds(bounds);
2662     }
2663     c.putClientProperty(LAYOUT_DONE, Boolean.TRUE);
2664
2665     return bounds;
2666   }
2667
2668   public static Rectangle layout(JComponent c, int x, int y, int width, int height) {
2669     return layout(c, new Rectangle(x, y, width, height));
2670   }
2671
2672   public static void resetLayout(JComponent c) {
2673     if (c == null) return;
2674     c.putClientProperty(LAYOUT_DONE, null);
2675   }
2676
2677   private void applyResetComponents() {
2678     for (int i = 0; i < getComponentCount(); i++) {
2679       final Component each = getComponent(i);
2680       if (each instanceof JComponent) {
2681         final JComponent jc = (JComponent)each;
2682         final Object done = jc.getClientProperty(LAYOUT_DONE);
2683         if (!Boolean.TRUE.equals(done)) {
2684           layout(jc, new Rectangle(0, 0, 0, 0));
2685         }
2686       }
2687     }
2688   }
2689
2690
2691   @NotNull
2692   public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) {
2693     myTabActionsMouseDeadzone = length;
2694     final List<TabInfo> all = getTabs();
2695     for (TabInfo each : all) {
2696       final TabLabel eachLabel = myInfo2Label.get(each);
2697       eachLabel.updateTabActions();
2698     }
2699     return this;
2700   }
2701
2702   @NotNull
2703   public JBTabsPresentation setTabsPosition(final JBTabsPosition position) {
2704     myPosition = position;
2705     relayout(true, false);
2706     return this;
2707   }
2708
2709   public JBTabsPosition getTabsPosition() {
2710     return myPosition;
2711   }
2712
2713   public TimedDeadzone.Length getTabActionsMouseDeadzone() {
2714     return myTabActionsMouseDeadzone;
2715   }
2716
2717   public JBTabsPresentation setTabDraggingEnabled(boolean enabled) {
2718     myTabDraggingEnabled = enabled;
2719     return this;
2720   }
2721
2722   public boolean isTabDraggingEnabled() {
2723     return myTabDraggingEnabled && isSingleRow();
2724   }
2725
2726   public JBTabsPresentation setProvideSwitchTargets(boolean provide) {
2727     myOwnSwitchProvider = provide;
2728     return this;
2729   }
2730
2731   void reallocate(TabInfo source, TabInfo target, boolean before) {
2732     if (source == target || source == null || target == null) return;
2733
2734     final int targetIndex = myVisibleInfos.indexOf(target);
2735     final int sourceIndex = myVisibleInfos.indexOf(source);
2736
2737     boolean needsValidation = false;
2738
2739     myVisibleInfos.remove(source);
2740     myVisibleInfos.add(targetIndex, source);
2741     needsValidation = true;
2742
2743     //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
2744     //}
2745
2746     if (needsValidation) {
2747       invalidate();
2748       relayout(true, true);
2749     }
2750   }
2751
2752   boolean isHorizontalTabs() {
2753     return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
2754   }
2755
2756   public void putInfo(Map<String, String> info) {
2757     final TabInfo selected = getSelectedInfo();
2758     if (selected != null) {
2759       selected.putInfo(info);
2760     }
2761   }
2762
2763   public boolean isUseBufferedPaint() {
2764     return myUseBufferedPaint;
2765   }
2766
2767   public void setUseBufferedPaint(boolean useBufferedPaint) {
2768     myUseBufferedPaint = useBufferedPaint;
2769     revalidate();
2770     repaint();
2771   }
2772
2773   public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
2774     ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
2775     for (TabInfo each : myVisibleInfos) {
2776       result.add(new TabTarget(each));
2777     }
2778
2779     if (originalProvider && mySwitchDelegate != null) {
2780       List<SwitchTarget> additional = mySwitchDelegate.getTargets(onlyVisible, false);
2781       if (additional != null) {
2782         result.addAll(additional);
2783       }
2784     }
2785
2786     return result;
2787   }
2788
2789
2790   public SwitchTarget getCurrentTarget() {
2791     if (mySwitchDelegate != null) {
2792       SwitchTarget selection = mySwitchDelegate.getCurrentTarget();
2793       if (selection != null) return selection;
2794     }
2795
2796     return new TabTarget(getSelectedInfo());
2797   }
2798
2799   private class TabTarget extends ComparableObject.Impl implements SwitchTarget {
2800
2801     private TabInfo myInfo;
2802
2803     private TabTarget(TabInfo info) {
2804       myInfo = info;
2805     }
2806
2807     public ActionCallback switchTo(boolean requestFocus) {
2808       return select(myInfo, requestFocus);
2809     }
2810
2811     public boolean isVisible() {
2812       return getRectangle() != null;
2813     }
2814
2815     public RelativeRectangle getRectangle() {
2816       TabLabel label = myInfo2Label.get(myInfo);
2817       if (label.getRootPane() == null) return null;
2818
2819       Rectangle b = label.getBounds();
2820       b.x += 2;
2821       b.width -= 4;
2822       b.y += 2;
2823       b.height -= 4;
2824       return new RelativeRectangle(label.getParent(), b);
2825     }
2826
2827     public Component getComponent() {
2828       return myInfo2Label.get(myInfo);
2829     }
2830
2831     @Override
2832     public String toString() {
2833       return myInfo.getText();
2834     }
2835
2836     @Override
2837     public Object[] getEqualityObjects() {
2838       return new Object[] {myInfo};
2839     }
2840   }
2841 }