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