6d49d1718d60c5beae3ce0f95042340d2dd3aa8a
[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 = UIUtil.getFocusedFillColor();
1618         shapeInfo.to = UIUtil.getFocusedFillColor();
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 ShapeInfo computeSelectedLabelShape() {
1678     final ShapeInfo shape = new ShapeInfo();
1679
1680     shape.path = getEffectiveLayout().createShapeTransform(getSize());
1681     shape.insets = shape.path.transformInsets(getLayoutInsets());
1682     shape.labelPath = shape.path.createTransform(getSelectedLabel().getBounds());
1683
1684     shape.labelBottomY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
1685     shape.labelTopY = shape.labelPath.getY();
1686     shape.labelLeftX = shape.labelPath.getX();
1687     shape.labelRightX = shape.labelPath.getX() + shape.labelPath.deltaX(shape.labelPath.getWidth());
1688
1689     Insets border = myBorder.getEffectiveBorder();
1690     TabInfo selected = getSelectedInfo();
1691     boolean first = myLastLayoutPass.getPreviousFor(selected) == null;
1692     boolean last = myLastLayoutPass.getNextFor(selected) == null;
1693
1694     boolean leftEdge = !isSingleRow() && first && border.left == 0;
1695     boolean rightEdge = !isSingleRow() && last && Boolean.TRUE.equals(myInfo2Label.get(selected).getClientProperty(STRETCHED_BY_WIDTH)) && border.right == 0;
1696
1697     boolean isDraggedNow = selected != null && myDragHelper != null && selected.equals(myDragHelper.getDragSource());
1698
1699     if (leftEdge && !isDraggedNow) {
1700       shape.path.moveTo(shape.insets.left, shape.labelTopY + shape.labelPath.deltaY(getEdgeArcSize()));
1701       shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getEdgeArcSize()), shape.labelTopY);
1702       shape.path.lineTo(shape.labelRightX - shape.labelPath.deltaX(getArcSize()), shape.labelTopY);
1703     } else {
1704       shape.path.moveTo(shape.insets.left, shape.labelBottomY);
1705       shape.path.lineTo(shape.labelLeftX, shape.labelBottomY);
1706       shape.path.lineTo(shape.labelLeftX, shape.labelTopY + shape.labelPath.deltaY(getArcSize()));
1707       shape.path.quadTo(shape.labelLeftX, shape.labelTopY, shape.labelLeftX + shape.labelPath.deltaX(getArcSize()), shape.labelTopY);
1708     }
1709
1710     int lastX = shape.path.getWidth() - shape.path.deltaX(shape.insets.right + 1);
1711
1712     if (isStealthModeEffective()) {
1713       shape.path.lineTo(lastX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1714       shape.path.quadTo(lastX, shape.labelTopY, lastX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1715       shape.path.lineTo(lastX, shape.labelBottomY);
1716     }
1717     else {
1718       if (rightEdge) {
1719         shape.path.lineTo(shape.labelRightX + 1 - shape.path.deltaX(getArcSize()), shape.labelTopY);
1720         shape.path.quadTo(shape.labelRightX + 1, shape.labelTopY, shape.labelRightX + 1, shape.labelTopY + shape.path.deltaY(getArcSize()));
1721       } else {
1722         shape.path.lineTo(shape.labelRightX - shape.path.deltaX(getArcSize()), shape.labelTopY);
1723         shape.path.quadTo(shape.labelRightX, shape.labelTopY, shape.labelRightX, shape.labelTopY + shape.path.deltaY(getArcSize()));
1724       }
1725       if (myLastLayoutPass.hasCurveSpaceFor(selected)) {
1726         shape.path.lineTo(shape.labelRightX, shape.labelBottomY - shape.path.deltaY(getArcSize()));
1727         shape.path.quadTo(shape.labelRightX, shape.labelBottomY, shape.labelRightX + shape.path.deltaX(getArcSize()), shape.labelBottomY);
1728       }
1729       else {
1730         if (rightEdge) {
1731           shape.path.lineTo(shape.labelRightX + 1, shape.labelBottomY);
1732         } else {
1733           shape.path.lineTo(shape.labelRightX, shape.labelBottomY);
1734         }
1735       }
1736     }
1737
1738     if (!rightEdge) {
1739       shape.path.lineTo(lastX, shape.labelBottomY);
1740     }
1741
1742     if (isStealthModeEffective()) {
1743       shape.path.closePath();
1744     }
1745
1746     shape.fillPath = shape.path.copy();
1747     if (!isHideTabs()) {
1748       shape.fillPath.lineTo(lastX, shape.labelBottomY + shape.fillPath.deltaY(1));
1749       shape.fillPath.lineTo(shape.labelLeftX, shape.labelBottomY + shape.fillPath.deltaY(1));
1750       shape.fillPath.closePath();
1751     }
1752     return shape;
1753   }
1754
1755   protected TabLabel getSelectedLabel() {
1756     return myInfo2Label.get(getSelectedInfo());
1757   }
1758
1759   protected static class ShapeInfo {
1760     public ShapeInfo() {}
1761     public ShapeTransform path;
1762     public ShapeTransform fillPath;
1763     public ShapeTransform labelPath;
1764     public int labelBottomY;
1765     public int labelTopY;
1766     public int labelLeftX;
1767     public int labelRightX;
1768     public Insets insets;
1769     public Color from;
1770     public Color to;
1771   }
1772
1773
1774   protected void paintFirstGhost(Graphics2D g2d) {
1775     final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.firstGhost);
1776
1777     int topX = path.getX() + path.deltaX(getCurveArc());
1778     int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1779     int bottomX = path.getMaxX() + path.deltaX(1);
1780     int bottomY = path.getMaxY() + path.deltaY(1);
1781
1782     path.moveTo(topX, topY);
1783
1784     final boolean isLeftFromSelection = mySingleRowLayout.myLastSingRowLayout.toLayout.indexOf(getSelectedInfo()) == 0;
1785
1786     if (isLeftFromSelection) {
1787       path.lineTo(bottomX, topY);
1788     }
1789     else {
1790       path.lineTo(bottomX - getArcSize(), topY);
1791       path.quadTo(bottomX, topY, bottomX, topY + path.deltaY(getArcSize()));
1792     }
1793
1794     path.lineTo(bottomX, bottomY);
1795     path.lineTo(topX, bottomY);
1796
1797     path.quadTo(topX - path.deltaX(getCurveArc() * 2 - 1), bottomY - path.deltaY(Math.abs(bottomY - topY) / 4), topX,
1798                 bottomY - path.deltaY(Math.abs(bottomY - topY) / 2));
1799
1800     path.quadTo(topX + path.deltaX(getCurveArc() - 1), topY + path.deltaY(Math.abs(bottomY - topY) / 4), topX, topY);
1801
1802     path.closePath();
1803
1804     g2d.setColor(getBackground());
1805     g2d.fill(path.getShape());
1806
1807     g2d.setColor(getBoundsColor());
1808     g2d.draw(path.getShape());
1809
1810     g2d.setColor(getTopBlockColor());
1811     g2d.drawLine(topX + path.deltaX(1), topY + path.deltaY(1), bottomX - path.deltaX(getArcSize()), topY + path.deltaY(1));
1812
1813     g2d.setColor(getRightBlockColor());
1814     g2d.drawLine(bottomX - path.deltaX(1), topY + path.deltaY(getArcSize()), bottomX - path.deltaX(1), bottomY - path.deltaY(1));
1815   }
1816
1817   protected SingleRowLayout getSingleRowLayout() {
1818     return mySingleRowLayout;
1819   }
1820   
1821   protected void paintLastGhost(Graphics2D g2d) {
1822     final ShapeTransform path = getEffectiveLayout().createShapeTransform(mySingleRowLayout.myLastSingRowLayout.lastGhost);
1823
1824     int topX = path.getX() - path.deltaX(getArcSize());
1825     int topY = path.getY() + path.deltaY(getSelectionTabVShift());
1826     int bottomX = path.getMaxX() - path.deltaX(getCurveArc());
1827     int bottomY = path.getMaxY() + path.deltaY(1);
1828
1829     path.moveTo(topX, topY);
1830     path.lineTo(bottomX, topY);
1831     path.quadTo(bottomX - getCurveArc(), topY + (bottomY - topY) / 4, bottomX, topY + (bottomY - topY) / 2);
1832     path.quadTo(bottomX + getCurveArc(), bottomY - (bottomY - topY) / 4, bottomX, bottomY);
1833     path.lineTo(topX, bottomY);
1834
1835     path.closePath();
1836
1837     g2d.setColor(getBackground());
1838     g2d.fill(path.getShape());
1839
1840     g2d.setColor(getBoundsColor());
1841     g2d.draw(path.getShape());
1842
1843     g2d.setColor(getTopBlockColor());
1844     g2d.drawLine(topX, topY + path.deltaY(1), bottomX - path.deltaX(getCurveArc()), topY + path.deltaY(1));
1845   }
1846
1847   protected int getCurveArc() {
1848     return 2;
1849   }
1850
1851   protected Color getBoundsColor() {
1852     return Color.gray;
1853   }
1854
1855   protected Color getRightBlockColor() {
1856     return Color.lightGray;
1857   }
1858
1859   protected Color getTopBlockColor() {
1860     return Color.white;
1861   }
1862   
1863   protected boolean shouldPaintFocus() {
1864     return myPaintFocus;
1865   }
1866
1867   private void paintNonSelectedTabs(final Graphics2D g2d, final boolean leftGhostExists, final boolean rightGhostExists) {
1868     TabInfo selected = getSelectedInfo();
1869     if (myLastPaintedSelection == null || !myLastPaintedSelection.equals(selected)) {
1870       List<TabInfo> tabs = getTabs();
1871       for (TabInfo each : tabs) {
1872         myInfo2Label.get(each).setInactiveStateImage(null);
1873       }
1874     }
1875
1876     for (int eachRow = 0; eachRow < myLastLayoutPass.getRowCount(); eachRow++) {
1877       for (int eachColumn = myLastLayoutPass.getColumnCount(eachRow) - 1; eachColumn >= 0; eachColumn--) {
1878         final TabInfo each = myLastLayoutPass.getTabAt(eachRow, eachColumn);
1879         if (getSelectedInfo() == each) {
1880           continue;
1881         }
1882         paintNonSelected(g2d, each, leftGhostExists, rightGhostExists);
1883       }
1884     }
1885
1886     myLastPaintedSelection = selected;
1887   }
1888
1889   private void paintNonSelected(final Graphics2D g2d, final TabInfo each, final boolean leftGhostExists, final boolean rightGhostExists) {
1890     if (myDropInfo == each) return;
1891
1892     final TabLabel label = myInfo2Label.get(each);
1893     if (label.getBounds().width == 0) return;
1894
1895     int imageInsets = getArcSize() + 1;
1896
1897     Rectangle bounds = label.getBounds();
1898
1899     int x = bounds.x - imageInsets;
1900     int y = bounds.y;
1901     int width = bounds.width + imageInsets * 2 + 1;
1902     int height = bounds.height + getArcSize() + 1;
1903
1904     if (isToBufferPainting()) {
1905       BufferedImage img = label.getInactiveStateImage(bounds);
1906
1907       if (img == null) {
1908         img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
1909         Graphics2D imgG2d = img.createGraphics();
1910         imgG2d.addRenderingHints(g2d.getRenderingHints());
1911         doPaintInactive(imgG2d, leftGhostExists, label, new Rectangle(imageInsets, 0, label.getWidth(), label.getHeight()),
1912                         rightGhostExists);
1913         imgG2d.dispose();
1914       }
1915
1916       g2d.drawImage(img, x, y, width, height, null);
1917
1918       label.setInactiveStateImage(img);
1919     } else {
1920       doPaintInactive(g2d, leftGhostExists, label, label.getBounds(), rightGhostExists);
1921       label.setInactiveStateImage(null);
1922     }
1923   }
1924
1925   private boolean isToBufferPainting() {
1926     return Registry.is("ide.tabbedPane.bufferedPaint") && myUseBufferedPaint;
1927   }
1928
1929   protected List<TabInfo> getVisibleInfos() {
1930     return myVisibleInfos;
1931   }
1932   
1933   protected LayoutPassInfo getLastLayoutPass() {
1934     return myLastLayoutPass;
1935   }
1936   
1937   protected TabLabel getInfoForLabel(TabInfo info) {
1938     return myInfo2Label.get(info);
1939   }
1940   
1941   protected TableLayout getTableLayout() {
1942     return myTableLayout;
1943   }
1944
1945   protected DragHelper getDragHelper() {
1946     return myDragHelper;
1947   }
1948
1949   @Override
1950   public Color getBackground() {
1951     return UIUtil.isUnderNimbusLookAndFeel() ? UIUtil.getPanelBackground() : super.getBackground();
1952   }
1953
1954   protected void doPaintInactive(Graphics2D g2d,
1955                                  boolean leftGhostExists,
1956                                  TabLabel label,
1957                                  Rectangle effectiveBounds,
1958                                  boolean rightGhostExists) {
1959     int tabIndex = myVisibleInfos.indexOf(label.getInfo());
1960
1961     final int arc = getArcSize();
1962     Color topBlockColor = getTopBlockColor();
1963     Color rightBlockColor = getRightBlockColor();
1964     Color boundsColor = getBoundsColor();
1965     Color backgroundColor = getBackground();
1966
1967     final Color tabColor = label.getInfo().getTabColor();
1968     if (tabColor != null) {
1969       backgroundColor = tabColor;
1970       boundsColor = tabColor.darker();
1971       topBlockColor = tabColor.brighter().brighter();
1972       rightBlockColor = tabColor;
1973     }
1974
1975     final TabInfo selected = getSelectedInfo();
1976     final int selectionTabVShift = getSelectionTabVShift();
1977
1978
1979     final TabInfo prev = myLastLayoutPass.getPreviousFor(myVisibleInfos.get(tabIndex));
1980     final TabInfo next = myLastLayoutPass.getNextFor(myVisibleInfos.get(tabIndex));
1981
1982
1983     boolean firstShowing = prev == null;
1984     if (!firstShowing && !leftGhostExists) {
1985       firstShowing = myInfo2Label.get(prev).getBounds().width == 0;
1986     }
1987
1988     boolean lastShowing = next == null;
1989     if (!lastShowing) {
1990       lastShowing = myInfo2Label.get(next).getBounds().width == 0;
1991     }
1992
1993     boolean leftFromSelection = selected != null && tabIndex == myVisibleInfos.indexOf(selected) - 1;
1994
1995     Rectangle originalBounds = effectiveBounds;
1996     final ShapeTransform shape = getEffectiveLayout().createShapeTransform(originalBounds);
1997
1998     int leftX = firstShowing ? shape.getX() : shape.getX() - shape.deltaX(arc + 1);
1999     int topY = shape.getY() + shape.deltaY(selectionTabVShift);
2000     int rightX = !lastShowing && leftFromSelection ? shape.getMaxX() + shape.deltaX(arc + 1) : shape.getMaxX();
2001     int bottomY = shape.getMaxY() + shape.deltaY(1);
2002
2003     Insets border = myBorder.getEffectiveBorder();
2004
2005     if (border.left > 0 || leftGhostExists || !firstShowing) {
2006       shape.moveTo(leftX, bottomY);
2007       shape.lineTo(leftX, topY + shape.deltaY(arc));
2008       shape.quadTo(leftX, topY, leftX + shape.deltaX(arc), topY);
2009     } else {
2010       if (firstShowing) {
2011         shape.moveTo(leftX, topY + shape.deltaY(getEdgeArcSize()));
2012         shape.quadTo(leftX, topY, leftX + shape.deltaX(getEdgeArcSize()), topY);
2013       }
2014     }
2015
2016     boolean rightEdge = false;
2017     if (border.right > 0 || rightGhostExists || !lastShowing || !Boolean.TRUE.equals(label.getClientProperty(STRETCHED_BY_WIDTH))) {
2018       shape.lineTo(rightX - shape.deltaX(arc), topY);
2019       shape.quadTo(rightX, topY, rightX, topY + shape.deltaY(arc));
2020       shape.lineTo(rightX, bottomY);
2021     } else {
2022       if (lastShowing) {
2023         shape.lineTo(rightX - shape.deltaX(arc), topY);
2024         shape.quadTo(rightX + 1, topY, rightX + 1, topY + shape.deltaY(arc));
2025
2026         shape.lineTo(rightX + 1, bottomY);
2027         rightEdge = true;
2028       }
2029     }
2030
2031     if (!isSingleRow()) {
2032       final TablePassInfo info = myTableLayout.myLastTableLayout;
2033       if (!info.isInSelectionRow(label.getInfo())) {
2034         shape.lineTo(rightX, bottomY + shape.deltaY(getArcSize()));
2035         shape.lineTo(leftX, bottomY + shape.deltaY(getArcSize()));
2036         shape.lineTo(leftX, bottomY);
2037       }
2038     }
2039
2040     if (!rightEdge) {
2041       shape.lineTo(leftX, bottomY);
2042     }
2043
2044     g2d.setColor(backgroundColor);
2045     g2d.fill(shape.getShape());
2046
2047     // TODO
2048
2049     final Line2D.Float gradientLine =
2050       shape.transformLine(0, topY, 0, topY + shape.deltaY((int) (shape.getHeight() / 1.5 )));
2051
2052     final GradientPaint gp =
2053       new GradientPaint(gradientLine.x1, gradientLine.y1,
2054                         shape.transformY1(backgroundColor.brighter().brighter(), backgroundColor),
2055                         gradientLine.x2, gradientLine.y2,
2056                         shape.transformY1(backgroundColor, backgroundColor.brighter().brighter()));
2057     final Paint old = g2d.getPaint();
2058     g2d.setPaint(gp);
2059     g2d.fill(shape.getShape());
2060     g2d.setPaint(old);
2061
2062     g2d.setColor(topBlockColor);
2063     g2d.draw(
2064       shape.transformLine(leftX + shape.deltaX(arc + 1), topY + shape.deltaY(1), rightX - shape.deltaX(arc - 1), topY + shape.deltaY(1)));
2065
2066     if (!rightEdge) {
2067       g2d.setColor(rightBlockColor);
2068       g2d.draw(shape.transformLine(rightX - shape.deltaX(1), topY + shape.deltaY(arc - 1), rightX - shape.deltaX(1), bottomY));
2069     }
2070
2071     g2d.setColor(boundsColor);
2072     g2d.draw(shape.getShape());
2073   }
2074
2075   public int getSelectionTabVShift() {
2076     return 2;
2077   }
2078
2079   protected void paintBorder(Graphics2D g2d, ShapeInfo shape, final Color borderColor) {
2080     final ShapeTransform shaper = shape.path.copy().reset();
2081
2082     final Insets paintBorder = shape.path.transformInsets(myBorder.getEffectiveBorder());
2083
2084     int topY = shape.labelPath.getMaxY() + shape.labelPath.deltaY(1);
2085
2086     int bottomY = topY + paintBorder.top - 2;
2087     int middleY = topY + (bottomY - topY) / 2;
2088
2089
2090     final int boundsX = shape.path.getX() + shape.path.deltaX(shape.insets.left);
2091
2092     final int boundsY =
2093       isHideTabs() ? shape.path.getY() + shape.path.deltaY(shape.insets.top) : shape.labelPath.getMaxY() + shape.path.deltaY(1);
2094
2095     final int boundsHeight = Math.abs(shape.path.getMaxY() - boundsY) - shape.insets.bottom - paintBorder.bottom;
2096     final int boundsWidth = Math.abs(shape.path.getMaxX() - (shape.insets.left + shape.insets.right));
2097
2098     if (paintBorder.top > 0) {
2099       if (isHideTabs()) {
2100         if (isToDrawBorderIfTabsHidden()) {
2101           g2d.setColor(borderColor);
2102           g2d.fill(shaper.reset().doRect(boundsX, boundsY, boundsWidth, 1).getShape());
2103         }
2104       }
2105       else {
2106         Color tabFillColor = getActiveTabColor(null);
2107         if (tabFillColor == null) {
2108           tabFillColor = shape.path.transformY1(shape.to, shape.from);
2109         }
2110
2111         g2d.setColor(tabFillColor);
2112         g2d.fill(shaper.reset().doRect(boundsX, topY + shape.path.deltaY(1), boundsWidth, paintBorder.top - 1).getShape());
2113
2114         g2d.setColor(borderColor);
2115         if (paintBorder.top == 2) {
2116           final Line2D.Float line = shape.path.transformLine(boundsX, topY, boundsX + shape.path.deltaX(boundsWidth - 1), topY);
2117
2118           g2d.drawLine((int)line.x1, (int)line.y1, (int)line.x2, (int)line.y2);
2119         }
2120         else if (paintBorder.top > 2) {
2121 //todo kirillk
2122 //start hack
2123           int deltaY = 0;
2124           if (myPosition == JBTabsPosition.bottom || myPosition == JBTabsPosition.right) {
2125             deltaY = 1;
2126           }
2127 //end hack
2128           final int topLine = topY + shape.path.deltaY(paintBorder.top - 1);
2129           g2d.fill(shaper.reset().doRect(boundsX, topLine + deltaY, boundsWidth - 1, 1).getShape());
2130         }
2131       }
2132     }
2133
2134     g2d.setColor(borderColor);
2135
2136     //bottom
2137     g2d.fill(shaper.reset().doRect(boundsX, Math.abs(shape.path.getMaxY() - shape.insets.bottom - paintBorder.bottom), boundsWidth,
2138                                    paintBorder.bottom).getShape());
2139
2140     //left
2141     g2d.fill(shaper.reset().doRect(boundsX, boundsY, paintBorder.left, boundsHeight).getShape());
2142
2143     //right
2144     g2d.fill(shaper.reset()
2145       .doRect(shape.path.getMaxX() - shape.insets.right - paintBorder.right, boundsY, paintBorder.right, boundsHeight).getShape());
2146
2147   }
2148
2149   public boolean isStealthModeEffective() {
2150     return myStealthTabMode && getTabCount() == 1 && isSideComponentVertical() && getTabsPosition() == JBTabsPosition.top;
2151   }
2152
2153
2154   private boolean isNavigationVisible() {
2155     if (myStealthTabMode && getTabCount() == 1) return false;
2156     return !myVisibleInfos.isEmpty();
2157   }
2158
2159
2160   public void paint(final Graphics g) {
2161     Rectangle clip = g.getClipBounds();
2162     if (clip == null) {
2163       return;
2164     }
2165
2166     if (myPaintBlocked) {
2167       if (myImage != null) {
2168         g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
2169       }
2170       return;
2171     }
2172
2173     super.paint(g);
2174   }
2175
2176   protected void paintChildren(final Graphics g) {
2177     super.paintChildren(g);
2178
2179     final GraphicsConfig config = new GraphicsConfig(g);
2180     config.setAntialiasing(true);
2181     paintSelectionAndBorder((Graphics2D)g);
2182     config.restore();
2183
2184     final TabLabel selected = getSelectedLabel();
2185     if (selected != null) {
2186       selected.paintImage(g);
2187     }
2188
2189     mySingleRowLayout.myMoreIcon.paintIcon(this, g);
2190   }
2191
2192   private Max computeMaxSize() {
2193     Max max = new Max();
2194     for (TabInfo eachInfo : myVisibleInfos) {
2195       final TabLabel label = myInfo2Label.get(eachInfo);
2196       max.myLabel.height = Math.max(max.myLabel.height, label.getPreferredSize().height);
2197       max.myLabel.width = Math.max(max.myLabel.width, label.getPreferredSize().width);
2198       final Toolbar toolbar = myInfo2Toolbar.get(eachInfo);
2199       if (myLayout.isSideComponentOnTabs() && toolbar != null && !toolbar.isEmpty()) {
2200         max.myToolbar.height = Math.max(max.myToolbar.height, toolbar.getPreferredSize().height);
2201         max.myToolbar.width = Math.max(max.myToolbar.width, toolbar.getPreferredSize().width);
2202       }
2203     }
2204
2205     max.myToolbar.height++;
2206
2207     return max;
2208   }
2209
2210   public Dimension getMinimumSize() {
2211     return computeSize(new Transform<JComponent, Dimension>() {
2212       public Dimension transform(JComponent component) {
2213         return component.getMinimumSize();
2214       }
2215     }, 1);
2216   }
2217
2218   public Dimension getPreferredSize() {
2219     return computeSize(new Transform<JComponent, Dimension>() {
2220       public Dimension transform(JComponent component) {
2221         return component.getPreferredSize();
2222       }
2223     }, 3);
2224   }
2225
2226   private Dimension computeSize(Transform<JComponent, Dimension> transform, int tabCount) {
2227     Dimension size = new Dimension();
2228     for (TabInfo each : myVisibleInfos) {
2229       final JComponent c = each.getComponent();
2230       if (c != null) {
2231         final Dimension eachSize = transform.transform(c);
2232         size.width = Math.max(eachSize.width, size.width);
2233         size.height = Math.max(eachSize.height, size.height);
2234       }
2235     }
2236
2237     addHeaderSize(size, tabCount);
2238     return size;
2239   }
2240
2241   private void addHeaderSize(Dimension size, final int tabsCount) {
2242     Dimension header = computeHeaderPreferredSize(tabsCount);
2243
2244     final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
2245     if (horizontal) {
2246       size.height += header.height;
2247       size.width = Math.max(size.width, header.width);
2248     }
2249     else {
2250       size.height += Math.max(size.height, header.height);
2251       size.width += header.width;
2252     }
2253
2254     final Insets insets = getLayoutInsets();
2255     size.width += insets.left + insets.right + 1;
2256     size.height += insets.top + insets.bottom + 1;
2257   }
2258
2259   private Dimension computeHeaderPreferredSize(int tabsCount) {
2260     final Iterator<TabInfo> infos = myInfo2Label.keySet().iterator();
2261     Dimension size = new Dimension();
2262     int currentTab = 0;
2263
2264     final boolean horizontal = getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
2265
2266     while (infos.hasNext()) {
2267       final boolean canGrow = currentTab < tabsCount;
2268
2269       TabInfo eachInfo = infos.next();
2270       final TabLabel eachLabel = myInfo2Label.get(eachInfo);
2271       final Dimension eachPrefSize = eachLabel.getPreferredSize();
2272       if (horizontal) {
2273         if (canGrow) {
2274           size.width += eachPrefSize.width;
2275         }
2276         size.height = Math.max(size.height, eachPrefSize.height);
2277       }
2278       else {
2279         size.width = Math.max(size.width, eachPrefSize.width);
2280         if (canGrow) {
2281           size.height += eachPrefSize.height;
2282         }
2283       }
2284
2285       currentTab++;
2286     }
2287
2288     if (isSingleRow() && isGhostsAlwaysVisible()) {
2289       if (horizontal) {
2290         size.width += getGhostTabLength() * 2;
2291       }
2292       else {
2293         size.height += getGhostTabLength() * 2;
2294       }
2295     }
2296
2297     if (horizontal) {
2298       size.height += myBorder.getTabBorderSize();
2299     }
2300     else {
2301       size.width += myBorder.getTabBorderSize();
2302     }
2303
2304     return size;
2305   }
2306
2307   public int getTabCount() {
2308     return getTabs().size();
2309   }
2310
2311   @NotNull
2312   public JBTabsPresentation getPresentation() {
2313     return this;
2314   }
2315
2316   public ActionCallback removeTab(final JComponent component) {
2317     return removeTab(findInfo(component));
2318   }
2319
2320   public ActionCallback removeTab(final TabInfo info) {
2321     return removeTab(info, null);
2322   }
2323
2324   public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer) {
2325     return removeTab(info, forcedSelectionTranfer, true);
2326   }
2327
2328   public ActionCallback removeTab(final TabInfo info, @Nullable TabInfo forcedSelectionTranfer, boolean transferFocus) {
2329     return removeTab(info, forcedSelectionTranfer, transferFocus, false);
2330   }
2331
2332   private ActionCallback removeTab(TabInfo info, TabInfo forcedSelectionTranfer, boolean transferFocus, boolean isDropTarget) {
2333     if (!isDropTarget) {
2334       if (info == null || !getTabs().contains(info)) return new ActionCallback.Done();
2335     }
2336
2337     if (isDropTarget && myLastLayoutPass != null) {
2338       myLastLayoutPass.myVisibleInfos.remove(info);
2339     }
2340
2341     final ActionCallback result = new ActionCallback();
2342
2343     TabInfo toSelect;
2344     if (forcedSelectionTranfer == null) {
2345       toSelect = getToSelectOnRemoveOf(info);
2346     }
2347     else {
2348       assert myVisibleInfos.contains(forcedSelectionTranfer) : "Cannot find tab for selection transfer, tab=" + forcedSelectionTranfer;
2349       toSelect = forcedSelectionTranfer;
2350     }
2351
2352
2353     if (toSelect != null) {
2354       processRemove(info, false);
2355       _setSelected(toSelect, transferFocus).doWhenProcessed(new Runnable() {
2356         public void run() {
2357           removeDeferred().notifyWhenDone(result);
2358         }
2359       });
2360     }
2361     else {
2362       processRemove(info, true);
2363       removeDeferred().notifyWhenDone(result);
2364     }
2365
2366     if (myVisibleInfos.isEmpty()) {
2367       removeDeferredNow();
2368     }
2369
2370     revalidateAndRepaint(true);
2371
2372     return result;
2373   }
2374
2375   private void processRemove(final TabInfo info, boolean forcedNow) {
2376     remove(myInfo2Label.get(info));
2377     remove(myInfo2Toolbar.get(info));
2378
2379     JComponent tabComponent = info.getComponent();
2380
2381     if (!isToDeferRemoveForLater(tabComponent) || forcedNow) {
2382       remove(tabComponent);
2383     }
2384     else {
2385       queueForRemove(tabComponent);
2386     }
2387
2388     myVisibleInfos.remove(info);
2389     myHiddenInfos.remove(info);
2390     myInfo2Label.remove(info);
2391     myInfo2Toolbar.remove(info);
2392     resetTabsCache();
2393
2394     updateAll(false, false);
2395
2396     // avoid leaks
2397     myLastPaintedSelection = null;
2398   }
2399
2400   public TabInfo findInfo(Component component) {
2401     for (TabInfo each : getTabs()) {
2402       if (each.getComponent() == component) return each;
2403     }
2404
2405     return null;
2406   }
2407
2408   public TabInfo findInfo(String text) {
2409     if (text == null) return null;
2410
2411     for (TabInfo each : getTabs()) {
2412       if (text.equals(each.getText())) return each;
2413     }
2414
2415     return null;
2416   }
2417
2418   public TabInfo findInfo(MouseEvent event) {
2419     return findInfo(event, false);
2420   }
2421
2422   private TabInfo findInfo(final MouseEvent event, final boolean labelsOnly) {
2423     final Point point = SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), this);
2424     return _findInfo(point, labelsOnly);
2425   }
2426
2427   public TabInfo findInfo(final Object object) {
2428     for (int i = 0; i < getTabCount(); i++) {
2429       final TabInfo each = getTabAt(i);
2430       final Object eachObject = each.getObject();
2431       if (eachObject != null && eachObject.equals(object)) return each;
2432     }
2433     return null;
2434   }
2435
2436   public TabInfo findTabLabelBy(final Point point) {
2437     return _findInfo(point, true);
2438   }
2439
2440   private TabInfo _findInfo(final Point point, boolean labelsOnly) {
2441     Component component = findComponentAt(point);
2442     if (component == null) return null;
2443     while (component != this || component != null) {
2444       if (component instanceof TabLabel) {
2445         return ((TabLabel)component).getInfo();
2446       }
2447       if (!labelsOnly) {
2448         final TabInfo info = findInfo(component);
2449         if (info != null) return info;
2450       }
2451       if (component == null) break;
2452       component = component.getParent();
2453     }
2454
2455     return null;
2456   }
2457
2458   public void removeAllTabs() {
2459     for (TabInfo each : getTabs()) {
2460       removeTab(each);
2461     }
2462   }
2463
2464
2465   private static class Max {
2466     Dimension myLabel = new Dimension();
2467     Dimension myToolbar = new Dimension();
2468   }
2469
2470   private void updateContainer(boolean forced, final boolean layoutNow) {
2471     final TabLabel selectedLabel = getSelectedLabel();
2472
2473     for (TabInfo each : myVisibleInfos) {
2474       final JComponent eachComponent = each.getComponent();
2475       if (getSelectedInfo() == each && getSelectedInfo() != null) {
2476         unqueueFromRemove(eachComponent);
2477
2478         final Container parent = eachComponent.getParent();
2479         if (parent != null && parent != this) {
2480           parent.remove(eachComponent);
2481         }
2482
2483         if (eachComponent.getParent() == null) {
2484           add(eachComponent);
2485         }
2486       }
2487       else {
2488         if (eachComponent.getParent() == null) continue;
2489         if (isToDeferRemoveForLater(eachComponent)) {
2490           queueForRemove(eachComponent);
2491         }
2492         else {
2493           remove(eachComponent);
2494         }
2495       }
2496     }
2497
2498     relayout(forced, layoutNow);
2499   }
2500
2501   protected void addImpl(final Component comp, final Object constraints, final int index) {
2502     unqueueFromRemove(comp);
2503
2504     if (comp instanceof TabLabel) {
2505       ((TabLabel)comp).apply(myUiDecorator.getDecoration());
2506     }
2507
2508     super.addImpl(comp, constraints, index);
2509   }
2510
2511
2512   private static boolean isToDeferRemoveForLater(JComponent c) {
2513     return c.getRootPane() != null;
2514   }
2515
2516   void relayout(boolean forced, final boolean layoutNow) {
2517     if (!myForcedRelayout) {
2518       myForcedRelayout = forced;
2519     }
2520     revalidateAndRepaint(layoutNow);
2521   }
2522
2523   public TabsBorder getTabsBorder() {
2524     return myBorder;
2525   }
2526
2527   @NotNull
2528   public JBTabs addTabMouseMotionListener(@NotNull MouseMotionListener listener) {
2529     removeListeners();
2530     myTabMouseListeners.add(listener);
2531     addListeners();
2532     return this;
2533   }
2534
2535   @NotNull
2536   public JBTabs addTabMouseListener(@NotNull MouseListener listener) {
2537     removeListeners();
2538     myTabMouseListeners.add(listener);
2539     addListeners();
2540     return this;
2541   }
2542
2543   @NotNull
2544   public JComponent getComponent() {
2545     return this;
2546   }
2547
2548   public boolean isCycleRoot() {
2549     return false;
2550   }
2551
2552   @NotNull
2553   public JBTabs removeTabMouseListener(@NotNull MouseListener listener) {
2554     removeListeners();
2555     myTabMouseListeners.remove(listener);
2556     addListeners();
2557     return this;
2558   }
2559
2560   private void addListeners() {
2561     for (TabInfo eachInfo : myVisibleInfos) {
2562       final TabLabel label = myInfo2Label.get(eachInfo);
2563       for (EventListener eachListener : myTabMouseListeners) {
2564         if (eachListener instanceof MouseListener) {
2565           label.addMouseListener((MouseListener)eachListener);
2566         }
2567         else if (eachListener instanceof MouseMotionListener) {
2568           label.addMouseMotionListener((MouseMotionListener)eachListener);
2569         }
2570         else {
2571           assert false;
2572         }
2573       }
2574     }
2575   }
2576
2577   private void removeListeners() {
2578     for (TabInfo eachInfo : myVisibleInfos) {
2579       final TabLabel label = myInfo2Label.get(eachInfo);
2580       for (EventListener eachListener : myTabMouseListeners) {
2581         if (eachListener instanceof MouseListener) {
2582           label.removeMouseListener((MouseListener)eachListener);
2583         }
2584         else if (eachListener instanceof MouseMotionListener) {
2585           label.removeMouseMotionListener((MouseMotionListener)eachListener);
2586         }
2587         else {
2588           assert false;
2589         }
2590       }
2591     }
2592   }
2593
2594   private void updateListeners() {
2595     removeListeners();
2596     addListeners();
2597   }
2598
2599   public JBTabs addListener(@NotNull TabsListener listener) {
2600     myTabListeners.add(listener);
2601     return this;
2602   }
2603
2604   public JBTabs removeListener(@NotNull final TabsListener listener) {
2605     myTabListeners.remove(listener);
2606     return this;
2607   }
2608
2609   @Override
2610   public JBTabs setSelectionChangeHandler(SelectionChangeHandler handler) {
2611     mySelectionChangeHandler = handler;
2612     return this;
2613   }
2614
2615   protected void onPopup(final TabInfo popupInfo) {
2616   }
2617
2618   public void setFocused(final boolean focused) {
2619     if (myFocused == focused) return;
2620
2621     myFocused = focused;
2622
2623     if (myPaintFocus) {
2624       repaint();
2625     }
2626   }
2627
2628   public int getIndexOf(@Nullable final TabInfo tabInfo) {
2629     return myVisibleInfos.indexOf(tabInfo);
2630   }
2631
2632   public boolean isHideTabs() {
2633     return myHideTabs;
2634   }
2635
2636   public void setHideTabs(final boolean hideTabs) {
2637     if (isHideTabs() == hideTabs) return;
2638
2639     myHideTabs = hideTabs;
2640
2641     relayout(true, false);
2642   }
2643
2644   public JBTabsPresentation setPaintBorder(int top, int left, int right, int bottom) {
2645     return myBorder.setPaintBorder(top, left, right, bottom);
2646   }
2647
2648   public JBTabsPresentation setTabSidePaintBorder(int size) {
2649     return myBorder.setTabSidePaintBorder(size);
2650   }
2651
2652   static int getBorder(int size) {
2653     return size == -1 ? 1 : size;
2654   }
2655
2656   public boolean isPaintFocus() {
2657     return myPaintFocus;
2658   }
2659
2660   @NotNull
2661   public JBTabsPresentation setAdjustBorders(final boolean adjust) {
2662     myAdjustBorders = adjust;
2663     return this;
2664   }
2665
2666   @NotNull
2667   public JBTabsPresentation setActiveTabFillIn(@Nullable final Color color) {
2668     if (!isChanged(myActiveTabFillIn, color)) return this;
2669
2670     myActiveTabFillIn = color;
2671     revalidateAndRepaint(false);
2672     return this;
2673   }
2674
2675   private static boolean isChanged(Object oldObject, Object newObject) {
2676     if (oldObject == null && newObject == null) return false;
2677     return oldObject != null && !oldObject.equals(newObject) || newObject != null && !newObject.equals(oldObject);
2678   }
2679
2680   @NotNull
2681   public JBTabsPresentation setTabLabelActionsAutoHide(final boolean autoHide) {
2682     if (myTabLabelActionsAutoHide != autoHide) {
2683       myTabLabelActionsAutoHide = autoHide;
2684       revalidateAndRepaint(false);
2685     }
2686     return this;
2687   }
2688
2689   @Nullable
2690   public Color getActiveTabFillIn() {
2691     return myActiveTabFillIn;
2692   }
2693
2694   public JBTabsPresentation setFocusCycle(final boolean root) {
2695     setFocusCycleRoot(root);
2696     return this;
2697   }
2698
2699
2700   public JBTabsPresentation setPaintFocus(final boolean paintFocus) {
2701     myPaintFocus = paintFocus;
2702     return this;
2703   }
2704
2705   private abstract static class BaseNavigationAction extends AnAction {
2706
2707     private final ShadowAction myShadow;
2708     private final ActionManager myActionManager;
2709     private final JBTabsImpl myTabs;
2710
2711     protected BaseNavigationAction(final String copyFromID, JBTabsImpl tabs, ActionManager mgr) {
2712       myActionManager = mgr;
2713       myTabs = tabs;
2714       myShadow = new ShadowAction(this, myActionManager.getAction(copyFromID), tabs);
2715       Disposer.register(tabs, myShadow);
2716       setEnabledInModalContext(true);
2717     }
2718
2719     public final void update(final AnActionEvent e) {
2720       JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2721       e.getPresentation().setVisible(tabs != null);
2722       if (tabs == null) return;
2723
2724       tabs = findNavigatableTabs(tabs);
2725       e.getPresentation().setEnabled(tabs != null);
2726       if (tabs != null) {
2727         _update(e, tabs, tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo()));
2728       }
2729     }
2730     
2731     @Nullable
2732     protected JBTabsImpl findNavigatableTabs(JBTabsImpl tabs) {
2733       // The debugger UI contains multiple nested JBTabsImpl, where the innermost JBTabsImpl has only one tab. In this case,
2734       // the action should target the outer JBTabsImpl.
2735       if (tabs == null || tabs != myTabs) {
2736         return null;
2737       }
2738       if (isNavigatable(tabs)) {
2739         return tabs;
2740       }
2741       Component c = tabs.getParent();
2742       while (c != null) {
2743         if (c instanceof JBTabsImpl && isNavigatable((JBTabsImpl) c)) {
2744           return (JBTabsImpl)c;
2745         }
2746         c = c.getParent();
2747       }
2748       return null;
2749     }
2750
2751     private static boolean isNavigatable(JBTabsImpl tabs) {
2752       final int selectedIndex = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2753       return tabs.isNavigationVisible() && selectedIndex >= 0 && tabs.myNavigationActionsEnabled;
2754     }
2755
2756     public void reconnect(String actionId) {
2757       myShadow.reconnect(myActionManager.getAction(actionId));
2758     }
2759
2760     protected abstract void _update(AnActionEvent e, final JBTabsImpl tabs, int selectedIndex);
2761
2762     public final void actionPerformed(final AnActionEvent e) {
2763       JBTabsImpl tabs = e.getData(NAVIGATION_ACTIONS_KEY);
2764       tabs = findNavigatableTabs(tabs);
2765       if (tabs == null) return;
2766
2767       final int index = tabs.myVisibleInfos.indexOf(tabs.getSelectedInfo());
2768       if (index == -1) return;
2769       _actionPerformed(e, tabs, index);
2770     }
2771
2772     protected abstract void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex);
2773   }
2774
2775   private static class SelectNextAction extends BaseNavigationAction {
2776
2777     private SelectNextAction(JBTabsImpl tabs, ActionManager mgr) {
2778       super(IdeActions.ACTION_NEXT_TAB, tabs, mgr);
2779     }
2780
2781     protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2782       e.getPresentation().setEnabled(tabs.findEnabledForward(selectedIndex, true) != null);
2783     }
2784
2785     protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2786       tabs.select(tabs.findEnabledForward(selectedIndex, true), true);
2787     }
2788   }
2789
2790   private static class SelectPreviousAction extends BaseNavigationAction {
2791     private SelectPreviousAction(JBTabsImpl tabs, ActionManager mgr) {
2792       super(IdeActions.ACTION_PREVIOUS_TAB, tabs, mgr);
2793     }
2794
2795     protected void _update(final AnActionEvent e, final JBTabsImpl tabs, int selectedIndex) {
2796       e.getPresentation().setEnabled(tabs.findEnabledBackward(selectedIndex, true) != null);
2797     }
2798
2799     protected void _actionPerformed(final AnActionEvent e, final JBTabsImpl tabs, final int selectedIndex) {
2800       tabs.select(tabs.findEnabledBackward(selectedIndex, true), true);
2801     }
2802   }
2803
2804   private void disposePopupListener() {
2805     if (myActivePopup != null) {
2806       myActivePopup.removePopupMenuListener(myPopupListener);
2807       myActivePopup = null;
2808     }
2809   }
2810
2811   public JBTabsPresentation setStealthTabMode(final boolean stealthTabMode) {
2812     myStealthTabMode = stealthTabMode;
2813
2814     relayout(true, false);
2815
2816     return this;
2817   }
2818
2819   public boolean isStealthTabMode() {
2820     return myStealthTabMode;
2821   }
2822
2823   public JBTabsPresentation setSideComponentVertical(final boolean vertical) {
2824     myHorizontalSide = !vertical;
2825
2826     for (TabInfo each : myVisibleInfos) {
2827       each.getChangeSupport().firePropertyChange(TabInfo.ACTION_GROUP, "new1", "new2");
2828     }
2829
2830
2831     relayout(true, false);
2832
2833     return this;
2834   }
2835
2836   public JBTabsPresentation setSingleRow(boolean singleRow) {
2837     myLayout = singleRow ? mySingleRowLayout : myTableLayout;
2838
2839     relayout(true, false);
2840
2841     return this;
2842   }
2843
2844   public JBTabsPresentation setGhostsAlwaysVisible(final boolean visible) {
2845     myGhostsAlwaysVisible = visible;
2846
2847     relayout(true, false);
2848
2849     return this;
2850   }
2851
2852   public boolean isGhostsAlwaysVisible() {
2853     return myGhostsAlwaysVisible;
2854   }
2855
2856   public boolean isSingleRow() {
2857     return getEffectiveLayout() == mySingleRowLayout;
2858   }
2859
2860   public boolean isSideComponentVertical() {
2861     return !myHorizontalSide;
2862   }
2863
2864   public TabLayout getEffectiveLayout() {
2865     if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout;
2866     return mySingleRowLayout;
2867   }
2868
2869   public JBTabsPresentation setUiDecorator(UiDecorator decorator) {
2870     myUiDecorator = decorator == null ? ourDefaultDecorator : decorator;
2871     applyDecoration();
2872     return this;
2873   }
2874
2875   protected void setUI(final ComponentUI newUI) {
2876     super.setUI(newUI);
2877     applyDecoration();
2878   }
2879
2880   public void updateUI() {
2881     super.updateUI();
2882     SwingUtilities.invokeLater(new Runnable() {
2883       public void run() {
2884         applyDecoration();
2885
2886         revalidateAndRepaint(false);
2887       }
2888     });
2889   }
2890
2891   private void applyDecoration() {
2892     if (myUiDecorator != null) {
2893       UiDecorator.UiDecoration uiDecoration = myUiDecorator.getDecoration();
2894       for (TabLabel each : myInfo2Label.values()) {
2895         each.apply(uiDecoration);
2896       }
2897     }
2898
2899
2900     for (TabInfo each : getTabs()) {
2901       adjust(each);
2902     }
2903
2904     relayout(true, false);
2905   }
2906
2907   private void adjust(final TabInfo each) {
2908     if (myAdjustBorders) {
2909       UIUtil.removeScrollBorder(each.getComponent());
2910     }
2911   }
2912
2913   public void sortTabs(Comparator<TabInfo> comparator) {
2914     Collections.sort(myVisibleInfos, comparator);
2915
2916     relayout(true, false);
2917   }
2918
2919   public boolean isRequestFocusOnLastFocusedComponent() {
2920     return myRequestFocusOnLastFocusedComponent;
2921   }
2922
2923   public JBTabsPresentation setRequestFocusOnLastFocusedComponent(final boolean requestFocusOnLastFocusedComponent) {
2924     myRequestFocusOnLastFocusedComponent = requestFocusOnLastFocusedComponent;
2925     return this;
2926   }
2927
2928
2929   @Nullable
2930   public Object getData(@NonNls final String dataId) {
2931     if (myDataProvider != null) {
2932       final Object value = myDataProvider.getData(dataId);
2933       if (value != null) return value;
2934     }
2935
2936     if (SwitchProvider.KEY.getName().equals(dataId) && myOwnSwitchProvider) {
2937       return this;
2938     }
2939
2940     if (QuickActionProvider.KEY.getName().equals(dataId)) {
2941       return this;
2942     }
2943
2944     return NAVIGATION_ACTIONS_KEY.is(dataId) ? this : null;
2945   }
2946
2947   public List<AnAction> getActions(boolean originalProvider) {
2948     ArrayList<AnAction> result = new ArrayList<AnAction>();
2949
2950     TabInfo selection = getSelectedInfo();
2951     if (selection != null) {
2952       ActionGroup group = selection.getGroup();
2953       if (group != null) {
2954         AnAction[] children = group.getChildren(null);
2955         for (int i = 0; i < children.length; i++) {
2956           result.add(children[i]);
2957         }
2958       }
2959     }
2960
2961     return result;
2962   }
2963
2964   public DataProvider getDataProvider() {
2965     return myDataProvider;
2966   }
2967
2968   public JBTabsImpl setDataProvider(@NotNull final DataProvider dataProvider) {
2969     myDataProvider = dataProvider;
2970     return this;
2971   }
2972
2973
2974   public boolean isSelectionClick(final MouseEvent e, boolean canBeQuick) {
2975     if (e.getClickCount() == 1 || canBeQuick) {
2976       if (!e.isPopupTrigger()) {
2977         return e.getButton() == MouseEvent.BUTTON1 && !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
2978       }
2979     }
2980
2981     return false;
2982   }
2983
2984
2985   private static class DefautDecorator implements UiDecorator {
2986     @NotNull
2987     public UiDecoration getDecoration() {
2988       return new UiDecoration(null, new Insets(0, 4, 0, 5));
2989     }
2990   }
2991
2992   public Rectangle layout(JComponent c, Rectangle bounds) {
2993     final Rectangle now = c.getBounds();
2994     if (!bounds.equals(now)) {
2995       c.setBounds(bounds);
2996     }
2997     c.putClientProperty(LAYOUT_DONE, Boolean.TRUE);
2998
2999     return bounds;
3000   }
3001
3002   public Rectangle layout(JComponent c, int x, int y, int width, int height) {
3003     return layout(c, new Rectangle(x, y, width, height));
3004   }
3005
3006   public static void resetLayout(JComponent c) {
3007     if (c == null) return;
3008     c.putClientProperty(LAYOUT_DONE, null);
3009     c.putClientProperty(STRETCHED_BY_WIDTH, null);
3010   }
3011
3012   private void applyResetComponents() {
3013     for (int i = 0; i < getComponentCount(); i++) {
3014       final Component each = getComponent(i);
3015       if (each instanceof JComponent) {
3016         final JComponent jc = (JComponent)each;
3017         final Object done = jc.getClientProperty(LAYOUT_DONE);
3018         if (!Boolean.TRUE.equals(done)) {
3019           layout(jc, new Rectangle(0, 0, 0, 0));
3020         }
3021       }
3022     }
3023   }
3024
3025
3026   @NotNull
3027   public JBTabsPresentation setTabLabelActionsMouseDeadzone(final TimedDeadzone.Length length) {
3028     myTabActionsMouseDeadzone = length;
3029     final List<TabInfo> all = getTabs();
3030     for (TabInfo each : all) {
3031       final TabLabel eachLabel = myInfo2Label.get(each);
3032       eachLabel.updateTabActions();
3033     }
3034     return this;
3035   }
3036
3037   @NotNull
3038   public JBTabsPresentation setTabsPosition(final JBTabsPosition position) {
3039     myPosition = position;
3040     relayout(true, false);
3041     return this;
3042   }
3043
3044   public JBTabsPosition getTabsPosition() {
3045     return myPosition;
3046   }
3047
3048   public TimedDeadzone.Length getTabActionsMouseDeadzone() {
3049     return myTabActionsMouseDeadzone;
3050   }
3051
3052   public JBTabsPresentation setTabDraggingEnabled(boolean enabled) {
3053     myTabDraggingEnabled = enabled;
3054     return this;
3055   }
3056
3057   public boolean isTabDraggingEnabled() {
3058     return myTabDraggingEnabled;
3059   }
3060
3061   public JBTabsPresentation setProvideSwitchTargets(boolean provide) {
3062     myOwnSwitchProvider = provide;
3063     return this;
3064   }
3065
3066   void reallocate(TabInfo source, TabInfo target, boolean before) {
3067     if (source == target || source == null || target == null) return;
3068
3069     final int targetIndex = myVisibleInfos.indexOf(target);
3070     final int sourceIndex = myVisibleInfos.indexOf(source);
3071
3072     boolean needsValidation = false;
3073
3074     myVisibleInfos.remove(source);
3075     myVisibleInfos.add(targetIndex, source);
3076     needsValidation = true;
3077
3078     //if (before && targetIndex < sourceIndex || !before && targetIndex > sourceIndex) {
3079     //}
3080
3081     if (needsValidation) {
3082       invalidate();
3083       relayout(true, true);
3084     }
3085   }
3086
3087   boolean isHorizontalTabs() {
3088     return getTabsPosition() == JBTabsPosition.top || getTabsPosition() == JBTabsPosition.bottom;
3089   }
3090
3091   public void putInfo(@NotNull Map<String, String> info) {
3092     final TabInfo selected = getSelectedInfo();
3093     if (selected != null) {
3094       selected.putInfo(info);
3095     }
3096   }
3097
3098   public boolean isUseBufferedPaint() {
3099     return myUseBufferedPaint;
3100   }
3101
3102   public void setUseBufferedPaint(boolean useBufferedPaint) {
3103     myUseBufferedPaint = useBufferedPaint;
3104     revalidate();
3105     repaint();
3106   }
3107
3108   public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
3109     ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
3110     for (TabInfo each : myVisibleInfos) {
3111       result.add(new TabTarget(each));
3112     }
3113
3114     if (originalProvider && mySwitchDelegate != null) {
3115       List<SwitchTarget> additional = mySwitchDelegate.getTargets(onlyVisible, false);
3116       if (additional != null) {
3117         result.addAll(additional);
3118       }
3119     }
3120
3121     return result;
3122   }
3123
3124
3125   public SwitchTarget getCurrentTarget() {
3126     if (mySwitchDelegate != null) {
3127       SwitchTarget selection = mySwitchDelegate.getCurrentTarget();
3128       if (selection != null) return selection;
3129     }
3130
3131     return new TabTarget(getSelectedInfo());
3132   }
3133
3134   private class TabTarget extends ComparableObject.Impl implements SwitchTarget {
3135
3136     private final TabInfo myInfo;
3137
3138     private TabTarget(TabInfo info) {
3139       myInfo = info;
3140     }
3141
3142     public ActionCallback switchTo(boolean requestFocus) {
3143       return select(myInfo, requestFocus);
3144     }
3145
3146     public boolean isVisible() {
3147       return getRectangle() != null;
3148     }
3149
3150     public RelativeRectangle getRectangle() {
3151       TabLabel label = myInfo2Label.get(myInfo);
3152       if (label.getRootPane() == null) return null;
3153
3154       Rectangle b = label.getBounds();
3155       b.x += 2;
3156       b.width -= 4;
3157       b.y += 2;
3158       b.height -= 4;
3159       return new RelativeRectangle(label.getParent(), b);
3160     }
3161
3162     public Component getComponent() {
3163       return myInfo2Label.get(myInfo);
3164     }
3165
3166     @Override
3167     public String toString() {
3168       return myInfo.getText();
3169     }
3170
3171     @Override
3172     public Object[] getEqualityObjects() {
3173       return new Object[] {myInfo};
3174     }
3175   }
3176
3177   @Override
3178   public void resetDropOver(TabInfo tabInfo) {
3179     if (myDropInfo != null) {
3180       TabInfo dropInfo = myDropInfo;
3181       myDropInfo = null;
3182       setDropInfoIndex(-1);
3183       removeTab(dropInfo, null, false, true);
3184     }
3185   }
3186
3187   @Override
3188   public Image startDropOver(TabInfo tabInfo, RelativePoint point) {
3189     myDropInfo = tabInfo;
3190
3191     int index = myLayout.getDropIndexFor(point.getPoint(this));
3192     setDropInfoIndex(index);
3193     addTab(myDropInfo, index, true);
3194
3195     TabLabel label = myInfo2Label.get(myDropInfo);
3196     Dimension size = label.getPreferredSize();
3197     label.setBounds(0, 0, size.width, size.height);
3198
3199     BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
3200     Graphics2D g = img.createGraphics();
3201     label.paintOffscreen(g);
3202     g.dispose();
3203
3204     relayout(true, false);
3205
3206     return img;
3207   }
3208
3209   @Override
3210   public void processDropOver(TabInfo over, RelativePoint point) {
3211     int index = myLayout.getDropIndexFor(point.getPoint(this));
3212     if (index != getDropInfoIndex()) {
3213       setDropInfoIndex(index);
3214       relayout(true, false);
3215     }
3216   }
3217
3218   public int getDropInfoIndex() {
3219     return myDropInfoIndex;
3220   }
3221
3222   @Override
3223   public boolean isEmptyVisible() {
3224     return myVisibleInfos.isEmpty();
3225   }
3226
3227   @Override
3228   public int getInterTabSpaceLength() {
3229     return 1;
3230   }
3231
3232   @Override
3233   public String toString() {
3234     return "JBTabs visible=" + myVisibleInfos + " selected=" + mySelectedInfo;
3235   }
3236 }